私はいくつかのコードをレビューしていて、誰かが
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 )
空でない文字列をチェックします。
gcc stdlib strcmp(http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hbのソースへのリンクを提供するためのGui13への+1 = 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
を使用してコンパイルしました。驚いたことに、私はアセンブリをよく読むことはできませんが、最適化されていないバージョンは明らかに違いを示し、最適化されたバージョンは2つの関数が同一のアセンブリを生成することを明確に示しました。
したがって、一般に、このレベルの最適化について心配する必要はないと思います。
組み込みシステムにコンパイラを使用していて、この種の単純な最適化を処理しないことがわかっている(または標準ライブラリがまったくない)場合は、手動でコーディングした特殊ケースバージョンを使用してください。
通常のコーディングを行っている場合は、より読みやすいバージョンを使用します(strcmpまたはstrlen、またはコンテキストに応じて[0] == 0の可能性があります)。
1秒間に数千回または数百万回呼び出されることが予想される非常に効率的なコードを記述している場合、(a)実際にはより効率的なテスト、および(b)読み取り可能なバージョンが実際には遅すぎる場合は、コンパイルされるsomethignを記述してみてくださいより良いアセンブリ。
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]注意してください-ゼロに対する直接テストよりも複雑なケースでは、ライブラリとコンパイラのコードは通常、手作りのコードよりも優れています。
優れた最適化コンパイラは、関数呼び出しを最適化し、インライン化された関数からループを排除する場合があります。同じ速度になる可能性はありますが、メソッドが遅くなる可能性はありません。
配列へのアクセスは、実行時間で1次であるため、関数よりも高速です。
これは可能な限りマイクロ最適化ですが、fooにインデックスを付ける前にnullチェックを追加した場合(それがnullになることを知らない限り)、技術的に関数呼び出しのオーバーヘッドを節約できると思います。
それは明らかに速くなるでしょう、そしてあなたがそれを前進させることを計画しているなら、おそらくあなた自身のコードをインライン関数、あるいはおそらくマクロでさえ置く価値があるでしょう:
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
を反転させます)