web-dev-qa-db-ja.com

パフォーマンスベンチマークで誤った結果が返されるのはなぜですか?

Clang-tidyオプション performance-faster-string-find は、単一の文字列リテラルを引数としてstd::basic_string::findメソッド(および関連するメソッド)の使用を検出します。彼らによれば、文字リテラルを使用する方が効率的です。

それをテストするための小さなベンチマークを実行したかったのです。したがって、私はこの小さなプログラムを作りました:

#include <string>
#include <chrono>
#include <iostream>

int main() {
    int res = 0;
    std::string s(STRING_LITERAL);

    auto start = std::chrono::steady_clock::now();

    for(int i = 0; i < 10000000; i++) {
#ifdef CHAR_TEST
        res += s.find('A');
#else  
        res += s.find("A");
#endif
    }

    auto end = std::chrono::steady_clock::now();

    std::chrono::duration<double> elapsed_seconds = end-start;
    std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";

    return res;
}

このプログラムでは2つのマクロが使用されます。

  • STRING_LITERALこれは、find関数を呼び出すstd::stringのコンテンツになります。私のベンチマークでは、このマクロは2つの値を持つことができます:小さな文字列、たとえば"BAB"、または長い文字列、たとえば"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
  • CHAR_TEST(定義されている場合)、文字リテラルのベンチマークを実行します。そうでない場合、findは単一の文字列リテラルで呼び出されます。

結果は次のとおりです。

> (echo "char with small string" ; g++ -DSTRING_LITERAL=\"BAB\" -DCHAR_TEST -O3 -o toy_exe toy.cpp && ./toy_exe) ; (echo "string literal with small string" ; g++ -DSTRING_LITERAL=\"BAB\" -O3 -o toy_exe toy.cpp && ./toy_exe) ; (echo "char with long string" ; g++ -DSTRING_LITERAL=\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\" -DCHAR_TEST -O3 -o toy_exe toy.cpp && ./toy_exe) ; (echo "string literal with long string" ; g++ -DSTRING_LITERAL=\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\" -O3 -o toy_exe toy.cpp && ./toy_exe)

char with small string
elapsed time: 0.0551678s
string literal with small string
elapsed time: 0.0493302s

char with long string
elapsed time: 0.0599704s
string literal with long string
elapsed time: 0.188888s

私の非常に醜いコマンドは、マクロの4つの可能な組み合わせのベンチマークを実行し、長いstd::stringを使用して、findの引数として文字リテラルを使用する方が確かに効率的ですが、小さいstd::stringには当てはまりません。実験を繰り返したところ、小さいstd::stringの文字リテラルの実行時間は常に約10%増加しました。

並行して、私の同僚の1人が quick-bench.com でいくつかのベンチマークを作成し、次の results を見つけました:

  • 文字リテラル付きの小さいstd::string:11単位時間
  • 小さいstd::string、1文字の文字列リテラル:20単位時間
  • 長いstd::string、文字リテラル:13単位時間
  • 長いstd::stringと単一の文字列リテラル:22単位時間

これらの結果は、clang-tidy(および論理的に聞こえる)の主張と一致しています。それで、私のベンチマークの何が問題になっていますか?なぜ一貫した間違った結果があるのですか?


EDIT:このベンチマークは、DebianでGCC 6.3.0を使用して実行されました。同様の結果を得るために、Clang 8.0.0を使用して実行しました。

4
Pierre

ベンチのマーキングに問題があるとは思いません。 repl.ioプラットフォームでまったく同じコードを実行すると、「クイックベンチ」に一致する結果が得られます。

char with small string .elapsed time:0.402103s

stringリテラルsmall文字列経過時間:0.489828s

char with long文字列経過時間:0.400224s

stringリテラルlong文字列経過時間:0.53304s

頭に浮かぶことが1つあります。あなたのプロファイリングはループ上で行われます。

0
Yitshak Yarom