为什么glibc中strcpy的性能更差?

lin*_*xer 3 c performance glibc

我正在阅读源代码glibc2.9.阅读该strcpy函数的源代码,性能不如我预期的那么好.

以下是strcpyin 的源代码glibc2.9:

   char * strcpy (char *dest, const char* src)
    {
        reg_char c;
        char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
        const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
        size_t n;

        do {
            c = *s++;
            s[off] = c;
        }
        while (c != '\0');

        n = s - src;
        (void) CHECK_BOUNDS_HIGH (src + n);
        (void) CHECK_BOUNDS_HIGH (dest + n);

        return dest;
    }
Run Code Online (Sandbox Code Playgroud)

因为我不知道使用偏移量的原因,所以我通过将上面的代码与以下代码进行比较来进行一些性能测试:

char* my_strcpy(char *dest, const char *src)
{
    char *d = dest;
    register char c;

    do {
        c = *src++;
        *d++ = c;
    } while ('\0' != c);

    return dest;
}
Run Code Online (Sandbox Code Playgroud)

结果,strcpy在我的测试中表现更差.我删除了有关绑定指针的代码.

为什么glibc版本使用偏移?

以下是关于测试的介绍.

  1. 平台:x86(英特尔(R)奔腾(R)4),gcc版本4.4.2
  2. 编译标志:没有标志,因为我不想要任何优化; 命令是gcc test.c.

我使用的测试代码如下:

#include <stdio.h>
#include <stdlib.h>

char* my_strcpy1(char *dest, const char *src)
{
    char *d = dest;
    register char c;

    do {
        c = *src++;
        *d++ = c;
    } while ('\0' != c);

    return dest;
}

/* Copy SRC to DEST. */
char *
my_strcpy2 (dest, src)
     char *dest;
     const char *src;
{
  register char c;
  char * s = (char *)src;
  const int off = dest - s - 1;

  do
    {
      c = *s++;
      s[off] = c;
    }
  while (c != '\0');

  return dest;
}

int main()
{
    const char str1[] = "test1";
    const char str2[] = "test2";
    char buf[100];

    int i;
    for (i = 0; i < 10000000; ++i) {
        my_strcpy1(buf, str1);
        my_strcpy1(buf, str2);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用该my_strcpy1功能时,输出为:

[root@Lnx99 test]#time ./a.out

real    0m0.519s
user    0m0.517s
sys     0m0.001s
[root@Lnx99 test]#time ./a.out

real    0m0.520s
user    0m0.520s
sys     0m0.001s
[root@Lnx99 test]#time ./a.out

real    0m0.519s
user    0m0.516s
sys     0m0.002s
Run Code Online (Sandbox Code Playgroud)

使用时my_strcpy2,输出为:

[root@Lnx99 test]#time ./a.out

real    0m0.647s
user    0m0.647s
sys     0m0.000s
[root@Lnx99 test]#time ./a.out

real    0m0.642s
user    0m0.638s
sys     0m0.001s
[root@Lnx99 test]#time ./a.out

real    0m0.639s
user    0m0.638s
sys     0m0.002s
Run Code Online (Sandbox Code Playgroud)

我知道这个命令不太准确time.但我可以从用户时间得到答案.

更新:

To remove the cost used to calculate the offset, I removed some code and added a global variable.

#include <stdio.h>
#include <stdlib.h>

char* my_strcpy1(char *dest, const char *src)
{
    char *d = dest;
    register char c;

    do {
        c = *src++;
        *d++ = c;
    } while ('\0' != c);

    return dest;
}


int off;

/* Copy SRC to DEST. */
char *
my_strcpy2 (dest, src)
     char *dest;
     const char *src;
{
  register char c;
  char * s = (char *)src;

  do
    {
      c = *s++;
      s[off] = c;
    }
  while (c != '\0');

  return dest;
}

int main()
{
    const char str1[] = "test1test1test1test1test1test1test1test1";
    char buf[100];

    off = buf-str1-1;

    int i;
    for (i = 0; i < 10000000; ++i) {
        my_strcpy2(buf, str1);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

但表现my_strcpy2还是差于my_strcpy1.然后我检查了汇编的代码,但也未能得到答案.

我还扩大了弦的大小和性能my_strcpy1仍然比my_strcpy2

caf*_*caf 8

它使用偏移方法,因为这消除了循环中的一个增量 - glibc代码只需要递增s,而你的代码必须增加s和d.

请注意,您正在查看的代码是与体系结构无关的回退实现 - glibc具有覆盖许多体系结构的组件实现(例如x86-64strcpy()).

  • 更好的是,现代编译器将这类函数视为内置函数,因此几乎从不使用回退代码. (2认同)
  • @ user411318:独立于体系结构的代码*不一定是任何特定体系结构中最快的代码 - *尤其是在像Pentium 4这样的架构上奇怪的设计上.毕竟,这就是为什么有特定于体系结构的覆盖!这里的权衡 - 在可变指针偏移的循环中减少一个 - 将在不同的CPU上以不同的方式工作.您需要进行更广泛的测试(包括更长的字符串)才能完全评估设计. (2认同)