Как написать лучшую функцию strlen?

Я читаю «Написать отличный код, том 2», и он показывает следующую реализацию strlen:

int myStrlen( char *s )
{
    char *start;
    start = s;
    while( *s != 0 )
    {
        ++s;
    }
    return s - start;
}

в книге написано, что такая реализация типична для неопытного программиста на Си. Я программировал на C в течение последних 11 лет, и я не вижу, как написать функцию лучше, чем эта, на C (я могу подумать о написании лучшей вещи на ассемблере). Как можно написать код лучше этого на C? Я просмотрел стандартную библиотечную реализацию функции strlen в glibc и большую ее часть не понял. Где я могу найти более подробную информацию о том, как писать высокооптимизированный код?


person Victor    schedule 05.07.2011    source источник
comment
Вы уверены, что это проблема оптимизации? Или просто стандартная проблема безопасности?   -  person Martin Kristiansen    schedule 05.07.2011
comment
@Victor Не верь всему, что читаешь. Эта функция работает достаточно быстро.   -  person cnicutar    schedule 05.07.2011
comment
Однажды я написал strlen() на ассемблере для системы i386, которая использовала коды операций CPU string (REP) и работала в 6 раз быстрее, чем оптимизированный код C.   -  person David R Tribble    schedule 05.07.2011
comment
@Loadmaster: не могли бы вы опубликовать этот код, пожалуйста?   -  person Victor    schedule 05.07.2011
comment
Это было в то время, когда компиляторы были не так хороши. Вы даже можете ускорить код с помощью register int i;.   -  person Bo Persson    schedule 05.07.2011
comment
Чтобы написать это так, почему бы не while (*s++ != 0) ; вместо этого?   -  person sidyll    schedule 05.07.2011
comment
Я бы возражал против приведения ptrdiff_t к int да, вы, вероятно, не передаете строки размером 2 ГБ в strlen(), но все же это неаккуратно. Также компилятор может создать лучший код из int i=0; while(s[i]) i++; return i;, потому что он может больше рассказать о том, что вы делаете с указателем (т.е. он может лучше анализировать этот цикл).   -  person Spudd86    schedule 06.07.2011


Ответы (7)


Из Оптимизация strlen(), сообщения в блоге Колма Маккартэя. :

К сожалению, в C мы обречены на реализацию O(n), в лучшем случае, но мы еще не закончили… мы можем сделать что-то с самим размером n.

Это хороший пример, в каком направлении вы можете работать, чтобы ускорить его. И еще цитата оттуда

Иногда очень-очень быстрое движение просто сводит вас с ума.

person Mojo Risin    schedule 05.07.2011

Виктор, взгляните на это:
http://en.wikipedia.org/wiki/Strlen#Implementation

P.S. Причина, по которой вы не понимаете версию glibc, вероятно, заключается в том, что она использует сдвиг битов для поиска \0.

person gkrogers    schedule 05.07.2011
comment
Я предполагаю, что с умеренным компилятором это создаст точно тот же байт-код, что и реализация OP... - person Constantinius; 05.07.2011
comment
@Martin: вы не можете проверить «слово» на ноль, это не сработает - person Karoly Horvath; 05.07.2011
comment
@Victor: Извините, я не хотел ставить под сомнение ваши способности в программировании. Если вы ищете объяснение реализации glibc, задайте вопрос, и я уверен, что более умные люди, чем я, смогут его объяснить. - person gkrogers; 05.07.2011
comment
Нет проблем, я не обиделся, я просто прокомментировал. - person Victor; 05.07.2011
comment
Упомянутые инструкции x86 в особом случае на самом деле медленнее, чем цикл на современных процессорах IIRC. - person Spudd86; 06.07.2011

Во-первых, это бесполезно для таких кодировок, как UTF-8... то есть вычисление количества символов в строке UTF-8 является более сложным, тогда как количество байтов, конечно, так же легко вычислить, как и в , скажем, строка ASCII.

В общем, вы можете оптимизировать на некоторых платформах, читая в регистры большего размера. Поскольку другие ссылки, опубликованные до сих пор, не имеют примера этого, вот немного псевдо-псевдокода для младшего порядка:

