Дисклаймер: Этот комментарий добавлен не для того чтобы показать "тормознутось" Хаскеля или "превосходство" C (ибо у каждого языка свое назначение), но чтобы обратить внимание что чуть менее чем все подобные "хайповые" бенчмарки и сравнения содержат достаточно недочетов чтобы не принимать их всерьез. При этом всё же уместно напомнить тезис, что языки без zero cost abstraction никогда не смогут конкурировать по скорости кода с теми, где zero cost abstraction есть.
Ниже под спойлером чистый "сишный" функциональный аналог кода на Haskell, без SIMD и прочей "магии". На моём пристарелом ноуте c i7-4600U 2.10GHz он отрабатывает за секунду:
$ clang -Wall -Wextra -Wpedantic -Ofast -march=native naive_wc.c -o naive_wc
$ ./naive_wc /dev/shm/test.txt
lines 15000000, words 44774631, chars 1871822210
took 1.038524 seconds
$ clang --version
clang version 8.0.1- (branches/release_80)
Target: x86_64-pc-linux-gnu
$ grep "model name" /proc/cpuinfo | head -n1
model name : Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz
Для проверке результаты запуска системного wc c теми-же данными:
Файл test.txt был получен ровно как описано в статье, только я сразу разместил его в /dev/shm.
Компилятор clang с -march=native использован чтобы создать максимально равные условия с ghc из статьи. У меня уже была 8-я версия clang, и тратить время на установку более новой я не стал.
Мой ноут с i7-4600U CPU @ 2.10GHz медленнее использованного в статье i7-4770 @ 3.40GHz. Разница как минимум в 1.6 раза, а если судить по времени работы системного wc, то почти в два раза. При этом наивный, но равноценный вариант на C отрабатывает в два раза быстрее. Соответственно нормированная разница, с учетом скорости машин, получается где-то 3-4 раза.
Итого: Наивный код на C быстрее фукционального аналога на Haskell в 3-4 раза.
У меня всё, но прошу перепроверить результаты.
Желающие могут написать статью "Побеждаем Хаскель-флеша 15 строками на С" ;)
в который добавлена пара "свистелок" (например, подсчитывается максимальная "печатная" длина строк).
Так повторюсь: я же их не просил показывать. Если язык не позволяет достаточно легко написать достаточно композабельные вещи, чтобы не считать те штуки, которые не просили, то это вопрос к языку.
Причем тут композиция. Задачи и объем вычислений разный. "Быстрая" реализация на Хаскеле не выполняет ту же работу, в результате было сделано сравнение теплого с мягким.
Даже на очень тривиальной задаче можно руками написать очень тупой и топорный код с парой SIMD-функций, который будет этак в 2.7 быстрее того, что сгенерирует компилятор.
Опять я вас не понимаю. Если точечное, но правильное и по-месту, применение SIMD дает ускорение в разы — то это примерно идеальный вариант оптимизации (максимальный результат при минимальных усилиях).
Более того, если язык или компилятор не позволяет такое (в том числе не умеют zero const abstraction), то они не подходят для задач требующих предельной производительности.
В это можно вкладывать очень разный смысл, особенно формулируя требования/критери переносимости и прозрачности кода, и т.п. Говоря про оптимизированную версию для упомянутого процессора я конечно имел в виду использование всех его возможностей.
Если посмотреть на wc из coreutils, то там простой, переносимый и легко-поддерживаемый код, в который добавлена пара "свистелок" (например, подсчитывается максимальная "печатная" длина строк). Его можно ускорить, особенно если отказаться от чего-либо из перечисленного.
Только на практике это (оптимальность) не реализуется даже для очень простых задач, приходится, например, брать интринсики, что уже не совсем С (и статья про это у меня на очереди).
Тут не совсем понятно что вы имели в виду под "не реализуется". Человеком это не реализуется потому что дорого, в остальном актуальные компиляторы более-менее справляются. Но опять таки, возможность автовекторизации сильно зависит от того как человек сформулировал конкретный алгоритм в терминах конкретного языка.
Использование интринсиков, с моей точки зрения, является штатным средством оптимизации. В том числе, потому что является штатным вызовом функции с точки зрения синтаксиса и семантики языка.
На вашем процессоре (Core i7 4770) действительно "оптимизированная хакерами" версия wc "на C" обработает 1.8G из tmpfs где-то за 0,5 — 0,072 секунды (зависит от компилятора и memory bandwidth), что и будет отражать реальное соотношение "скорости" Хаскель/Си в подобных "задачах".
На всякий: Си будет быстрее не потому что он "хороший", или "Хаскель плохой", а потому что позволяет не делать ничего лишнего и получать машинно-оптимальный код. Подобный вариант на Си конечно потребует больше работы человека. Поэтому такая оптимизация мало где задействуется, а болезный wc хороший тому пример ;)
Автор оригинала совсем не прав, ибо сравнил теплое с мягким и сделал вывод что одно кислее другого. Еще и Александреску приплел.
Скорость вброса-обработки исключений не влияет на производительность, так как в нормальном коде исключения очень редки.
Рецепт Александреску не про производительность, а для возврата и обработки ошибок в Go-стиле, что во многих случаях проще-удобнее по ряду причин.
Предлагаемый "возврат исключений" быстрее только в случае возврата псевдо-исключений с тривиальной структурой (код ошибки), что и демонстрируют результаты (замедление в ~5 раз для normal-path).
Если не округлять углы, то в статье примерно пурга, которая только путает "джунов".
В Mirai у Тойоты два баллона примерно по 60 литров, под давлением порядка 700 кг/см2. Машине весом 2 тонны (вместе с пассажирами) этого хватает где-то на 500 км.
Вопрос сводится к тому насколько безопасным можно считать эти 120 литров под таким давлением, включая топлевопроводы (от заправочного штуцера до выходного редуктора) в условиях аварии. Стоит напомнить, что давления 300-500 (тем более 700) достаточно для самовоспламенения водорода.
Есть разные оценки рисков взрыва, но их достаточно сложно трактовать, верифицировать и оценивать последствия. К примеру, в этой работе делается попытка оценить риски для ряда простейших случаев, с результатом 1 взрыв на 2000 машин в год. Однако, есть нюансы:
Оценка для конструкции с одним баком. Если же баков два, то какое-то слагаемое нужно умножать на два.
Если бак один, но большой, то может быть хуже, ибо площадь поверхности баллона больше, а вероятность повреждения и модуль разрывающей силы пропорциональны площади.
При оценке рисков не учли эффекта самовоспламенение водорода (hydrogen autoignition).
Без информации о методе хранения водорода и его доказанной безопасности — это в лучшем случае "фотошопный фуфлотрак" для легального отъема денег у бестолочей-инвесторов, а в худшем — компактная версия "Гинденбурга", которую нельзя выпускать на дороги.
В качестве такой информации лучше всего выступят ссылки на патенты, которыми владеет или может пользоваться обозначенная компания.
В NTFS есть две кошмарно медленные вещи: сжатие и TxF. Они настолько медленные и неэффективные относительно современного уровня развития подобных технологий, что ими пользуются только по незнанию реального положения дел.
Тем не менее, думаю можно быть уверенными в том, что ни "компрессию" ни "транзакции" никогда не выпилят из NTFS. M$ либо оставит всё как есть до смерти Windows, либо перейдет на новую файловую систему без этих deprecated features.
Взял исходники здесь и добавил в обозначенный выше бенчмарк.
Пришлось немного поправить:
Вместо int сортировать int64_t, как в остальных сортировках в бенчмарке.
Увязать delete[] и malloc() в бенчмарке (он примерно на C):
каждый delete[] ptr => if (pfr != malloced_array) delete[] ptr;
копировать отсортированный массив если он был перевыделен (да, это искажает результаты, но не принципиально).
В сухом остатке у показанной сортировки:
большие проблемы с low cardinality, до 1000 раз медленнее
на случайных данных вдвое медленнее std::sort
на упорядоченных данных часто примерно как timsort, иногда выигрывает в 2 раза, но местами проигрывает в 4-5-6 раз.
В самом бенчмарке я до этого уже добавил несколько тестов (вариантов распределения значений для сортировки) и две свои сортирующие функции (посмотреть можно тут).
gcc версия 9.2.1 20190827 (Red Hat 9.2.1-1) (GCC)
Целевая архитектура: x86_64-redhat-linux
CFLAGS = -Ofast -g -Wall -pedantic
Хм, только сейчас обратил внимание на то, как вычисляется "Result %".
Некий "шарообразный в вакууме" оптимум точно найден, осталось понять для чего он оптимален )
Их линейка кривовата относительно здравого смысла. Оптимум выбирается видимо "по-банковски", как лучшая сделка из совершенных по покупке сжатия за время. Но ошибка в том, что в "пропорцию" на равных попадают все варианты, включая самые долгие, хотя жмут лишь чуточку лучше. Соответственно, время обесценивается относительно степени сжатия.
Нельзя сказать что это как-то совсем не правильно, критерии могут быть разные.
Но один из вариантов (с точки зрения оптимизации) — нулевое время без сжатия, и он линейку ломает.
А если добавить еще несколько экстремально долгих вариантов, то перекос будет еще больше.
Если вам действительно нужен рационально выверенный баланс между скоростью сжатия/разжатия и уровнем компрессии, то нужно использовать современные алгоритмы. Соответственно см https://github.com/mcmilk/7-Zip-zstd (там несколько алгоритмов, не только zstd).
Дисклаймер: Этот комментарий добавлен не для того чтобы показать "тормознутось" Хаскеля или "превосходство" C (ибо у каждого языка свое назначение), но чтобы обратить внимание что чуть менее чем все подобные "хайповые" бенчмарки и сравнения содержат достаточно недочетов чтобы не принимать их всерьез. При этом всё же уместно напомнить тезис, что языки без zero cost abstraction никогда не смогут конкурировать по скорости кода с теми, где zero cost abstraction есть.
Ниже под спойлером чистый "сишный" функциональный аналог кода на Haskell, без SIMD и прочей "магии". На моём пристарелом ноуте c i7-4600U 2.10GHz он отрабатывает за секунду:
Для проверке результаты запуска системного
wc
c теми-же данными:Файл
test.txt
был получен ровно как описано в статье, только я сразу разместил его в/dev/shm
.Компилятор
clang
с-march=native
использован чтобы создать максимально равные условия сghc
из статьи. У меня уже была 8-я версия clang, и тратить время на установку более новой я не стал.Мой ноут с i7-4600U CPU @ 2.10GHz медленнее использованного в статье i7-4770 @ 3.40GHz. Разница как минимум в 1.6 раза, а если судить по времени работы системного
wc
, то почти в два раза. При этом наивный, но равноценный вариант на C отрабатывает в два раза быстрее. Соответственно нормированная разница, с учетом скорости машин, получается где-то 3-4 раза.Итого: Наивный код на C быстрее фукционального аналога на Haskell в 3-4 раза.
У меня всё, но прошу перепроверить результаты.
Желающие могут написать статью "Побеждаем Хаскель-флеша 15 строками на С" ;)
P.S. 15 строк имеется в виду непосредственно сам подсчет, между
/* begin */
и/* end */
Причем тут композиция. Задачи и объем вычислений разный. "Быстрая" реализация на Хаскеле не выполняет ту же работу, в результате было сделано сравнение теплого с мягким.
Опять я вас не понимаю. Если точечное, но правильное и по-месту, применение SIMD дает ускорение в разы — то это примерно идеальный вариант оптимизации (максимальный результат при минимальных усилиях).
Более того, если язык или компилятор не позволяет такое (в том числе не умеют zero const abstraction), то они не подходят для задач требующих предельной производительности.
Получается вот такой Хаскель-флеш ;)
В это можно вкладывать очень разный смысл, особенно формулируя требования/критери переносимости и прозрачности кода, и т.п. Говоря про оптимизированную версию для упомянутого процессора я конечно имел в виду использование всех его возможностей.
Если посмотреть на
wc
из coreutils, то там простой, переносимый и легко-поддерживаемый код, в который добавлена пара "свистелок" (например, подсчитывается максимальная "печатная" длина строк). Его можно ускорить, особенно если отказаться от чего-либо из перечисленного.Тут не совсем понятно что вы имели в виду под "не реализуется". Человеком это не реализуется потому что дорого, в остальном актуальные компиляторы более-менее справляются. Но опять таки, возможность автовекторизации сильно зависит от того как человек сформулировал конкретный алгоритм в терминах конкретного языка.
Использование интринсиков, с моей точки зрения, является штатным средством оптимизации. В том числе, потому что является штатным вызовом функции с точки зрения синтаксиса и семантики языка.
На вашем процессоре (Core i7 4770) действительно "оптимизированная хакерами" версия
wc
"наC
" обработает 1.8G изtmpfs
где-то за 0,5 — 0,072 секунды (зависит от компилятора и memory bandwidth), что и будет отражать реальное соотношение "скорости" Хаскель/Си в подобных "задачах".На всякий: Си будет быстрее не потому что он "хороший", или "Хаскель плохой", а потому что позволяет не делать ничего лишнего и получать машинно-оптимальный код. Подобный вариант на Си конечно потребует больше работы человека. Поэтому такая оптимизация мало где задействуется, а болезный
wc
хороший тому пример ;)Автор оригинала совсем не прав, ибо сравнил теплое с мягким и сделал вывод что одно кислее другого. Еще и Александреску приплел.
Если не округлять углы, то в статье примерно пурга, которая только путает "джунов".
В Mirai у Тойоты два баллона примерно по 60 литров, под давлением порядка 700 кг/см2. Машине весом 2 тонны (вместе с пассажирами) этого хватает где-то на 500 км.
Вопрос сводится к тому насколько безопасным можно считать эти 120 литров под таким давлением, включая топлевопроводы (от заправочного штуцера до выходного редуктора) в условиях аварии. Стоит напомнить, что давления 300-500 (тем более 700) достаточно для самовоспламенения водорода.
Есть разные оценки рисков взрыва, но их достаточно сложно трактовать, верифицировать и оценивать последствия. К примеру, в этой работе делается попытка оценить риски для ряда простейших случаев, с результатом 1 взрыв на 2000 машин в год. Однако, есть нюансы:
Без информации о методе хранения водорода и его доказанной безопасности — это в лучшем случае "фотошопный фуфлотрак" для легального отъема денег у бестолочей-инвесторов, а в худшем — компактная версия "Гинденбурга", которую нельзя выпускать на дороги.
В качестве такой информации лучше всего выступят ссылки на патенты, которыми владеет или может пользоваться обозначенная компания.
Пока же запатентованы
рамочкидвери и нарисованы картинки.Из 6 частей осталось одна, так-что очень даже "поменялось" ;)
В NTFS есть две кошмарно медленные вещи: сжатие и TxF. Они настолько медленные и неэффективные относительно современного уровня развития подобных технологий, что ими пользуются только по незнанию реального положения дел.
Тем не менее, думаю можно быть уверенными в том, что ни "компрессию" ни "транзакции" никогда не выпилят из NTFS. M$ либо оставит всё как есть до смерти Windows, либо перейдет на новую файловую систему без этих deprecated features.
Предлагаю добавить первым пунктом совет по переходу на Tarantool, со ссылкой на https://www.tarantool.io/ru/doc/2.2/book/getting_started/
Шикарно
Безумству храбрых поём мы песню!
rela589n, вы видели эти результаты?
Взял исходники здесь и добавил в обозначенный выше бенчмарк.
Пришлось немного поправить:
int
сортироватьint64_t
, как в остальных сортировках в бенчмарке.delete[]
иmalloc()
в бенчмарке (он примерно на C):delete[] ptr
=>if (pfr != malloced_array) delete[] ptr
;В сухом остатке у показанной сортировки:
В самом бенчмарке я до этого уже добавил несколько тестов (вариантов распределения значений для сортировки) и две свои сортирующие функции (посмотреть можно тут).
А какой есть хороший (ну более-менее) набор готовых тестов для оценки сортировок?
Лучше что пока нашел — это "набора сортировок" от Christopher Swenson.
Просто пустой. Видимо автор отложил до одобрения в песочнице и забыл.
Хм, только сейчас обратил внимание на то, как вычисляется "Result %".
Некий "шарообразный в вакууме" оптимум точно найден, осталось понять для чего он оптимален )
Их линейка кривовата относительно здравого смысла. Оптимум выбирается видимо "по-банковски", как лучшая сделка из совершенных по покупке сжатия за время. Но ошибка в том, что в "пропорцию" на равных попадают все варианты, включая самые долгие, хотя жмут лишь чуточку лучше. Соответственно, время обесценивается относительно степени сжатия.
Нельзя сказать что это как-то совсем не правильно, критерии могут быть разные.
Но один из вариантов (с точки зрения оптимизации) — нулевое время без сжатия, и он линейку ломает.
А если добавить еще несколько экстремально долгих вариантов, то перекос будет еще больше.
Если вам действительно нужен рационально выверенный баланс между скоростью сжатия/разжатия и уровнем компрессии, то нужно использовать современные алгоритмы. Соответственно см https://github.com/mcmilk/7-Zip-zstd (там несколько алгоритмов, не только zstd).
Как говориться — Не рой яму другому, а то сам попадешь.