Подтвердить что ты не робот

Почему strcmp намного быстрее, чем моя функция?

Я написал функцию Str::Compare, которая в основном перезаписывается strcmp по-другому. При сравнении двух функций в цикле, повторяющемся 500'000'000 раз, strcmp выполняется слишком быстро, примерно x750 раз быстрее.

Этот код был скомпилирован в C-библиотеке с активным параметром -Os:

int Str::Compare(char* String_1, char* String_2)
{
    char TempChar_1, TempChar_2;

   do
   {
        TempChar_1 = *String_1++;
        TempChar_2 = *String_2++;
   } while(TempChar_1 && TempChar_1 == TempChar_2);

   return TempChar_1 - TempChar_2;
}

Время выполнения этой функции 3.058s, а strcmp только 0.004s.

Почему это происходит?

Также я реализовал цикл тестирования:

int main()
{
     char Xx[] = {"huehuehuehuehuehuehuehuehuehuehuehuehuehue"},
          Yy[] = {"huehuehuehuehuehuehuehuehuehuehuehuehuehue"};
     for(int i = 0; i < 500000000; ++i)
         Str::Compare(Xx, Yy);
}

Edit: При тестировании кода, который я написал, и оптимизации, которая значительно улучшила скорость Str::Compare. Если раньше strcmp было x750 раз быстрее, теперь только x250. Это новый код:

int Str::Compare(char* String_1, char* String_2)
{
     char TempChar_1, TempChar_2, TempChar_3;

     while(TempChar_1 && !TempChar_3)
     {
          TempChar_1 = *String_1++;
          TempChar_2 = *String_2++;
          TempChar_3 = TempChar_1 ^ TempChar_2;
     }

     return TempChar_1 - TempChar_2;
}

Новое время выполнения 0.994s.

4b9b3361

Ответ 1

Мне было интересно и создать тестовую программу:

#include <string.h>

compare(char* String_1, char* String_2)
{
    char TempChar_1,
         TempChar_2;

   do
   {
        TempChar_1 = *String_1++;
        TempChar_2 = *String_2++;
   } while(TempChar_1 && TempChar_1 == TempChar_2);

   return TempChar_1 - TempChar_2;
}


int main(){
    int i=strcmp("foo","bar");
    int j=compare("foo","bar");

    return i;
}

Я скомпилировал его на ассемблере с помощью gcc -S -Os test.c, используя gcc 4.7.3, в результате чего появился следующий ассемблер:

    .file   "test.c"
    .text
    .globl  compare
    .type   compare, @function
compare:
.LFB24:
    .cfi_startproc
    xorl    %edx, %edx
.L2:
    movsbl  (%rdi,%rdx), %eax
    movsbl  (%rsi,%rdx), %ecx
    incq    %rdx
    cmpb    %cl, %al
    jne .L4
    testb   %al, %al
    jne .L2
.L4:
    subl    %ecx, %eax
    ret
    .cfi_endproc
.LFE24:
    .size   compare, .-compare
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "bar"
.LC1:
    .string "foo"
    .section    .text.startup,"ax",@progbits
    .globl  main
    .type   main, @function
main:
.LFB25:
    .cfi_startproc
    movl    $.LC0, %esi
    movl    $.LC1, %edi
    call    compare
    movl    $1, %eax
    ret
    .cfi_endproc
.LFE25:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
    .section    .note.GNU-stack,"",@progbits

Я не так хорош в ассемблере x86, но, насколько я вижу, вызов strcmp удаляется и просто заменяется постоянным выражением (movl $1, %eax). Поэтому, если вы используете константное выражение для своих тестов, gcc, вероятно, оптимизирует strcmp для константы.

Ответ 2

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

Проблема заключается в том, что strcmp сравнивает две строки const C-style. Если вы зацикливаете 500 000 000 раз на strcmp(string_a, string_b), оптимизирующий компилятор будет достаточно умным, чтобы уменьшить этот цикл, чтобы оптимизировать этот цикл, а затем, возможно, достаточно умный, чтобы оптимизировать оставшийся вызов до strcmp.

Ваша функция сравнения принимает две неконстантные строки. Что касается компилятора, ваша функция может иметь побочные эффекты. Компилятор не знает, поэтому он не может оптимизировать цикл до нуля. Он должен генерировать код для сравнения 500 000 000 раз.

Ответ 3

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