Ответ
Реализация strlen в glibc сильно оптимизирована. Основная идея — проверять не по одному байту, а считывая и анализируя сразу машинное слово (4 байта на 32-битных, 8 байт на 64-битных системах), чтобы минимизировать количество итераций цикла.
Упрощенный алгоритм (на основе кода glibc):
- Выравнивание указателя: Сначала обрабатываются байты до первого адреса, выровненного на границу слова.
- Проверка словами: Затем в цикле считываются слова. Чтобы быстро проверить, есть ли в слове нулевой байт, используется битовая магия без ветвлений.
- Определение точной позиции: Когда слово с нулевым байтом найдено, определяется точная позиция этого байта.
Ключевая оптимизация — макрос для 32-битных систем:
#define DETECT_NULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080)
Он работает потому, что:
0x01010101— маска, где в каждом байте установлен младший бит.- Если в байте
Xноль, то(X - 0x01)даст перенос в старший бит этого байта, который затем отлавливается маской0x80. - Операция
& ~(X)исключает случаи, где в старшем бите байта уже была единица (т.е. байт >= 128).
Пример схематичного кода:
size_t strlen_optimized(const char *str) {
const char *char_ptr = str;
const unsigned long *longword_ptr;
// Обработка невыровненных начальных байтов
for (; ((uintptr_t)char_ptr & (sizeof(long) - 1)) != 0; ++char_ptr) {
if (*char_ptr == ' ') return char_ptr - str;
}
// Проверка словами
longword_ptr = (const unsigned long*)char_ptr;
while (true) {
unsigned long longword = *longword_ptr++;
if (DETECT_NULL(longword) != 0) {
// Найдено слово с нулем, определяем какой именно байт
const char *cp = (const char*)(longword_ptr - 1);
if (cp[0] == 0) return cp - str;
if (cp[1] == 0) return cp - str + 1;
// ... проверка cp[2], cp[3] ...
}
}
}
Эта оптимизация дает выигрыш в несколько раз по сравнению с наивной реализацией на длинных строках.
Ответ 18+ 🔞
Слушай, а ты в курсе, как в glibc strlen сделана? Это же не просто цикл, там, блядь, целая наука, ядрёна вошь! Они не тупо по байту ползают, а сразу целыми словами машины хватают — четыре байта на старых системах, восемь на новых. Идея в том, чтобы итераций дохуя меньше было.
Если по-простому, то алгоритм такой:
- Сначала подравниваем указатель. Пока адрес не кратен размеру слова, чекаем байты по одному. Это типа разминка, ебушки-воробушки.
- А потом — основная фишка. В цикле читаем уже целыми словами. И чтобы проверить, не затесался ли в этом слове нулевой байт, используют такую битовую магию, что волосы дыбом. Всё без единого
if, на чистых битовых операциях. - Ну и когда нашли слово с нулём, остаётся только выяснить, какой именно байт в нём первый нулевой.
Вот этот самый магический макрос для 32-бит:
#define DETECT_NULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080)
Выглядит как пиздопроебибна, но работает железно. Суть в чём: отнимаем от каждого байта единичку. Если байт был ноль, происходит заём из старшего бита, и мы это ловим. А & ~(X) нужен, чтобы не сработало на байтах, где старший бит уже и так единица.
Примерно вот так это всё выглядит в коде:
size_t strlen_optimized(const char *str) {
const char *char_ptr = str;
const unsigned long *longword_ptr;
// Пока не выровнялись — тычемся по байту
for (; ((uintptr_t)char_ptr & (sizeof(long) - 1)) != 0; ++char_ptr) {
if (*char_ptr == ' ') return char_ptr - str;
}
// А теперь — главная фишка, читаем словами
longword_ptr = (const unsigned long*)char_ptr;
while (true) {
unsigned long longword = *longword_ptr++;
if (DETECT_NULL(longword) != 0) {
// О, нашли слово с нулём! Теперь ищем, где именно.
const char *cp = (const char*)(longword_ptr - 1);
if (cp[0] == 0) return cp - str;
if (cp[1] == 0) return cp - str + 1;
// ... и так проверяем cp[2], cp[3] ...
}
}
}
Вот такая, блядь, хитрая жопа. Зато на длинных строках скорость просто овердохуища, в разы быстрее наивного варианта. Умные ребята, чёрт побери.