web-dev-qa-db-ja.com

空の文字列のstrcmp

私はいくつかのコードをレビューしていて、誰かが

if (0 == strcmp(foo,""))

私はそれを行う方が速いと思うので、私は好奇心が強い

if (foo[0] == '\0')

これは正しいですか、またはstrcmpがそれらを同じにするのに十分最適化されていますか?.

(違いがあったとしてもそれは小さいと思いますが、私の方法を使用して少なくともいくつかの指示を保存すると考えています。)

26
Pablitorun

そのとおりです。strcmp()を呼び出すと、スタック管理とメモリジャンプが実際のstrcmp命令に追加されるため、文字列の最初のバイトを確認するだけでいくつかの命令を取得できます。

好奇心のために、ここでstrcmp()コードを確認できます: http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b ; hb = HEAD

(コードは#ifdefおよびあいまい__GNUSOMETHING、しかしそれは実際にはかなり簡単です!)

10
Gui13

strcmp()は関数呼び出しであるため、関数呼び出しのオーバーヘッドがあります。 foo [0]は配列に直接アクセスするため、明らかに高速です。

9
ralphtheninja

この場合、strcmpを使用する利点はありません。コンパイラーはそれを最適化するのに十分賢いかもしれませんが、 '\ 0'バイトを直接チェックするよりも速くはありません。これの実装者は、より読みやすいと考えたため、この構成を選択した可能性がありますが、この場合、これは好みの問題だと思います。これは空の文字列をチェックするために最も頻繁に使用されているように見えるイディオムであるため、チェックは少し異なりますが、

if( !*str )

そして

if( *str )

空でない文字列をチェックします。

5
x4u

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]注意してください-ゼロに対する直接テストよりも複雑なケースでは、ライブラリとコンパイラのコードは通常、手作りのコードよりも優れています。

4
Jack V.

優れた最適化コンパイラは、関数呼び出しを最適化し、インライン化された関数からループを排除する場合があります。同じ速度になる可能性はありますが、メソッドが遅くなる可能性はありません。

1
τεκ

配列へのアクセスは、実行時間で1次であるため、関数よりも高速です。

0
mdaguerre

これは可能な限りマイクロ最適化ですが、fooにインデックスを付ける前にnullチェックを追加した場合(それがnullになることを知らない限り)、技術的に関数呼び出しのオーバーヘッドを節約できると思います。

0
Brandon Moretz

それは明らかに速くなるでしょう、そしてあなたがそれを前進させることを計画しているなら、おそらくあなた自身のコードをインライン関数、あるいはおそらくマクロでさえ置く価値があるでしょう:

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を反転させます)

0
pickypg