web-dev-qa-db-ja.com

""比較で空の文字列をチェックするための最適化がないのはなぜですか?

Quick Bench でチェックされるこのgoogle-benchmarkコードは、string::empty()が空の文字列リテラルと比較するよりもはるかに速く実行されることを示しています。ただし、""の名前文字列を作成すると、コンパイラーはチェックを最適化します。

bool compareWithNamedConst(const std::string& target) {
    const std::string emptyString = "";
    return emptyString == target;
}

bool compareWithLiteral(const std::string& target) {
    return "" == target;
}

bool compareWithRvalue(const std::string& target) {
    return std::string{""} == target;
}

bool checkForEmpty(const std::string& target) {
    return target.empty();
}

各呼び出しのパフォーマンスを次に示します。

ご覧のとおり、""との比較は、他のすべてのオプションと比較して非常に遅くなっています。なぜそうなのでしょうか。これをテストする場合、const char*にSSOが適用されていないことに何らかの関係があるはずです。

bool compareWithLiteral(const std::string& target) {
    return "test with a longer string not optimized" == target;
}

bool compareWithRvalue(const std::string& target) {
    return std::string{"test with a longer string not optimized"} == target;
}

リテラルと比較した結果は実際には高速です:

文字列が空かどうかを確認する場合、読みやすい最も簡単な構文は"" == myVariableです。これは、myVariableが不要な混乱のないstd::stringであることを明確に示しているためです。他のすべてのケースがあるのに、なぜそれを最適化できないのですか?

5
Ádám Hunyadi

空性チェックでは、リテラル構文が本当に好きな場合は、_using namespace std::string_literals;_を使用してコンパイラを支援し、_""_を_""s_で置き換えることができます。その場合、compareWithLiteralcompareWithRvalue、およびcheckForEmptyは基本的に同じです。これは、_operator==_間の_const std::string&_は通常、コンテンツの前にサイズをチェックするため、 _""s_のサイズは取るに足らないものです。

_bool compareWithLiteral(const std::string& target) {
    using namespace std::string_literals;
    return ""s == target;
}
_

文字列がSSOに該当しない場合は、残念ながらC++ 17以降でしか使用できない場合でも、_std::string_view_とその_operator""sv_を試してみてください。 Boostには_boost::string_view_があります。コンパイル時の作成用にユーザー文字列リテラルは提供されていません(テスト文字列の長さがバイナリでハードコードされるため必須です)が、簡単に定義できます。

_#include <boost/utility/string_view.hpp>

constexpr auto operator""_sv(const char* str, std::size_t len) noexcept {
    return boost::basic_string_view<char>{str, len};
}

bool compareWithLiteral(const std::string& target) {
    return "test with a longer string not optimized"_sv == target;
}
_

このバージョンは、少なくともcompareWithLiteralサイズが異なる場合、targetバージョンよりもはるかに高速に実行されます。

1

主な違いは、std::stringオブジェクトを最初に比較するだけの比較だと思いますサイズをチェックします。それらが等しくない場合、比較(すぐに戻る)。 (これは、@ KamilCukで指摘されているように、両方の文字列が同じ長さである場合にmemcmpが使用される理由も説明します。)

逆に、リテラルでは、これらはC文字列/文字配列として扱われ、長さに関する情報は提供されません。したがって、strcmp関数が内部で使用されます大きなオーバーヘッドがある場合でも、文字列の1つが空の場合(少なくとも、strcmpからの関数呼び出しのオーバーヘッドはアセンブリにインライン化されていないようです) 。

最後の質問compareWithRvalueについては、文字列がSSOに対して大きすぎるため、動的メモリ割り当て(および割り当て解除)が実行されます(おそらくここで最適化されません)。これは比較的大きなオーバーヘッドを表しますここに。

0
Daniel Langr