Как в библиотеке GNU (glibc) реализована функция strlen?

Ответ

Реализация strlen в glibc сильно оптимизирована. Основная идея — проверять не по одному байту, а считывая и анализируя сразу машинное слово (4 байта на 32-битных, 8 байт на 64-битных системах), чтобы минимизировать количество итераций цикла.

Упрощенный алгоритм (на основе кода glibc):

  1. Выравнивание указателя: Сначала обрабатываются байты до первого адреса, выровненного на границу слова.
  2. Проверка словами: Затем в цикле считываются слова. Чтобы быстро проверить, есть ли в слове нулевой байт, используется битовая магия без ветвлений.
  3. Определение точной позиции: Когда слово с нулевым байтом найдено, определяется точная позиция этого байта.

Ключевая оптимизация — макрос для 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 сделана? Это же не просто цикл, там, блядь, целая наука, ядрёна вошь! Они не тупо по байту ползают, а сразу целыми словами машины хватают — четыре байта на старых системах, восемь на новых. Идея в том, чтобы итераций дохуя меньше было.

Если по-простому, то алгоритм такой:

  1. Сначала подравниваем указатель. Пока адрес не кратен размеру слова, чекаем байты по одному. Это типа разминка, ебушки-воробушки.
  2. А потом — основная фишка. В цикле читаем уже целыми словами. И чтобы проверить, не затесался ли в этом слове нулевой байт, используют такую битовую магию, что волосы дыбом. Всё без единого if, на чистых битовых операциях.
  3. Ну и когда нашли слово с нулём, остаётся только выяснить, какой именно байт в нём первый нулевой.

Вот этот самый магический макрос для 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] ...
        }
    }
}

Вот такая, блядь, хитрая жопа. Зато на длинных строках скорость просто овердохуища, в разы быстрее наивного варианта. Умные ребята, чёрт побери.