Почему стандартные строковые функции быстрее, чем мои пользовательские строковые функции?

Я решил найти скорости 2 функций:

  • strcmp-стандартная функция сравнения, определенная в строке.h
  • xstrcmp-функция, которая имеет те же параметры и делает то же самое, только что я ее создал.

вот моя функция xstrcmp:

int xstrlen(char *str)
{
    int i;
    for(i=0;;i++)
    {
        if(str[i]=='')
            break;
    }
    return i;
}

int xstrcmp(char *str1, char *str2)
{
    int i, k;
    if(xstrlen(str1)!=xstrlen(str2))
        return -1;
    k=xstrlen(str1)-1;
    for(i=0;i<=k;i++)
    {
        if(str1[i]!=str2[i])
            return -1;
    }
    return 0;
}

Я не хотел зависеть от strlen, так как я хочу, чтобы все определялось пользователем.

Итак, я нашел результаты. strcmp сделал 364 сравнения в миллисекунду и мой xstrcmp сделал только 20 сравнений в миллисекунду (по крайней мере, на моем компьютере!)

кто может сказать, почему это так ? Что делает функция xstrcmp, чтобы сделать себя так быстро ?

6 ответов


if(xstrlen(str1)!=xstrlen(str2))    //computing length of str1
    return -1;                      
k=xstrlen(str1)-1;                  //computing length of str1 AGAIN!

вы вычисляете длину str1 два раза. Это одна из причин, почему ваша функция проигрывает.

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


strcmp и другие подпрограммы библиотеки написаны в сборке или специализированном коде C опытными инженерами и используют различные методы.

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

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

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

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

многие из этих проблем требуют подробных инструкций по сборке, и необходимый контроль над точными инструкциями, используемыми, недоступен в C. точные методы, используемые, варьируются от модели процессора к модели процессора и сильно варьируются от архитектуры к архитектуре.


более быстрая реализация strlen:

//Return difference in addresses - 1 as we don't count null terminator in strlen.
int xstrlen(char *str)
{
    char* ptr = str;
    while (*str++);
    return str - ptr - 1;
}

//Pretty nifty strcmp from here:
//http://vijayinterviewquestions.blogspot.com/2007/07/implement-strcmpstr1-str2-function.html
int mystrcmp(const char *s1, const char *s2)
{
    while (*s1==*s2)
    {
        if(*s1=='')
            return(0);
        ++s1;
        ++s2;
    }
    return(*s1-*s2);
}

Я сделаю другой позже, если у меня есть время. Вы также должны отметить, что большинство из них выполняются на языке ассемблера или с использованием других оптимизированных средств, которые будут быстрее, чем лучшая реализация stright C, которую вы можете написать.


помимо проблем в вашем коде (которые уже были указаны), -- по крайней мере, в gcc-c-libs,str и mem-функции быстрее с перевесом в большинстве случаев, потому что их шаблоны доступа к памяти очень оптимизирован.

там были обсуждение темы так уже.


попробуйте это:

int xstrlen(const char* s){
  const char* s0 = s;
  while(*s) s++;
  return(s - s0);
}

int xstrcmp(const char* a, const char* b){
  while(*a && *a==*b){a++; b++;}
  int del = *a - *b;
  if (del < 0) return -1;
  else if (del > 0) return 1;
  else return 0;
}

Это, вероятно, может быть ускорено с некоторым развертыванием цикла.


1. Алгоритм

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

// Adapted from http://vijayinterviewquestions.blogspot.co.uk
int xstrcmp(const char *s1, const char *s2)
{
  for (;*s1==*s2;++s1,++s2)
  {
    if(*s1=='') return(0);
  }
  return(*s1-*s2);
}

Это не все, но должен быть простым и работать в большинстве случаев.

2. Компилятор оптимизация

Это глупый вопрос, но убедитесь, что вы включили все выключатели оптимизации при компиляции.

3. Более сложные оптимизации

люди, пишущие библиотеки, часто используют более продвинутые методы, такие как загрузка 4-байтового или 8-байтового int сразу и сравнение его, и только сравнение отдельных байтов, если все совпадает. Вам нужно быть экспертом, чтобы знать, что подходит для этого случая, но вы можете найти людей, обсуждающих большинство эффективная реализация при переполнении стека (ссылка?)

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

4. Линкер "обман" со стандартной библиотекой

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

5. Хорошо, хорошо, я понимаю, но когда я должен реализовать свой собственный strcmp?

С моей головы, единственные причины сделать это являются:

  • вы хотите научиться. Это хорошая причина.
  • вы пишете для платформы, которая не имеет достаточно хорошей стандартной библиотеки. Это очень маловероятно.
  • сравнение строк было измерено как значительное узкое место в вашем коде, и вы знаете что-то конкретное о своих строках, что означает, что вы можете сравнить их более эффективно, чем наивный алгоритм. (Напр. все строки выделяются 8-байтовым выравниванием, или все строки имеют N-байтовый префикс. Это очень, очень маловероятно.

6. Но...

хорошо, почему вы хотите избежать полагаться на strlen? Вы беспокоитесь о размере кода? О переносимости кода или исполняемых файлов?

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