我正在审查一些代码,看到有人进行了以下操作:
if (0 == strcmp(foo,""))
我很好奇,因为我认为这样做会更快:
if (foo[0] == '\0')
这样做是否正确,或者说strcmp已经被优化到让它们变得相同了。(我意识到即使存在一些差异,它们也很小,但我想通过使用我的方法至少可以节省几条指令。)
我正在审查一些代码,看到有人进行了以下操作:
if (0 == strcmp(foo,""))
我很好奇,因为我认为这样做会更快:
if (foo[0] == '\0')
这样做是否正确,或者说strcmp已经被优化到让它们变得相同了。(我意识到即使存在一些差异,它们也很小,但我想通过使用我的方法至少可以节省几条指令。)
你是对的:因为调用strcmp()
会增加堆栈管理和内存跳转到实际的strcmp指令,所以仅仅检查你字符串的第一个字节就可以获得几条指令。
如果你感兴趣,可以在这里查看strcmp()的代码:http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD
(我原以为代码会充满#ifdef
和晦涩的__GNUSOMETHING
,但实际上相当简单!)
strcmp() 是一个函数调用,因此具有函数调用开销。foo[0] 是对数组的直接访问,因此显然更快。
在这种情况下,我认为使用strcmp没有任何优势。编译器可能足够聪明以将其优化掉,但它不会比直接检查'\0'字节更快。实现者可能选择这个结构是因为他认为它更易读,但我认为在这种情况下这是一种口味问题。虽然我会写一个稍微不同的检查,因为这似乎是用于检查空字符串的最常用习语:
if( !*str )
if( *str )
检查非空字符串。
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
编译。令人惊喜的是,尽管我不太能读懂汇编语言,但未优化版本明显显示出差异,优化版本则清楚地显示出两个函数生成相同的汇编代码。因此,总体而言,不必担心这种级别的优化。
如果您在嵌入式系统中使用编译器并且知道它不能处理这种简单的优化(或者根本没有标准库),请使用手动编写的特殊情况版本。
如果您正在正常编码,请使用更易读的版本(在上下文中可能是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] 尽管如此,需要注意的是 -- 在任何比直接测试零更复杂的情况下,库和编译器代码通常会比手工编写的代码更好。
显然,这样会更快,如果您打算继续使用它,将自己的代码放在内联函数中甚至宏中可能是值得的:
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
)
f() == !a()
远远超出了任何优化器的能力。 - mikerobi访问数组的执行时间为1,比函数更快。
strcmp
调用没有任何意义。 - R.. GitHub STOP HELPING ICE