我正在审核一些代码,我看到有人做了
if (0 == strcmp(foo,""))
我很好奇,因为我认为做一个
会更快if (foo[0] == '\0')
这是正确的还是strcmp足够优化以使它们相同。
(我意识到即使存在一些差异,它也会很小,但是我想通过使用我的方法保存至少一些指令。)
答案 0 :(得分:10)
你是对的:因为调用strcmp()
会增加堆栈管理并将内存跳转到实际的strcmp指令,所以只需检查字符串的第一个字节就可以获得一些指令。
为了您的好奇心,您可以在此处查看strcmp()代码:http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD
(我认为代码会被#ifdef
和__GNUSOMETHING
填充,但它实际上相当简单!)
答案 1 :(得分:9)
strcmp()是一个函数调用,因此具有函数调用开销。 foo [0]是对数组的直接访问,所以它显然更快。
答案 2 :(得分:5)
在这种情况下,我认为没有使用strcmp的优势。编译器我足够聪明,可以优化它,但它不会比直接检查'\ 0'字节快。这个实现者可能选择了这个结构,因为他认为它更具可读性,但我认为这是一个在这种情况下的品味问题。虽然我会把支票写得有点不同,因为这是最常用来检查空字符串的习语:
if( !*str )
和
if( *str )
检查非空字符串。
答案 3 :(得分:4)
+1到Gui13用于提供gcc stdlib strcmp源代码的链接(http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h= bd53c05c6e21130b091bd75c3fc93872dd71fe4b; HB = HEAD)
!你是正确的,strcmp永远不会比直接比较快[1],但问题是,编译器会优化吗?我很害怕尝试测量它,但我很惊讶它是多么容易。我的示例代码是(省略标题):
bool isEmpty(char * str) {
return 0==std::strcmp(str,"");
}
bool isEmpty2(char * str) {
return str[0]==0;
}
我尝试编译,首先使用gcc -S -o- emptystrcmptest.cc
,然后使用gcc -S -O2 -o- emptystrcmptest.cc
。令我惊喜的是,虽然我不能很好地阅读装配,但非优化版本显然有所不同,优化版本清楚地表明这两个功能产生了相同的装配。
所以,我想说的是,总的来说,没有必要担心这种优化水平。
如果您使用嵌入式系统的编译器并且知道它不处理这种简单的优化(或者根本没有标准库),请使用手工编写的特殊情况版本。
如果您正常编码,请使用更易读的版本(imho可能是strcmp或strlen或[0] == 0,具体取决于上下文)。
如果您正在编写高效代码,您希望每秒调用数千或数百万次,(a)测试实际上更有效率;(b)如果可读版本实际上太慢,请尝试写一些将编译为更好的装配。
使用gcc -S -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
.section .rdata,"dr"
LC0:
.ascii "\0"
.text
.align 2
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $LC0, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _strcmp
movl %eax, -4(%ebp)
cmpl $0, -4(%ebp)
sete %al
movzbl %al, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
.align 2
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
popl %ebp
ret
emptystrcmptest.cc:10:2: warning: no newline at end of file
.def _strcmp; .scl 2; .type 32; .endef
使用gcc -S -O2 -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
emptystrcmptest.cc:10:2: warning: no newline at end of file
.text
.align 2
.p2align 4,,15
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
.align 2
.p2align 4,,15
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
[1]虽然要小心 - 在比零直接测试更复杂的情况下,库和编译器代码通常会比手工编写的代码更好。
答案 4 :(得分:1)
一个好的优化编译器可能会优化掉函数调用,然后从内联函数中消除循环。你的方法可能没有办法变慢,尽管有可能速度相同。
答案 5 :(得分:0)
执行时对数组的访问顺序为1,因此,它比函数更快。
答案 6 :(得分:0)
这是微优化,但我想如果你在索引foo之前添加了一个空检查(除非你知道它永远不会为null),它在技术上会节省函数调用的开销。
答案 7 :(得分:0)
它显然会更快,如果您打算继续使用它,可能值得将您自己的代码放在内联函数中,甚至可能放在宏中:
int isEmpty(const char *string)
{
return ! *string;
}
int isNotEmpty(const char *string)
{
return *string;
}
int isNullOrEmpty(const char *string)
{
return string == NULL || ! *string;
}
int isNotNullOrEmpty(const char *string)
{
return string != NULL && *string;
}
让编译器为您优化。无论如何,strcmp
最终需要检查'\0'
,因此您始终至少等于它。 (老实说,我可能会让编译器优化上面的内部共享,例如,isEmpty
可能只是翻转isNotEmpty
)