私はいくつかのコードをレビューしていました、そして私は誰かがするのを見ました
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のソースへのリンクを提供するためのGui13への+1(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
。驚いたことに、アセンブリをよく読むことはできませんが、最適化されていないバージョンは明らかに違いを示し、最適化されたバージョンは2つの関数が同じアセンブリを生成することを明確に示しました。
したがって、一般的に、このレベルの最適化について心配する必要はありません。
組み込みシステム用のコンパイラを使用していて、この種の単純な最適化を処理できないことがわかっている場合(または標準ライブラリがまったくない場合)は、手書きの特殊なケースのバージョンを使用してください。
通常のコーディングをしている場合は、より読みやすいバージョンを使用してください(コンテキストに応じて、strcmpまたはstrlenまたは[0] == 0のimho)。
1秒間に数千回または数百万回呼び出されると予想される非常に効率的なコードを記述している場合、(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]注意が必要ですが、ゼロに対する直接テストよりも複雑な場合は、ライブラリとコンパイラのコードは通常、手作りのコードよりも優れています。
優れた最適化コンパイラは、関数呼び出しを最適化してから、インライン関数からループを排除する場合があります。同じ速度になる可能性はありますが、メソッドが遅くなる可能性はありません。
配列へのアクセスは実行時間の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
)