Comments 21
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Автор оригинала: Daniel Lemire
Хм.
Использованный в коде union
содержит поле as_str
. Однако нигде в коде это поле не используется, что говорит о том, что ни это поле никому тут нинафиг не нужно, ни весь union
никому тут нинафиг не нужен. Зачем код замусорен всем этим? В чем глубинный смысл?
В комментариях к оригиналу есть версия:
Neat! I have noticed a small mistake:
memcpy(&digits.as_int, str, sizeof(digits));
should be
memcpy(&digits.as_str, str, sizeof(digits));
Это не ошибка, это бессмыслица. Если уж автор воспользовался memcpy, то union ему действительно не нужен. Собственно, потому он и не заметил этой "ошибки", что код прекрасно работает и так. Union с фиктивными полями иногда приходится использовать для, целей выравнивания, но здесь совсем не тот случай. Автор наворотил ненужной фигни с union поверх вполне работоспособной техники и, похоже, до сих пор не понял, что это лишь ненужная фигня.
Можно вопрос? Зачем нужен digits.as_str, если он нигде не используется?
memcpy(&digits.as_int, str, sizeof(digits));
По-моему, копировать из строки больше байт, чем там есть - это UB, крэш, эксплойт на слив данных из соседней памяти ... и вообще нехорошо.
Согласно https://en.cppreference.com/w/cpp/types/integer типа uint32_t может не быть: provided if and only if the implementation directly supports the type
Возможно, стоит рассмотреть табличный метод: массив 256 байт на первый символ, далее на второй и третий. В телефонии это считается одним из самых быстрых способов делать фиксированные вычисления.
Я теряюсь в догадках, почему наивное решение настолько быстрее, чем стандартная библиотека
Ну я с таким на C# сталкиваюсь постоянно, недавно статью вот даже написал. И это удручает, так как стандартными методами пользуются весьма охотно.
Результаты from_chars совершенно разочаровывают. Я теряюсь в догадках, почему наивное решение настолько быстрее, чем стандартная библиотека.
Потому что голландец, видимо, середнячок, судя по другим комментариям о ненужном union
и незнании о том, что std::from_chars()
немного универсальней с четвёртым параметром int base = 10
, и там без произведения с base
не обойтись. Ну и всякие ошибки errc::invalid_argument
, errc::result_out_of_range
.
Насчёт base - если они не догадались оптимизировать самый частый контекст использования...
А кто подсчитывал, что основание 10 для байта самое частое, а не 16? И from_chars
не ограничена только диапазоном 0-255, теперь им для каждого целочисленного знакового и беззнакового типа писать оптимизации?
Разумеется, а зачем еще нужна стандартная библиотека? Чтобы самому все писать что ли?
я бы мог поспорить, но не вижу смысла, потому что у функции автора и std::from_chars разные требования, хоть с помощью них и можно решить одну задачу.
Помню пост, где wc на haskell был быстрее gnu имплементации на Си. Ох и споров там было.
Просто это нечестный, в какой-то степени, бенчмарк.
интересно, будет ли какой-то эффект, если убрать проверки на длину и all_digits
Какой смысл от них? Мне кажется это тоже самое, что добавлять проверку в strlen.
Задача взята из проекта simdzone под руководством Йероена Коеккоека (NLnet Labs).
Я тут слегка посмотрел и выяснил:
Что упоминающийся Йероен Коеккоек - это Jeroen Koekkoek (https://imap.nlnetlabs.nl/people/, https://github.com/k0ekk0ek), т.е. явно голландец;
И следовательно тут надо применять правила нидерландско-русской транскрипции;
И он получается скорее Йерун Куккук, там oe передаёт у.
P.S. Прошу меня извинить, просто странный набора знаков в глаза бросился ?
Ох, как будто Хабр 2000- ых. Спасибо :)
Не понятно, почему эта статья в хабах С и С++.
считывание четырёх байтов всегда безопасно, если мы не пересекаем границу страницы, а это проверить очень легко.
С этого места человек перестаёт писать код на Си/С++. Потому как стандарты Си и С++ ничего не знают ни про границы страниц, ни про что-то еще. Выход за границу массива определяется как UB.
Де факто, человек с этого момента начинает писать на ассемблере, под конкретную аппаратную архитектуру, конкретный аллокатор в конкретной ОС. Это допустимо, если он пишет код под одну конкретную железку, но в остальных случаях - такому коду не место в продакшене.
Я тут ради прикола написал самую дубовую реализацию этой функции через switch-case, вообще не думая.
static int parse_uint8_switch_case(const char *str, size_t len, uint8_t *num) {
uint8_t hi, mid, lo;
#define as_u8(x) ((uint8_t)((x) - '0'))
switch(len) {
case 1:
*num = as_u8(str[0]);
return *num < 10;
case 2:
hi = as_u8(str[0]);
lo = as_u8(str[1]);
*num = (uint8_t)(hi * 10 + lo);
return (hi < 10) && (lo < 10);
case 3:
hi = as_u8(str[0]);
mid = as_u8(str[1]);
lo = as_u8(str[2]);
*num = (uint8_t)(hi * 100 + mid * 10 + lo);
return
((hi == 2) && (mid < 6) && (lo < 6))
|| ((hi < 2) && (mid < 10) && (lo < 10));
default:
return 0;
}
#undef as_u8
}
А потом запустил бенчмарки. Результат довольно-таки занимательный:
Как всегда, кто-то решил что он умнее разработчиков компилятора, и умнее разработчиков предсказателя переходов)
P.S.
Что делает код Боба и почему он такой быстрый - я понятия не имею :) Есть подозрение, что выстрелил UB и код делает ровным счётом нихрена, я не вникал.
Да уж, помню как когда я начал изучать микроконтроллеры и С, меня впечатлило преобразование char в int простым вычитанием '0' )
Быстрый парсинг 8-битных целых чисел