6

TL;DR VS2013 のオプティマイザが混乱しているか、測定値が間違っているか、またはテストを有効にするために実際にグローバル ダミーを揮発性にする必要があるか、または ____ ですか?

免責事項:これは主に「学術的」な関心から外れています。私が見た違いが実際に製品コードに影響を与えるとは思いません。


はじめに: VS2013とVS2013の間で大きな違いが見られたため、私の最近の測定結果からこの質問にたどり着きました。(そこのコメントも参照してください)std::vector<std::unique_ptr<T> >boost::ptr_vector

私の特定のテスト ケースでは、boost::ptr_vector 内の要素へのアクセスは、unique_ptr のベクトルを使用するよりも 50% 高速になる可能性があります。

私のテストコードはここにあります: http://coliru.stacked-crooked.com/a/27dc2f1b91380cca (この質問には含めません。以下にスニペットを含めます)

  • gcc 4.8 は違いを報告しないため、これは VS2013 のものです。

    Start...
    The timings are as follows for accessing all (1000000) elements 200 times:
    * St6vectorISt10unique_ptrIjSt14default_deleteIjEESaIS3_EE: 1764 ms
    * N5boost10ptr_vectorIjNS_20heap_clone_allocatorESaIPvEEE: 1781 ms
    Dummy output: 500000
    
  • リンクされているテストコードの正確なタイミングは次のとおりです。

    Start...
    The timings are as follows for accessing all (1.000.000) elements 200 times:
    * class std::vector<....>: 344 ms
    * class boost::ptr_vector<unsigned int,....>: 216 ms
    Dummy output: 500.000
    

テストループは次のようになります。また、私が見たものを説明する長いコメントをそこに残しておきます。

template<typename C>
void RunContainerAccess(C& c) {
    for (size_t i = 0; i != loop_count; ++i) {
        for (auto& e : c) {
            // This is relevant: 
            // If the if-condition is present, VC++2013 will show 
            // approx. the same runtime for both cases. However,
            // if the only line in this loop is assigning the element
            // to the pGlobalDummy, *then* ptr_vector will run approx. 50%
            // faster than the unique_vector version!
            //
            // g++-4.8 does not show this behaviour
            //
            // Note: VS2013 commmand line: (release; /O2; no whole prg opt)
            //   /GS /analyze- /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"Release\vc120.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MD /Fa"Release\" /EHsc /nologo /Fo"Release\" /Fp"Release\simple.pch" 
            //
            // Note: http://coliru.stacked-crooked.com/ command line:
            //   g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

            // if (pGlobalDummy)
                pGlobalDummy = PtrShim(e);
        }
    }
}

ループ内の唯一の行が要素にアクセスしている (ptr をグローバル ダミーに入れている) 場合、VS2013 オプティマイザーが奇妙なことをしているように見えます。が存在する場合if (pGlobalDummy)、どちらの場合も同じです。

誰でもこれについていくつかの光を共有できますか?

ハワードの回答のおかげでvolatile、グローバルダミーに追加すると違いが生じることがわかりました。つまり、グローバルダミーが次のように揮発性である場合:

extern MyType* volatile pGlobalDummy;
MyType* volatile pGlobalDummy = nullptr;

ループの実行は少し遅くなりますが、まったく同じように実行されます。ここで揮発性が違いを生むべきですか?つまり、揮発性がなくてもテストは有効ですか?

4

1 に答える 1

6

あなたのテストでバグが見つかりました。これにより、オプティマイザーがさまざまな予測不可能な方法で最適化することが許可されます。これがあなたの結果に影響を与えているかどうかはわかりません。しかし、それは確かに私に影響を与えました。

私はトランクの先端のclang + libc++ -O3を使用しています。

コードを変更せずに実行すると、次のようになります。

Start...
The timings are as follows for accessing all (1,000,000) elements 200 times:
* NSt3__16vectorINS_10unique_ptrIjNS_14default_deleteIjEEEENS_9allocatorIS4_EEEE: 0 ms
* N5boost10ptr_vectorIjNS_20heap_clone_allocatorENSt3__19allocatorIPvEEEE: 0 ms
Dummy output: 500,000

出力単位をナノ秒に変更すると、次のようになりました。

Start...
The timings are as follows for accessing all (1,000,000) elements 200 times:
* NSt3__16vectorINS_10unique_ptrIjNS_14default_deleteIjEEEENS_9allocatorIS4_EEEE: 32 ns
* N5boost10ptr_vectorIjNS_20heap_clone_allocatorENSt3__19allocatorIPvEEEE: 32 ns
Dummy output: 500,000

疑わしい、ここに揮発性を挿入しました:

extern MyType* <ins>volatile</ins> pGlobalDummy;
MyType* <ins>volatile</ins> pGlobalDummy = nullptr;

しかし変化なし。

time[2]次に、初期化されていないことに気付いたので、次のようにします。

chron::nanoseconds time[2]<ins> = {}</ins>;

それでできました。単位をミリ秒に戻すと、次のようになります。

Start...
The timings are as follows for accessing all (1,000,000) elements 200 times:
* NSt3__16vectorINS_10unique_ptrIjNS_14default_deleteIjEEEENS_9allocatorIS4_EEEE: 394 ms
* N5boost10ptr_vectorIjNS_20heap_clone_allocatorENSt3__19allocatorIPvEEEE: 406 ms
Dummy output: 500,000

だから私は興味がありますtime[2].あなたがあなたのを明示的にゼロにする場合は、

chron::nanoseconds time[2] = {chron::nanoseconds(0), chron::nanoseconds(0)};

これはあなたが見ている結果に影響を与えますか?

明確化

std::chrono::durationデフォルトのコンストラクターは次のように指定されます。

constexpr duration() = default;

クライアントがlist-initializationを指定していない場合、これは をデフォルトで初期化します。例:durationrep

chrono::nanoseconds ns;  // default-initialized

が算術型の場合rep、初期化は行われません ([dcl.init]/p7/b3)。

クライアントがリストを初期化する場合、たとえば:

chrono::nanoseconds ns{};  // list-initialized

次にrep値が初期化されます([dcl.init.list]/p3/b7)。算術型の場合、値の初期化はゼロの初期化と同じです([dcl.init]/p8/b4)。

完全な作業例:

#include <iostream>
#include <chrono>

int
main()
{
    std::chrono::nanoseconds n1;
    std::chrono::nanoseconds n2{};
    std::chrono::nanoseconds n3 = {};
    std::cout << "n1 = " << n1.count() << "ns\n";
    std::cout << "n2 = " << n2.count() << "ns\n";
    std::cout << "n3 = " << n3.count() << "ns\n";
}

私にとって、 -O0 でコンパイルすると、次のようになります。

n1 = 0ns
n2 = 0ns
n3 = 0ns

しかし、同じものを -O3 でコンパイルすると、これは次のように変わります。

n1 = 32ns
n2 = 0ns
n3 = 0ns
于 2014-01-17T00:42:58.993 に答える