为啥 strcmp 比我的函数快这么多?

Posted

技术标签:

【中文标题】为啥 strcmp 比我的函数快这么多?【英文标题】:Why is strcmp so much faster than my function?为什么 strcmp 比我的函数快这么多? 【发布时间】:2014-01-11 03:56:11 【问题描述】:

我写了一个函数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);

编辑: 在测试我编写的一些代码和优化时,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

【问题讨论】:

你为什么不做while(*str1++ == *str2++); 因为那段代码是这个函数最低效的实现。 1Str::Compare的编辑版本不好-TempChar_3在初始化之前使用,2VS2010为原始功能生成的代码几乎和strcmp一样快。 3 很难通过简单的实现来击败大规模优化的功能(甚至是内在的!)。 解释为什么?该代码类似于 strcpy afaik 的标准 imp while(*str_1 &amp;&amp; *str_1++ == *str_2++); 其实是正确的形式。这更慢,原因很简单,处理器必须每次都解析指针的地址,并且性能损失。 【参考方案1】:

我相信大多数标准库都是用汇编语言编写的。这可能是您看到标准库比您的更快的原因。

【讨论】:

这可能是真的,但它不会解释 750 倍的速度差异。这比解释语言和编译语言之间的区别更大。通常,当我看到如此大的差异时,我会怀疑缓存未命中甚至分支预测失败之类的事情。【参考方案2】:

在比较性能时,我发现最好将测试函数和测试驱动程序放在单独的编译单元中。将您的测试函数放在单独的编译单元中,并将它们编译到您想要的任何优化级别,但编译未优化的测试驱动程序。否则你会遇到你在这里看到的那种问题。

问题在于strcmp 比较两个const C 风格的字符串。如果您在 strcmp(string_a, string_b) 上循环 500,000,000 次,优化编译器将足够聪明地减少该循环以优化该循环,然后可能足够聪明以优化对 strcmp 的剩余调用。

您的比较函数需要两个非常量字符串。就编译器而言,您的函数很可能有副作用。编译器不知道,因此它无法将循环优化到零。它必须生成代码来执行 500,000,000 次比较。

【讨论】:

const 指针参数的限定通常不会影响编译器推断副作用的能力。只要原始数组对象是非常量的,就可以抛弃常量。此外,该函数可能会调用其他导致字符串修改或别名的东西。【参考方案3】:

我对此感到好奇并构建了一个测试程序:

#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 4.7.3 将其编译为带有 gcc -S -Os test.c 的汇编器,得到以下汇编器:

    .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 优化为常量。

【讨论】:

Gcc 也可以用常量替换对 strlen 的调用 Here's G++ 4.8.1 reducing everything to a constant at Coliru. strcmp 即使在我没有使用常量的我的程序中也被替换为常量表达式。我会努力更好地理解为什么会这样。

以上是关于为啥 strcmp 比我的函数快这么多?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 foreach 比我的 for 循环快? [复制]

为啥 mySQL 查询,左连接“明显”比我的内连接快

为啥 utorrents Magnet to Torrent 文件的获取比我的 python 脚本快?

为啥 cffi 比 numpy 快这么多?

grep 怎么跑得这么快?

为啥我的 Office 365 OAuth2 刷新令牌总是这么快过期?