Как минимум год назад, но суть не в этом. Речь идёт про обучение, где нужно понять, как такие вещи в принципе делаются, прежде чем бросаться использовать библиотечные функции.
Как бы я ни хотел найти лучшую замену Питону для обучения, но Go для этого не создавался. Насколько я помню, львиную долю времени мы, будучи школьниками и студентами, писали алгоритмы и структуры данных. Без обобщений такие вещи делать некрасиво, а пользоваться втсроенными в язык — значит ничему не научиться.
И раз уж мы затронули эту злободневную тему, не расскажете ли преимущества «бутсрапинга или кодогенерации» перед нормальными обобщениями?
У нас тут не портал начинающих писателей, знаете ли, а технический ресурс. Автор виновен не только в бранности, но прежде всего — в несостоятельности своей позиции. Nim — замечательный язык, надежда многих разработчиков на хорошую смесь C++ и Питоном, но говорить о продуктивности здесь и сейчас как-то странно, учитывая нестабильность языка и неразвитость экосистемы.
Quick Sort, который на данный момент не поддается распараллеливанию
А можно здесь подробнее? Quick Sort ведь классический пример подхода «разделяй и властвуй», который в целом неплохо распараллеливается: разделил массив на 2 части (бОльшую и меньшую, чем якорь) и обрабатывай их параллельно, каждая часть в итоге может ветвиться точно также.
В «классической» реализации Radix Sort хранить значения предлагается в самом radix массиве, т.е. каждый элемент это тоже массив, в который записываются исходные числа, как раз здесь скрыта причина высокого расхода памяти.
Это где вы такую реализацию видели? O_o
Если позиция копирования числа уже занята, то ищем первый не занятый элемент в итоговом массиве.
И далеко вы собираетесь идти искать?
В результате был найден третий способ, более оптимально использующий память, но требующий дополнительного прохода radix массива после его заполнения
Не очевидно это. Как вы собираетесь распараллеливать?
Локальность данных при обработке — Достаточная
Финальная фаза, где заполняется результирующий массив, пишет элементы в разные участки памяти на каждой итерации. Трудно придумать сценарий хуже. Вы как-бы и сами пишете, что доступ к памяти «Умеренно случайный», что намекает.
Про 10 лет, Вы, конечно, преувеличили. Но вот «прямо сейчас», о чём так настойчиво говорит автор, ехать на этом феррари как-то рискованно, учитывая, что двигатель и колёса могут ещё пару раз заменить прямо на ходу.
Так… автор же об этом и пишет в последующем абзаце:
то для людей создающих платформу, на которой предполагается одновременный запуск сотен плагинов, различных версий и от десятка не знающих друг о друге вендоров любая лишняя зависимость — зло
Простите, но тут сплошной разброд и шатание, а не статья. Автор бегает вокруг, брюзжет на всё подряд, попутно не забывая показать свой многолетний опыт и навыки.
Слово «продуктивность» упоминается чуть ли не в каждом предложении, в то время как качеству, надёжности и производительности почти не уделено внимание (типа, железо уже сильное, не парьтесь). Создаётся вполне определённое впечатление о том, какого рода код пишет автор и его команда. Это код, измеряемый строчками в секунду… Быстрее, больше, ещё больше!
Хорошо написано! Пару слов меня одёрнули, впрочем («первоманс», «заимитить»), но это мелочи.
По представлению карты теней для всенаправленного источника света — помимо cube map и dual-paraboloid есть ещё такая интересная экзотика как тетраэдральные карты (tetrahedral shadow maps). Они требуют 4 клона геометрии вместо 6 и при этом не искажают результат как параболоиды. К сожалению, данный подход недостаточно изучен до сих пор, трудно найти приличные примеры кода.
Про depth bias — в DirectX 9 он ведь тоже был, пусть и в несколько ином формате. В принципе, ничего не мешало его использовать там, просто для подсчёта абсолютного смещения нужно было учитывать разрядность карты глубины.
Дьявол, как говорят, — в деталях. Дело не в языке, а в конкретных программах, до которых ещё не добрались оптимизаторы. По факту там яблоки с грушами сравнивают. На GСС используют SSE, OpenMP и явную многозадачность. На Rust код как из учебника (740 символов на regex-dna против 2579 на С).
Единственный пример, до которого «добрались» — k-nucleotide — почему-то рвёт GCC и по скорости, и по памяти, и по размеру кода. Так что я не вижу в этих цифрах проблемы, всё образуется ;)
Спасибо! Надеюсь, это развеит сомнения по поводу скорости в Rust.
Имейте ввиду, что основной целью последнего чуть ли не кода разработки была стабилизация API. Оптимизация же не была приоритетна, но ей обязательно займутся сейчас, после 1.0, не нарушая интерфейсов. Уверен, у разработчиков ещё есть пара спрятаных козырей, которые позволят ещё более разогнать язык.
Программа либо устанавливала флаг ошибки, либо возвращала код, который проверялся вызывающей стороной.
…
У обоих решений имеется общий недостаток: они загромождают код на стороне вызова. Вызывающая сторона должна проверять ошибки немедленно после вызова. К сожалению об этом легко забыть.
Кажется, автор не принимал во внимание алгебраические типы. С ними невозможно «забыть» проверить ошибки, а добавление try! (в простом случае) не особо нагромождает код:
let x = try!(foo());
bar(x);
Качество кода возросло, потому что два аспекта, которые прежде были тесно переплетены — алгоритм отключения устройства и обработка ошибок, — теперь изолированы друг от друга.
Сомнительное повышение качества. Хорошо разделять системы, работающие независимо друг от друга. Здесь же — обработка ошибок непосредственно влияет на алгоритм (прерывая его), а алгоритм — на обработку ошибок (требование сохранять инварианты).
Вы просто в примере написали println!("{}", it.next()), что явно указывает, что it — итератор. Чем всё-таки не подходит мой пример с while/next?
Стандартный способ — peek/match/next.
Это где так, в Kotlin?
Если речь о вводе-выводе, где next() — блокирующий вызов (и потому мы не хотим его вызывать пока не знаем точно, что ждать не придётся), то лучше не использовать итератор вовсе. К примеру, если у Вас есть Receiver, то можно написать цикл приёма сообщений так:
while let Ok(msg) = receiver.try_recv() {
println!("{}", msg);
}
1) Обещали стабильность самого языка, а не стандартной библиотеки. Как правильно, нестабильные методы являются вторичными. К примеру, вместо vec.push_all() можно использовать более универсальный vec.extend()
2) Не вижу в этом ошибки механизма. Что такое it у Вас? Если это другой итератор (тогда iter — итератор над итераторами?), то он должен быть изменяемым для вызова it.next(), в то время как peek() позволяет только подсмотреть следующее значение, но никак не поменять его.
Полагаю, на самом деле Вы хотели так:
while let Some(val) = iter.next() {
println!("{}", val);
}
3) Смотрим на Peekable, видим структуру с параметром I: Iterator. Ваша функция, следовательно, может выглядеть так:
А можно ссылку на форумы или куда-нибудь ещё, где ситуация описывается? Не может быть Rust настолько медленнее. Вы в release собирали? Простите за банальный вопрос :) Никогда не слышал о нерешаемых проблемах с производительностью в Rust.
Я понимаю, о чём Вы, но не совсем разделяю. Допустим, у вас есть такой код внутри функции:
foo(x);
bar(y);
Спаведливо (интуитивно? очевидно?) полагать, что сначала вызовется foo, а потом bar, как и написано. Однако, исключение в foo, которое здесь не отлавливается, может сорвать вызов bar. Таким образом, может быть нарушена целостность объекта, инвариант состояния, и т. д. (допустим, разрыв пары lock/unlock).
Чтобы понять, что произойдёт в действительности, программисту нужно знать, какие исключения и при каких условиях бросают foo и bar, что уже совсем не очевидно.
И раз уж мы затронули эту злободневную тему, не расскажете ли преимущества «бутсрапинга или кодогенерации» перед нормальными обобщениями?
А можно здесь подробнее? Quick Sort ведь классический пример подхода «разделяй и властвуй», который в целом неплохо распараллеливается: разделил массив на 2 части (бОльшую и меньшую, чем якорь) и обрабатывай их параллельно, каждая часть в итоге может ветвиться точно также.
Это где вы такую реализацию видели? O_o
И далеко вы собираетесь идти искать?
Как ваш способ соотносится со стандартной реализацией?
Не очевидно это. Как вы собираетесь распараллеливать?
Финальная фаза, где заполняется результирующий массив, пишет элементы в разные участки памяти на каждой итерации. Трудно придумать сценарий хуже. Вы как-бы и сами пишете, что доступ к памяти «Умеренно случайный», что намекает.
Слово «продуктивность» упоминается чуть ли не в каждом предложении, в то время как качеству, надёжности и производительности почти не уделено внимание (типа, железо уже сильное, не парьтесь). Создаётся вполне определённое впечатление о том, какого рода код пишет автор и его команда. Это код, измеряемый строчками в секунду… Быстрее, больше, ещё больше!
По представлению карты теней для всенаправленного источника света — помимо cube map и dual-paraboloid есть ещё такая интересная экзотика как тетраэдральные карты (tetrahedral shadow maps). Они требуют 4 клона геометрии вместо 6 и при этом не искажают результат как параболоиды. К сожалению, данный подход недостаточно изучен до сих пор, трудно найти приличные примеры кода.
Про depth bias — в DirectX 9 он ведь тоже был, пусть и в несколько ином формате. В принципе, ничего не мешало его использовать там, просто для подсчёта абсолютного смещения нужно было учитывать разрядность карты глубины.
Единственный пример, до которого «добрались» — k-nucleotide — почему-то рвёт GCC и по скорости, и по памяти, и по размеру кода. Так что я не вижу в этих цифрах проблемы, всё образуется ;)
Имейте ввиду, что основной целью последнего чуть ли не кода разработки была стабилизация API. Оптимизация же не была приоритетна, но ей обязательно займутся сейчас, после 1.0, не нарушая интерфейсов. Уверен, у разработчиков ещё есть пара спрятаных козырей, которые позволят ещё более разогнать язык.
Насколько я вижу, ничто не запрещает использовать «f» несмотря на возникшую ошибку.
Кажется, автор не принимал во внимание алгебраические типы. С ними невозможно «забыть» проверить ошибки, а добавление try! (в простом случае) не особо нагромождает код:
Сомнительное повышение качества. Хорошо разделять системы, работающие независимо друг от друга. Здесь же — обработка ошибок непосредственно влияет на алгоритм (прерывая его), а алгоритм — на обработку ошибок (требование сохранять инварианты).
Это где так, в Kotlin?
Если речь о вводе-выводе, где next() — блокирующий вызов (и потому мы не хотим его вызывать пока не знаем точно, что ждать не придётся), то лучше не использовать итератор вовсе. К примеру, если у Вас есть Receiver, то можно написать цикл приёма сообщений так:
2) Не вижу в этом ошибки механизма. Что такое it у Вас? Если это другой итератор (тогда iter — итератор над итераторами?), то он должен быть изменяемым для вызова it.next(), в то время как peek() позволяет только подсмотреть следующее значение, но никак не поменять его.
Полагаю, на самом деле Вы хотели так:
3) Смотрим на Peekable, видим структуру с параметром I: Iterator. Ваша функция, следовательно, может выглядеть так:
Документация описывает where и приводит ряд примеров.
Как тестировали? Могу ли я воспроизвести данный тест у себя на компьютере?
Nickel — не единственный вариант. Вот тут можно увидеть альтернативы — arewewebyet.com
Спаведливо (интуитивно? очевидно?) полагать, что сначала вызовется foo, а потом bar, как и написано. Однако, исключение в foo, которое здесь не отлавливается, может сорвать вызов bar. Таким образом, может быть нарушена целостность объекта, инвариант состояния, и т. д. (допустим, разрыв пары lock/unlock).
Чтобы понять, что произойдёт в действительности, программисту нужно знать, какие исключения и при каких условиях бросают foo и bar, что уже совсем не очевидно.
Автор, прошу Вас, меньше воды и капризов, больше практики и глубины.