2013-12-15 62 views
0

我在看GNU C库函数glibc-2.18,这是我找到的代码strncmp.c有人可以解释为什么这个功能是这样吗?

看着它,我不明白它为什么这样写。这个循环是否展开? 为什么不使用5或10而不是4?为什么他们这样写而不是使用更直接的方法?

/* Compare no more than N characters of S1 and S2, 
    returning less than, equal to or greater than zero 
    if S1 is lexicographically less than, equal to or 
    greater than S2. */ 
int 
STRNCMP (const char *s1, const char *s2, size_t n) 
{ 
    unsigned char c1 = '\0'; 
    unsigned char c2 = '\0'; 

    if (n >= 4) 
    { 
     size_t n4 = n >> 2; 
     do 
    { 
     c1 = (unsigned char) *s1++; 
     c2 = (unsigned char) *s2++; 
     if (c1 == '\0' || c1 != c2) 
     return c1 - c2; 
     c1 = (unsigned char) *s1++; 
     c2 = (unsigned char) *s2++; 
     if (c1 == '\0' || c1 != c2) 
     return c1 - c2; 
     c1 = (unsigned char) *s1++; 
     c2 = (unsigned char) *s2++; 
     if (c1 == '\0' || c1 != c2) 
     return c1 - c2; 
     c1 = (unsigned char) *s1++; 
     c2 = (unsigned char) *s2++; 
     if (c1 == '\0' || c1 != c2) 
     return c1 - c2; 
    } while (--n4 > 0); 
     n &= 3; 
    } 

    while (n > 0) 
    { 
     c1 = (unsigned char) *s1++; 
     c2 = (unsigned char) *s2++; 
     if (c1 == '\0' || c1 != c2) 
    return c1 - c2; 
     n--; 
    } 

    return c1 - c2; 
} 

谁能解释的代码背后的逻辑是什么?

谢谢。

+4

@Mat对此代码看不到任何“Duff”。 –

+0

是的,这看起来像是用手完成的循环展开。这个想法是,它会更快,因为更少的测试和跳转。 –

回答

2

是的,它是一个部分展开的循环。

4:

1)一般大小:总是有分支更之间的权衡(少数),以及较大的代码大小(大的数字)。

2)具体大小(4,不是5):正如Joachim Isaksson指出>>之前,如果选择5而不是4,需要将循环更改为除法。他说,在某些(如嵌入式) CPU变得重要。 (通常我们只考虑了循环的成本是什么,而不是它的设置,但他可能是正确的,因为大多数字符串往往是小!)

请注意,如果你想展开循环以任意数量的(不管而不是做一个分工和维护n4 - - 2或无法开机),那么你可以,你可以计算的s1结束指针,并在年底检查s1与它:

char* s1end = s1 + n; 
do {...} while(s1 < s1end); 
+0

4而不是5的原因很可能是由4划分/ mod是微不足道的计算,而5划分不一定在所有机器上。 –

+0

@JoachimIsaksson在循环中没有除法 –

+0

不是,除了在循环外由4'size_t n4 = n >> 2;'和一个mod操作'n &= 3;'进行除法。作为比较,在没有专用分频指令的情况下,在CPU上进行5分频可能会完全否定所有速度增益。 –

1

这样有少分支(如果语句),它允许编译器针对管道进行优化。

1

是的,这是循环展开。

为什么4而不是5或10.可能是因为这旨在缓存友好并确保调用与内存页/行对齐。

问题可能是为什么不是8,16或32.也许代码的大小,也许更容易为一些较旧的处理器生成优化程序集时,或者实际代码将太大,以适应典型的指令缓存。

相关问题