Комментарии 46
А ничё, что предыдущие ораторы считали не только слова?
Это требование убивает правило 32х и заставляет учитывать предыдущий символ, что в арифметике добавляет заметное проседание.
А то так-то я умею ванлайнер на пейтоне, который посчитает байты за наносекунду.
Очень интересно посмотреть будет.
Во-вторых, не очень понял суть Вашей претензии. Я посчитал слова/строки/байты, вроде бы это все, разве нет? И что значит я не учитываю предыдущий символ?
На счет <= ' ' — да, скрупулезная проверка на пробелы посложнее с точки зрения арифметики. Если будет возможность — может быть реализую и ее.
Ох. Как на перемену из третьего класса вышел, прям.
Я не уверен, я думаю, что в 2020 все компиляторы в натив сделают примерно одинаково.
Все виртуальные машины сделают чуть хуже, но не сильно.
Всех, кто так не сможет, надо на свалку.
И забавно, что на моей рабочей машине, с 32 ядрами, и идиоматичный си, и идиоматичный хаскель, влетают идиоматичному эрлангу на почти порядок.
Потому что в 2020 тут рулит идиоматичность распараллеливания, а не опций компилятора.
Убираю линейку с видом победителя, иду на урок.
Поделить файл — это как? Мне мать писала, что хаскель ленив до жути.
И в языке, аоторый из коробки готов к распараллеливанию, об этом думать не надо. Я именно об этом.
Думать о параллелизме надо всегда [когда не map
]
И да, и нет. Да — в смысле, нужно понять, когда эмитнуть синхронизацию (в совсем запутанных случаях). Нет — в смысле вот такого кошмара, как выше, в клиентском коде быть не должно.
Сравните с кодом из моего гиста:
file
|> File.stream!([], @chunk)
|> Flow.from_enumerable()
|> Flow.partition()
|> Flow.reduce(fn -> @acc end, &parse/2)
|> Enum.to_list()
На 28-29 примерно :)
FSM в наивной реализации будет в разы медленнее показанной наивной реализации на C. Но если продолжить вашу мысль в верном направлении, то получится что-то подобное.
Если не затруднит, то прогоните пожалуйста на вашей машине вариант с развернутым циклом (т.е. с заменой этого цикла на код под спойлером). Хочется нащупать отличие кода из-под ghc
от C
.
- Для полноты картины в статье конечно не хватает отсылки к не-наивной переносимой реализации (aka "С" наносит ответный удар"), в том числе замера её скорости на вашей машине. На всякий — popcount/Hamming_weight в там нет намеренно ради переносимости.
- О том что производительность "на SIMDах" будет в пределах memory bandwidth было заявлено примерно сразу. Тем не менее, соглашусь что многим это не очевидно и лучше "показать на пальцах".
- Вы еще раз поменяли условия/требования (выбросили табуляции и т.п.), чем еще больше понизили градус смысла в этом флешмобе бенчмарков.
В контрольных цифрах должно бытьlines 15000000, words 44774631, chars 1871822210
. - Другой CPU, другой компилятор, другая ОС. Поэтому недостаёт результатов работы других реализаций в ваших условиях.
- По информации 0xd34df00d Хаскель быстрее наивной С-реализации (я имею в виду комментарий "Но ведь не обходит. 2 секунды для С против 1.45 для хаскеля"). Если я правильно понимаю, причина в достаточно простой "механике":
ghc
(хаскель-компилятор) немного раскатывает вот этот цикл (aka loop unrolling). Точнее говоря, не раскатывает, а не полностью сворачивает (aka fold), и такой цикл отрабатывает быстрее. Предлагаю всем самостоятельно оценить насколько это определяющий критерий победы, с учетом того что clang с-march=native
заставляет наивный код работать быстрее хаскеля.
В моём понимании C-цикл под спойлером должен работать "со скоростью хаскеля". Хорошо-бы кто-то проверил (достаточно вставить этот кусок в код по первой ссылке и сравнить скорость с Хаскель-реализацией на одной машине).
for (size_t i = 0; i < bytes; i += 8) {
unsigned char c0 = text[i];
unsigned char c1 = text[i+1];
unsigned char c2 = text[i+2];
unsigned char c3 = text[i+3];
unsigned char c4 = text[i+4];
unsigned char c5 = text[i+5];
unsigned char c6 = text[i+6];
unsigned char c7 = text[i+7];
result.lines += c0 == '\n';
result.lines += c1 == '\n';
result.lines += c2 == '\n';
result.lines += c3 == '\n';
result.lines += c4 == '\n';
result.lines += c5 == '\n';
result.lines += c6 == '\n';
result.lines += c7 == '\n';
_Bool x0 = (c0 != ' ') && (c0 - 9 > 4);
_Bool x1 = (c1 != ' ') && (c1 - 9 > 4);
_Bool x2 = (c2 != ' ') && (c2 - 9 > 4);
_Bool x3 = (c3 != ' ') && (c3 - 9 > 4);
_Bool x4 = (c4 != ' ') && (c4 - 9 > 4);
_Bool x5 = (c5 != ' ') && (c5 - 9 > 4);
_Bool x6 = (c6 != ' ') && (c6 - 9 > 4);
_Bool x7 = (c7 != ' ') && (c7 - 9 > 4);
result.words += x0 && !prev;
result.words += x1 && !x0;
result.words += x2 && !x1;
result.words += x3 && !x2;
result.words += x4 && !x3;
result.words += x5 && !x4;
result.words += x6 && !x5;
result.words += x7 && !x6;
prev = x7;
}~~~
<!--</spoiler>-->
А ghc (вроде-бы) умеет в C
компилировать? Весьма вероятно этого будет достаточно.
Кстати, ассемблерный вывод ghc
совсем сухой, без его "технических" комментариев?
Может какой-нибудь ключик есть для аннотации?
Должны быть какие-то средства навигации или "дорожные столбы", чтобы сами разработчики ghc поманили что из чего получилось.
Очередная статья про wc