22

私はかなり長い間 C++11 コードを書いてきましたが、ベンチマークを行っていません。移動セマンティクスにより、ベクトル操作のようなものが「ただ速くなる」ことだけを期待しています。そのため、GCC 4.7.2 と clang 3.0 (Ubuntu 12.10 64 ビットのデフォルト コンパイラ) で実際にベンチマークを行うと、非常に満足のいく結果が得られません。これは私のテストコードです:

編集: @DeadMG と @ronag によって投稿された (良い) 回答に関して、要素の型を から を持たない に変更し、std::stringすべてmy::stringswap()内部文字列を大きく (200-700 バイト) して、そうならないようにしました。SSOの犠牲者。

EDIT2: COWが理由でした。偉大なコメントによってコードを再び適応させ、ストレージをstd::stringtoから変更し、std::vector<char>コピー/移動コンストラクターを除外しました (代わりにコンパイラーにそれらを生成させます)。COW がないと、実際には速度の差は非常に大きくなります。

EDIT3:でコンパイルしたときに以前のソリューションを再追加しました-DCOW。これにより、内部ストレージが@chicostd::stringの要求に応じて ではなくになります。std::vector<char>

#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <functional>

static std::size_t dec = 0;

namespace my { class string
{
public:
    string( ) { }
#ifdef COW
    string( const std::string& ref ) : str( ref ), val( dec % 2 ? - ++dec : ++dec ) {
#else
    string( const std::string& ref ) : val( dec % 2 ? - ++dec : ++dec ) {
        str.resize( ref.size( ) );
        std::copy( ref.begin( ), ref.end( ), str.begin( ) );
#endif
    }

    bool operator<( const string& other ) const { return val < other.val; }

private:
#ifdef COW
    std::string str;
#else
    std::vector< char > str;
#endif
    std::size_t val;
}; }


template< typename T >
void dup_vector( T& vec )
{
    T v = vec;
    for ( typename T::iterator i = v.begin( ); i != v.end( ); ++i )
#ifdef CPP11
        vec.push_back( std::move( *i ) );
#else
        vec.push_back( *i );
#endif
}

int main( )
{
    std::ifstream file;
    file.open( "/etc/passwd" );
    std::vector< my::string > lines;
    while ( ! file.eof( ) )
    {
        std::string s;
        std::getline( file, s );
        lines.push_back( s + s + s + s + s + s + s + s + s );
    }

    while ( lines.size( ) < ( 1000 * 1000 ) )
        dup_vector( lines );
    std::cout << lines.size( ) << " elements" << std::endl;

    std::sort( lines.begin( ), lines.end( ) );

    return 0;
}

これが行うことは、/etc/passwd を行のベクトルに読み込み、少なくとも 100 万エントリになるまでこのベクトルを何度も複製することです。std::move()これは、最初の最適化が役立つ場所です。 に表示される明示的なものだけでなく、内部配列のサイズを変更 (新規作成 + コピー) する必要がある場合はdup_vector()push_backそれ自体のパフォーマンスが向上するはずです。

最後に、ベクトルがソートされます。2 つの要素が交換されるたびに一時オブジェクトをコピーする必要がない場合、これは間違いなく高速です。

これを 2 つの方法でコンパイルして実行します。1 つは C++98 として、もう 1 つは C++11 として (明示的な移動には -DCPP11 を使用)。

1> $ rm -f a.out ; g++ --std=c++98 test.cpp ; time ./a.out
2> $ rm -f a.out ; g++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
3> $ rm -f a.out ; clang++ --std=c++98 test.cpp ; time ./a.out
4> $ rm -f a.out ; clang++ --std=c++11 -DCPP11 test.cpp ; time ./a.out

次の結果 (コンパイルごとに 2 回):

GCC C++98
1> real 0m9.626s
1> real 0m9.709s

GCC C++11
2> real 0m10.163s
2> real 0m10.130s

そのため、C++11 コードとしてコンパイルすると、実行が少し遅くなります。同様の結果はclangにも当てはまります:

clang C++98
3> real 0m8.906s
3> real 0m8.750s

clang C++11
4> real 0m8.858s
4> real 0m9.053s

誰かがこれがなぜなのか教えてもらえますか? コンパイラは、C++11 よりも前のバージョンでコンパイルする場合でも最適化が非常に優れているため、実際には移動セマンティック動作に到達しますか? を追加する-O2と、すべてのコードがより高速に実行されますが、異なる標準間の結果は上記とほぼ同じです。

EDIT : std::string ではなく my::string を使用した新しい結果、およびより大きな個々の文字列:

$ rm -f a.out ; g++ --std=c++98 test.cpp ; time ./a.out
real    0m16.637s
$ rm -f a.out ; g++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real    0m17.169s
$ rm -f a.out ; clang++ --std=c++98 test.cpp ; time ./a.out
real    0m16.222s
$ rm -f a.out ; clang++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real    0m15.652s

C++98 と C+11 の間には、移動セマンティクスに関する非常に小さな違いがあります。GCC を使用した C++11 ではわずかに遅く、clang ではわずかに高速ですが、それでも非常に小さな違いがあります。

EDIT2:std::stringの COW がなくなると、パフォーマンスが大幅に向上します。

$ rm -f a.out ; g++ --std=c++98 test.cpp ; time ./a.out
real    0m10.313s
$ rm -f a.out ; g++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real    0m5.267s
$ rm -f a.out ; clang++ --std=c++98 test.cpp ; time ./a.out
real    0m10.218s
$ rm -f a.out ; clang++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real    0m3.376s

最適化を使用すると、違いも大きくなります。

$ rm -f a.out ; g++ -O2 --std=c++98 test.cpp ; time ./a.out
real    0m5.243s
$ rm -f a.out ; g++ -O2 --std=c++11 -DCPP11 test.cpp ; time ./a.out
real    0m0.803s
$ rm -f a.out ; clang++ -O2 --std=c++98 test.cpp ; time ./a.out
real    0m5.248s
$ rm -f a.out ; clang++ -O2 --std=c++11 -DCPP11 test.cpp ; time ./a.out
real    0m0.785s

上記は、C++11 で最大 6 ~ 7 倍高速であることを示しています。

素晴らしいコメントと回答をありがとう。この投稿が他の人にとっても有益で興味深いものになることを願っています。

4

3 に答える 3

15

2つの要素が交換されるたびに一時オブジェクトをコピーする必要がない場合、これは間違いなく高速になるはずです。

std::stringswapメンバーを持っているので、sortすでにそれを使用し、その内部実装はすでに効果的に移動セマンティクスになります。std::stringまた、SSOが関係している限り、コピーと移動の違いはわかりません。さらに、GCCの一部のバージョンには、C ++ 11で許可されていないCOWベースの実装がまだあり、コピーと移動の間に大きな違いは見られません。

于 2013-01-12T12:23:58.860 に答える
2

これはおそらく、16文字より短い文字列に対して(コンパイラによっては)発生する可能性のある小さな文字列の最適化が原因です。これらはパスワードであるため、ファイル内のすべての行は非常に短いと思います。

特定の文字列に対して小さな文字列の最適化がアクティブになっている場合、移動はコピーとして実行されます。

移動セマンティクスによる速度の向上を確認するには、より大きな文字列が必要になります。

于 2013-01-12T12:19:37.753 に答える
2

プログラムのプロファイルを作成する必要があると思います。おそらく、ほとんどの時間は、行T v = vec;std::sort(..)2000 万の文字列のベクトルに費やされます!!! 移動のセマンティクスとは関係ありません。

于 2013-01-12T12:29:44.287 に答える