优化程序性能时,后知后觉地才发现strcpy()很耗时,特别是与memcpy()相比较。因此查看了一下两个函数的Linux实现源码。
strlen()和strcpy()的实现:
- static inline size_t __kernel_strlen(const char *s)
- {
- const char *sc;
- for (sc = s; *sc++; )
- ;
- return sc - s - 1;
- }
- static inline char *__kernel_strcpy(char *dest, const char *src)
- {
- char *xdest = dest;
- asm volatile ("\n"
- "1: move.b (%1)+,(%0)+\n"
- " jne 1b"
- : "+a" (dest), "+a" (src)
- : : "memory");
- return xdest;
- }
strlen()是C语言实现,逐字节取出字符串的内容,并判断是否为结束标志(0);strcpy()为了提高拷贝效率,使用了汇编语言来实现,基本作法也是逐字节拷贝,直到碰上字符串结束字符(0)。
而Linux内核源码中,可以找到多份不同的memcpy()实现。除了因为内嵌汇编因此必须针对不同处理器平台给出不同实现外,memcpy()还区分了32位系统和64位系统。以下以x86平台32位的一个实现为例:
- static __always_inline void *__memcpy(void *to, const void *from, size_t n)
- {
- int d0, d1, d2;
- asm volatile("rep ; movsl\n\t"
- "movl %4,%%ecx\n\t"
- "andl $3,%%ecx\n\t"
- "jz 1f\n\t"
- "rep ; movsb\n\t"
- "1:"
- : "=&c" (d0), "=&D" (d1), "=&S" (d2)
- : "0" (n / 4), "g" (n), "1" ((long)to), "2" ((long)from)
- : "memory");
- return to;
- }
为了提高效率,memcpy()也采用了内嵌汇编的作法。但memcpy()充分利用了机器字长为32位的特性(32位系统,一次内存读/写可操作4字节的数据, 对于64位系统,则一次可操作8字节数据)。先按4字节一组(movsl)拷贝,共复制n/4次;对剩下的零头再逐字节拷贝。如果支持,memcpy()还可以使用了MMX/SSE指令增加一次操作中的字节数,进一步提高效率。
比较strcpy()和memcpy()的实现,就知道了对于长符串,为什么strcpy()会消耗数倍于memcpy()的时间。
测算性能用到的几个方法:
1. 多次操作(比如重复100次)累加后再求平均用时
2. x86体系中的RDTSC指令也常被用来获取精确时间(CPU时钟滴哒数),但在多核系统中,由于多核之间的时钟计数存在不同步以及CPU降频运行的可能,因此,Win/Linux平台上,现在已不推荐使用此指令进行精确计时,而代之以clock_gettime()(linux系统)或QueryPerformanceCounter()和QueryPerformanceFrequency()(win系统)。
以前只是听说memcpy效率高,但从没有想到去看看它如何实现的,再次受教