简说GLIBC strncpy实现
比较以下两组代码,你认为哪组运行的更快些呢?
Example1:
int n = 100;
int n4 = n >> 2;
int i = 0;
int a[100];
for (i = 0; i < n4 ;i += 4) {
a[i] = i;
a[i+1] = i+1;
a[i+2] = i+2;
a[i+3] = i+3;
}
Example2:
for (i = 0;i < 100;i++) {
a[i] = i;
}
其实这个问题在"代码大全2nd"中也有讨论,从"代码大全"中的统计结果来看,一般来说Example1更占有优势。我在solaris上做了测试,在未开优化的情况下:两者运行时间分别为2ms和6ms;在打开-O2优化后,两者均为1ms。这种通过减少循环次数的方法在GLIBC中也有体现,比如说strncpy的实现:
下面是strncpy的GLIBC源码:
char *
x_strncpy (s1, s2, n)
char *s1;
const char *s2;
size_t n;
{
reg_char c;
char *s = s1;
–s1;
if (n >= 4)
{
size_t n4 = n >> 2; /* n4 = n / 4, n4表示下面的循环执行的次数*/
for (;;)
{
c = *s2++;
*++s1 = c;
if (c == '')
break;
c = *s2++;
*++s1 = c;
if (c == '')
break;
c = *s2++;
*++s1 = c;
if (c == '')
break;
c = *s2++;
*++s1 = c;
if (c == '')
break;
if (–n4 == 0)
goto last_chars; /* 如果n = 10,s2 = "hello world",则两轮循环后,还有"尾巴"没有copy完,在last_chars处继续处理 */
}
n = n – (s1 – s) – 1; /* 还没有copy完n个字节,s2就到达末尾了,跳到zero_fill处继续为s1补零 */
if (n == 0)
return s;
goto zero_fill;
}
last_chars:
n &= 3; /* n = n & 3 结果 n <= 3,n即为上面循环过后"尾巴字符"的数量 */
if (n == 0)
return s;
do
{
c = *s2++;
*++s1 = c;
if (–n == 0)
return s;
} while (c != '');
zero_fill:
do
*++s1 = '';
while (–n > 0);
return s;
}
相比于strlen的实现,strncpy的实现更易理解。其字面上的逻辑就是每四个字节(n>>2)作为一组,每组逐个字节进行拷贝赋值,其内在目的则是减少循环次数,以获得性能的提升。要想知道为什么减少循环次数能提升性能的话,那就要深入到汇编层面去了,这里不再详述。另外还要一提的是GLIBC中的strncmp,strncat的实现也遵循着与上面同样的逻辑。
评论