int size = 0;
int x;
int *caststring = (int *) yourstring;
while (int x = *caststring++) {
  if (!(x & 0xff)) /* first byte in this int-sized package is 0 */ return size;
  else if (!(x & 0xff00)) /* second byte etc. */ return size+1;
  /* rinse and repeat depending on target architecture, i.e. twice more for 32 bit */
  size += sizeof (int);
}
person Jan Krüger    schedule 05.07.2011
comment
Это, вероятно, не улучшит производительность, я чувствую, что это только ухудшит ее. - person Karoly Horvath; 05.07.2011
comment
@yi_H: Недостатки: одно дополнительное И на байт. Преимущества: На 75% меньше загружается из памяти, на 75% меньше скачков. Какая сторона победит в состязании, почти наверняка зависит от архитектуры. У меня нет конкретных сведений о том, как это будет работать на каких архитектурах, так что вы вполне можете быть правы. Но с таким же успехом вы можете ошибаться. ;) - person Jan Krüger; 05.07.2011
comment
на самом деле это на 75% меньше нагрузки на строку кэша, так как это последовательные байты. - person Karoly Horvath; 05.07.2011
comment
Ага. Совершенно верно. Возможно, мне следует пересмотреть свое отношение к бодрствованию. - person Jan Krüger; 05.07.2011

Как отмечали другие, более быстрый алгоритм считывает слова целиком, а не отдельные символы, и использует побитовые операции для найти завершающий нуль. Помните о выравнивании указателя по словам, если вы используете этот подход, поскольку некоторые архитектуры ЦП не позволяют вам читать слова с невыровненного адреса (и это отличный способ вызвать ошибку сегментации даже на архитектурах, которые не требуют выравнивания).

Итог:

Отличный код ставит читабельность выше скорости во всех случаях, кроме наиболее важных для производительности. Пишите свой код как можно четче и оптимизируйте только те части, которые оказались узкими местами.

person Tony the Pony    schedule 05.07.2011
comment
Я предполагаю, что аргумент, что отличный код - это удобный для чтения код, не работает в случае библиотеки C Std, которая нацелена на производительность. - person Victor Sorokin; 06.07.2011
comment
Поскольку библиотеки std очень широко и часто используются, критическое для производительности исключение уместно. Тем не менее, большинству из них не помешала бы лучшая документация... - person Tony the Pony; 06.07.2011

Чтение переменной, размер которой не совпадает с размером шины машинных данных, обходится дорого, потому что машина может считывать только переменные такого размера. Следовательно, всякий раз, когда запрашивается что-то другого размера (скажем, меньшего размера), машина должна выполнить работу, чтобы это выглядело как переменная запрошенного размера (например, сдвиг битов). Так что вам лучше прочитать данные в словах машинного размера, а затем использовать операцию И для проверки на 0. Кроме того, при сканировании строки убедитесь, что вы начинаете с выровненного начального адреса.

person Freek    schedule 05.07.2011

Отвечая на вопрос ОП о том, где найти предложения по написанию кода для повышения производительности, здесь ссылка на MIT OpenCourse по написанию оптимизированного кода C (ищите ссылку «Материалы» в левой части страницы).

person Victor Sorokin    schedule 06.07.2011

Следующее должно быть быстрее наивного алгоритма и работать для 32/64 бит.

union intptr {
    char* c;
    long* l;
#define LSIZE sizeof(long)
};

#define aligned_(x, a) \
    ((unsigned long) (x) % (a) == 0)

#define punpktt_(x, from, to) \
    ((to) (-1)/(from) (-1)*(from) (x))
#define punpkbl_(x) \
    punpktt_(x, unsigned char, unsigned long)

#define plessbl_(x, y) \
    (((x) - punpkbl_(y)) & ~(x) & punpkbl_(0x80))
#define pzerobl_(x) \
    plessbl_(x, 1)

static inline unsigned long maskffs_(unsigned long x)
{
    unsigned long acc = 0x00010203UL;
    if (LSIZE == 8)
       acc = ((acc << 16) << 16) | 0x04050607UL;
    return ((x & -x) >> 7) * acc >> (LSIZE*8-8);
}

size_t strlen(const char* base)
{
    union intptr p = { (char*) base };
    unsigned long mask;

    for ( ; !aligned_(p.c, LSIZE); p.c++ )
        if (*p.c == 0)
            return p.c - base;

    while ( !(mask = pzerobl_(*p.l)) )
        p.l++;
    return p.c - base + maskffs_(mask);
}
person mightymouse    schedule 26.10.2014