我决定找到2个功能的速度:
这是我的xstrcmp函数:
int xstrlen(char *str)
{
int i;
for(i=0;;i++)
{
if(str[i]=='\0')
break;
}
return i;
}
int xstrcmp(char *str1, char *str2)
{
int i, k;
if(xstrlen(str1)!=xstrlen(str2))
return -1;
k=xstrlen(str1)-1;
for(i=0;i<=k;i++)
{
if(str1[i]!=str2[i])
return -1;
}
return 0;
}
我不想依赖strlen,因为我想要用户定义的所有东西。
所以,我找到了结果。 strcmp每毫秒做了364次比较,而我的xstrcmp每毫秒只进行了20次比较(至少在我的电脑上!)
任何人都可以告诉为什么会这样吗? xstrcmp函数如何使自己如此之快?
答案 0 :(得分:34)
if(xstrlen(str1)!=xstrlen(str2)) //computing length of str1
return -1;
k=xstrlen(str1)-1; //computing length of str1 AGAIN!
您正在计算str1
TWICE的长度。这就是你的功能失去游戏的一个原因。
此外,与(大多数)标准库中定义的实现相比,xstrcmp
的实现非常幼稚。例如,你的xstrcmp
一次比较一个字节,实际上它可以一次比较多个字节,也可以利用正确的对齐,或者可以做很少的预处理以便在实际之前对齐内存块比较。
答案 1 :(得分:27)
strcmp和其他库例程由经验丰富的工程师用汇编或专用C代码编写,并使用各种技术。
例如,程序集实现可能会一次将四个字节加载到寄存器中,并将该寄存器(作为32位整数)与另一个字符串中的四个字节进行比较。在某些机器上,程序集实现可能会加载8个字节甚至更多。如果比较显示字节相等,则实现继续到接下来的四个字节。如果比较显示字节不相等,则实现停止。
即使进行了这种简单的优化,也有许多问题需要解决。如果字符串地址不是四个字节的倍数,则处理器可能没有加载四个字节的指令(许多处理器需要四个字节的加载才能使用与四个字节的倍数对齐的地址)。根据处理器的不同,实现可能必须使用较慢的未对齐加载或为每个对齐情况编写特殊代码,这些对齐情况会对齐加载并移位寄存器中的字节以对齐要比较的字节。
当实现一次加载四个字节时,它必须确保它不会加载超出终止空字符的字节,如果这些字节可能导致段错误(错误,因为你试图加载一个不可读的地址)。
如果四个字节确实包含终止空字符,则实现必须检测它并且不继续比较其他字节,即使当前四个字符在两个字符串中相等。
其中许多问题需要详细的汇编指令,并且C中没有对所使用的确切指令的所需控制。所使用的确切技术因处理器型号而异,并且从架构到架构各不相同。
答案 2 :(得分:5)
更快地实施strlen:
//Return difference in addresses - 1 as we don't count null terminator in strlen.
int xstrlen(char *str)
{
char* ptr = str;
while (*str++);
return str - ptr - 1;
}
//Pretty nifty strcmp from here:
//http://vijayinterviewquestions.blogspot.com/2007/07/implement-strcmpstr1-str2-function.html
int mystrcmp(const char *s1, const char *s2)
{
while (*s1==*s2)
{
if(*s1=='\0')
return(0);
++s1;
++s2;
}
return(*s1-*s2);
}
如果我有空的话,我会稍后再做另一个。您还应该注意到,大多数这些都是使用汇编语言或使用其他优化方法完成的,这些方法比您可以编写的最佳直接C实现更快。
答案 3 :(得分:4)
除了代码中的问题(已经指出), - 至少在gcc-C-lib中,str
- 和mem
- 函数是在大多数情况下,利润率更快,因为他们的内存访问模式得到了高度优化。
SO已经有了discussions on the topic。
答案 4 :(得分:2)
试试这个:
int xstrlen(const char* s){
const char* s0 = s;
while(*s) s++;
return(s - s0);
}
int xstrcmp(const char* a, const char* b){
while(*a && *a==*b){a++; b++;}
int del = *a - *b;
if (del < 0) return -1;
else if (del > 0) return 1;
else return 0;
}
这可能会通过一些循环展开来加速。
答案 5 :(得分:1)
<强> 1。算法强>
你的strcmp实现可以有更好的算法。根本不需要调用strlen,每次调用strlen都会再次遍历字符串的整个长度。你可以在网上找到简单但有效的实现,可能的起点就像:
// Adapted from http://vijayinterviewquestions.blogspot.co.uk
int xstrcmp(const char *s1, const char *s2)
{
for (;*s1==*s2;++s1,++s2)
{
if(*s1=='\0') return(0);
}
return(*s1-*s2);
}
这并不能解决所有问题,但在大多数情况下应该很简单并且有效。
<强> 2。编译器优化
这是一个愚蠢的问题,但请确保在编译时打开所有优化开关。
第3。更复杂的优化
编写库的人通常会使用更高级的技术,例如一次加载一个4字节或8字节的int,并进行比较,只有在整个匹配时才比较单个字节。您需要成为专家才能知道这种情况的适用性,但是您可以找到人们在堆栈溢出时讨论大多数高效实现(链接?)
如果编码器可以知道编译器可以找到比编译器更高效的实现,那么某些平台的某些标准库函数可以手工编写。现在这种情况越来越少见,但在某些嵌入式系统上可能很常见。
<强> 4。链接器“欺骗”标准库
使用一些标准库函数,链接器可能能够让您的程序调用它们,而不是调用代码中的函数,因为它旨在了解有关函数特定内部的更多信息(链接?)我不知道我知道在这种情况下是否适用,它可能不适用,但这是你必须考虑的事情。
<强> 5。好吧,好吧,我明白了,但什么时候我应该实现自己的strcmp?
脱离我的头脑,这样做的唯一原因是:
<强> 6。但是......
好的,你为什么要避免依赖strlen?你担心代码大小吗?关于代码或可执行文件的可移植性?
如果有充分的理由,请打开另一个问题,可能会有更具体的答案。所以我很抱歉,如果我遗漏了一些明显的东西,但依靠标准库通常要好得多,除非你想要改进一些特定的东西。