web-dev-qa-db-ja.com

Cで文字列を連結すると、どの方法がより効率的ですか?

文字列を連結するこれら2つの方法に出くわしました。

共通部分:

char* first= "First";
char* second = "Second";
char* both = malloc(strlen(first) + strlen(second) + 2);

方法1:

strcpy(both, first);
strcat(both, " ");       // or space could have been part of one of the strings
strcat(both, second);

方法2:

sprintf(both, "%s %s", first, second);

どちらの場合も、bothのコンテンツは"First Second"

どちらがより効率的か(いくつかの連結操作を実行する必要があります)、またはそれを行うためのより良い方法を知っているかどうかを知りたいです。

56
Xandy

読みやすくするために、

_char * s = malloc(snprintf(NULL, 0, "%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);
_

プラットフォームがGNU拡張機能をサポートしている場合、asprintf()も使用できます。

_char * s = NULL;
asprintf(&s, "%s %s", first, second);
_

MS Cランタイムにこだわっている場合は、_scprintf()を使用して、結果の文字列の長さを決定する必要があります。

_char * s = malloc(_scprintf("%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);
_

以下は、おそらく最速のソリューションです。

_size_t len1 = strlen(first);
size_t len2 = strlen(second);

char * s = malloc(len1 + len2 + 2);
memcpy(s, first, len1);
s[len1] = ' ';
memcpy(s + len1 + 1, second, len2 + 1); // includes terminating null
_
72
Christoph

効率について心配する必要はありません。コードを読みやすく保守しやすくします。これらの方法の違いがプログラムで重要になるとは思わない。

24
Ned Batchelder

ここにあなたのためのいくつかの狂気があります、私は実際に行ってそれを測定しました。血まみれの地獄、それを想像してください。意味のある結果が得られたと思います。

「gcc foo.c -o foo.exe -std = c99 -Wall -O2」でビルドした、mingw gcc 4.4を使用して、Windowsを実行するデュアルコアP4を使用しました。

元の投稿の方法1と方法2をテストしました。当初、ベンチマークループの外側でmallocを保持していました。方法1は方法2の48倍高速でした。奇妙なことに、ビルドコマンドから-O2を削除すると、結果のexeが30%高速になりました(まだ調査していません)。

次に、ループ内でmallocとfreeを追加しました。これにより、方法1が4.4倍遅くなりました。方法2は1.1倍遅くなりました。

そのため、malloc + strlen + freeは、sprintfを避ける価値があるプロファイルを十分に支配しないでください。

私が使用したコードは次のとおりです(ループは別として!=の代わりに<を使用して実装されましたが、この投稿のHTMLレンダリングを中断しました)。

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 48; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 1; i++)
        sprintf(both, "%s %s", first, second);
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));

    // Takes 3.7 sec with optimisations, 2.7 sec WITHOUT optimisations!
    a(first, second, both);

    // Takes 3.7 sec with or without optimisations
    //b(first, second, both);

    return 0;
}
18
size_t lf = strlen(first);
size_t ls = strlen(second);

char *both = (char*) malloc((lf + ls + 2) * sizeof(char));

strcpy(both, first);

both[lf] = ' ';
strcpy(&both[lf+1], second);

違いは重要ではありません:

  • 文字列が小さい場合、mallocは文字列の連結を消し去ります。
  • 文字列が大きい場合、データのコピーに費やされた時間は、strcat/sprintfの違いをなくします。

他のポスターが言及したように、これは時期尚早な最適化です。アルゴリズムの設計に専念し、プロファイリングがパフォーマンスの問題であることが判明した場合にのみ、これに戻ります。

とはいえ... I 疑わしい方法1の方が高速です。 sprintf format-stringを解析するためのオーバーヘッドが、確かに小さなものがいくつかあります。そして、strcatは「インライン可能」である可能性が高くなります。

2
ijprest

それらはほとんど同じであるはずです。違いは重要ではありません。必要なコードが少ないため、sprintfを使用します。

2
Jay Conrod

sprintf()は単なる文字列よりもはるかに多くを処理するように設計されており、strcat()はスペシャリストです。しかし、私はあなたが小さなものを汗をかいていると思われます。 C文字列は、これら2つの提案された方法の違いをわずかなものにするという点で、根本的に非効率的です。厄介な詳細については、Joel Spolskyによる "Back to Basics" を読んでください。

これは、一般にC++がCよりもパフォーマンスが高いインスタンスです。std:: stringを使用した重い文字列の処理は、より効率的で、確実に安全です。

[編集]

[2回目の編集]修正されたコード(C文字列実装での反復が多すぎる)、タイミング、および結論の変更

Andrew Bainbridgeのstd :: stringの方が遅いというコメントには驚きましたが、このテストケースの完全なコードは投稿しませんでした。私は彼を修正し(タイミングを自動化)、std :: stringテストを追加しました。テストは、デフォルトの「リリース」オプション(最適化)、Athlonデュアルコア、2.6 GHzを備えたVC++ 2008(ネイティブコード)で行われました。結果:

C string handling = 0.023000 seconds
sprintf           = 0.313000 seconds
std::string       = 0.500000 seconds

したがって、ここではstrcat()はCの文字列規則に固有の非効率性にもかかわらず、はるかに高速です(コンパイラとオプションに応じて走行距離が異なる場合があります)。 。ただし、読み取り可能性と安全性が圧倒的に低いため、パフォーマンスが重要でない場合は、IMOのメリットはほとんどありません。

また、std :: stringstreamの実装もテストしましたが、これもまたはるかに低速でしたが、複雑な文字列のフォーマットにはまだメリットがあります。

修正されたコードは次のとおりです。

#include <ctime>
#include <cstdio>
#include <cstring>
#include <string>

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
        sprintf(both, "%s %s", first, second);
}

void c(char *first, char *second, char *both)
{
    std::string first_s(first) ;
    std::string second_s(second) ;
    std::string both_s(second) ;

    for (int i = 0; i != 1000000; i++)
        both_s = first_s + " " + second_s ;
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));
    clock_t start ;

    start = clock() ;
    a(first, second, both);
    printf( "C string handling = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    b(first, second, both);
    printf( "sprintf           = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    c(first, second, both);
    printf( "std::string       = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    return 0;
}
1
Clifford

ケース2で実際の連結が行われたことを知りません。それらを連続して印刷することは、連結を構成しません。

でも、もっと速くなると教えてください:

1)a)文字列Aを新しいバッファにコピーb)文字列Bをバッファにコピーc)バッファを出力バッファにコピー

または

1)文字列Aを出力バッファbにコピーします。文字列bを出力バッファにコピーします

0
San Jacinto
  • strcpyとstrcatは、フォーマット文字列を解析する必要があるsprintfと比較して、はるかに簡単な操作です。
  • strcpyとstrcatは小さいので、一般にコンパイラーによってインライン化され、さらに1つの関数呼び出しオーバーヘッドが節約されます。たとえば、llvmでは、strcatはstrlenを使用してインライン化され、コピーの開始位置を見つけ、その後に単純なストア命令が続きます。
0
Tong Zhou