I was reviewing some code and I saw someone do a
if (0 == strcmp(foo开发者_JAVA技巧,""))
I am curious because I think it would be faster to do a
if (foo[0] == '\0')
Is this correct or is strcmp optimized enough to make them the same.
(I realize that even if there was some difference it would be small, but am thinking you save at least a few instructions by using my method.)
You're right: since calling strcmp()
adds up the stack management and the memory jump to the actual strcmp instructions, you'll gain a few instructions by just checking the first byte of your string.
For your curiosity, you can check the strcmp() code here: http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD
(I thought the code would be filled with #ifdef
and obscure __GNUSOMETHING
, but it's actually rather simple!)
strcmp() is a function call and thus has a function call overhead. foo[0] is direct access to the array, so it's obviously faster.
I see no advantage of using strcmp in this case. The compiler my be clever enough to optimize it away but it won't be any faster than checking for the '\0' byte directly. The implementer of this might have chosen this construct because he thought it is more readable but I think this is a matter of taste in this case. Although I would write the check a little different as this is the idiom that seems to be used most often to check for an empty string:
if( !*str )
and
if( *str )
to check for a non empty string.
+1 to Gui13 for providing a link to the source of gcc stdlib strcmp (http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD)!
You are correct that strcmp can never be faster than a direct comparison[1], but the question is, will the compiler optimise it? I was intimidated to try measuring that, but I was pleasantly surprised at how easy it was. My sample code is (omitting headers):
bool isEmpty(char * str) {
return 0==std::strcmp(str,"");
}
bool isEmpty2(char * str) {
return str[0]==0;
}
And I tried compiling that, first with gcc -S -o- emptystrcmptest.cc
and then with gcc -S -O2 -o- emptystrcmptest.cc
. To my pleasant surprise, although I can't read the assembly very well, the non-optimised version clearly showed a difference, and the optimised version clearly showed the two functions produced identical assembly.
So, I would say that in general, there's no point worrying about this level of optimisation.
If you are using a compiler for an embedded system and know it not to handle this sort of simple optimisation (or don't have a standard library at all), use the hand-coded special-case version.
If you are coding normally, use the more readable version (imho that may be strcmp or strlen or [0]==0 depending on context).
If you are writing highly efficient code you expect to be called thousands or millions of times a second, (a) test which is actually more efficient and (b) if the readable version is actually too slow, try to write somethign which will compile to better assembly.
With 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
With 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] Although be careful -- in cases any more complicated than a direct test against zero, the library and compiler code a typically WILL be better than hand-crafted code.
A good optimizing compiler might optimize away the function call and then eliminate the loop from the inlined function. There's no way that your method could be slower, though there is a chance that it will be the same speed.
Access to an array is order 1 in execution time, so, it´s faster than the function.
This is as micro-optimizing as it gets, but I suppose if you added a null check before you index foo (unless you know it'll never be null) it would technically save the overhead of a function call.
It's clearly going to be faster, and it's probably worth placing your own code in an inline function, or maybe even a macro, if you plan on moving forward with it:
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;
}
and let the compiler optimize that for you. Regardless, strcmp
needs to eventually check for '\0'
so you're always at least equal to it. (honestly, I'd probably let the compiler optimize internal sharing of the above, e.g., isEmpty
would probably just flip isNotEmpty
)
精彩评论