我正在阅读glibc2.9
的源代码。阅读strcpy
函数的源代码,性能不如我预期的那么好。
以下是strcpy
中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;
}
因为我不知道使用偏移的原因,我通过将上面的代码与以下代码进行比较来进行了一些性能测试:
char* my_strcpy(char *dest, const char *src)
{
char *d = dest;
register char c;
do {
c = *src++;
*d++ = c;
} while ('\0' != c);
return dest;
}
因此,strcpy
的性能在我的测试中更糟。我删除了有关绑定指针的代码。
为什么glibc
版本使用偏移量?
以下是关于测试的介绍。
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;
}
使用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
使用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
我知道命令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;
}
但my_strcpy2
的效果仍然比my_strcpy1
差。然后我检查了汇编的代码,但也没有得到答案。
我还扩大了字符串的大小,my_strcpy1
的效果仍优于my_strcpy2
答案 0 :(得分:8)
它使用偏移方法,因为这样可以消除循环中的一个增量 - glibc代码只需增加s
,而代码必须增加s
和d
。< / p>
请注意,您正在查看的代码是独立于体系结构的回退实现 - glibc具有覆盖许多体系结构的汇编实现(例如。the x86-64 strcpy()
)。
答案 1 :(得分:1)
根据我所看到的情况,我对你的代码更快感到惊讶。
看看循环,你的循环和glibc循环几乎完全相同。但是glibc在之前和之后都有额外的代码......
通常,简单的偏移不会降低性能,因为x86允许相当复杂的间接寻址方案。所以这里的两个循环可能会以相同的速度运行。
编辑:这是我的更新,其中包含您添加的信息。
您的字符串大小只有5个字符。尽管从长远来看偏移方法“可能”稍微快一点,但是在开始循环之前需要几次操作来计算偏移量这一事实会减慢短字符串的速度。也许如果你尝试更大的琴弦,那么差距就会缩小,并可能完全消失。
答案 2 :(得分:1)
这是我自己对strcpy
的优化。我认为它具有2x-3x的加速比天真的实现,但它需要进行基准测试。
https://codereview.stackexchange.com/questions/30337/x86-strcpy-can-this-be-shortened/30348#30348