3

以下のコード(私の大きなコードから縮小されたもので、速度がそれと比較してどのように低下​​したかに驚いた後std::vector)には、2つの特有の機能があります。

  • ソースコードにごくわずかな変更を加えると、3倍以上高速に実行されます(常にVisual C ++ 2010でコンパイルします)。/O2

    注:これをもう少し楽しくするために、最後に変更のヒントを示します。これにより、自分で変更を理解するために時間を費やすことができます。元のコードは約500行だったので、修正はパフォーマンスとはかなり無関係に見えるため、ピン留めするのにかなり長い時間がかかりました。

  • 出力ループは同じように見えますが、を使用した場合よりも約20%高速に実行されます。/MTd/MT

tiny-modificationの場合のアセンブリコードの違いは次のとおりです。

  • 変更せずにループする(〜300ミリ秒):

    00403383  mov         esi,dword ptr [esp+10h] 
    00403387  mov         edx,dword ptr [esp+0Ch] 
    0040338B  mov         dword ptr [edx+esi*4],eax 
    0040338E  add         dword ptr [esp+10h],ecx 
    00403392  add         eax,ecx 
    00403394  cmp         eax,4000000h 
    00403399  jl          main+43h (403383h) 
    
  • ループ /MTdます(同じように見えますが、約270ミリ秒):

    00407D73  mov         esi,dword ptr [esp+10h] 
    00407D77  mov         edx,dword ptr [esp+0Ch] 
    00407D7B  mov         dword ptr [edx+esi*4],eax 
    00407D7E  add         dword ptr [esp+10h],ecx 
    00407D82  add         eax,ecx 
    00407D84  cmp         eax,4000000h 
    00407D89  jl          main+43h (407D73h)    
    
  • 変更を加えてループます(〜100ミリ秒!!):

    00403361  mov         dword ptr [esi+eax*4],eax 
    00403364  inc         eax  
    00403365  cmp         eax,4000000h 
    0040336A  jl          main+21h (403361h) 
    

さて、私の質問は、なぜ上記の変更がそれらの効果をもたらすのかということです。それは完全に奇妙です!

特に最初のもの-それは(コードの違いを見ると)何にも影響を与えないはずですが、それでも速度を劇的に低下させます。

これについての説明はありますか?

#include <cstdio>
#include <ctime>
#include <algorithm>
#include <memory>
template<class T, class Allocator = std::allocator<T> >
struct vector : Allocator
{
    T *p;
    size_t n;
    struct scoped
    {
        T *p_;
        size_t n_;
        Allocator &a_;
        ~scoped() { if (p_) { a_.deallocate(p_, n_); } }
        scoped(Allocator &a, size_t n) : a_(a), n_(n), p_(a.allocate(n, 0)) { }
        void swap(T *&p, size_t &n)
        {
            std::swap(p_, p);
            std::swap(n_, n);
        }
    };
    vector(size_t n) : n(0), p(0) { scoped(*this, n).swap(p, n); }
    void push_back(T const &value) { p[n++] = value; }
};
int main()
{
    int const COUNT = 1 << 26;
    vector<int> vect(COUNT);
    clock_t start = clock();
    for (int i = 0; i < COUNT; i++) { vect.push_back(i); }
    printf("time: %d\n", (clock() - start) * 1000 / CLOCKS_PER_SEC);
}

ヒント(下にマウスを置く)

それはアロケータと関係があります。

回答

に変更Allocator &a_Allocator a_ます。

4

3 に答える 3

6

との違いについての私の推測では/MT/MTdヒープ/MTd割り当てはデバッグ目的でヒープメモリをペイントし、ページングされる可能性が高くなります。これは、クロックを開始する前に発生します。

ベクトル割り当てを「予熱」すると、/MT/MTd:に対して同じ数が得られます。

vector<int> vect(COUNT);

// make sure vect's memory is warmed up
for (int i = 0; i < COUNT; i++) { vect.push_back(i); }
vect.n = 0; // clear the vector

clock_t start = clock();
for (int i = 0; i < COUNT; i++) { vect.push_back(i); }
printf("time: %d\n", (clock() - start) * 1000 / CLOCKS_PER_SEC);
于 2012-07-27T07:45:41.427 に答える
1

Allocator&がエイリアスチェーンを切断し、Allocatorが切断しないのは不思議です。

あなたが試すことができます

for(int i=vect.n; i<COUNT;++i){
    ...
}

iとnを強制するために同期されます。これにより、vcの最適化がはるかに簡単になります。

于 2012-07-27T09:39:54.647 に答える
0

えーと…「最速」のコードのようです

00403361  mov         dword ptr [esi+eax*4],eax 
00403364  inc         eax  
00403365  cmp         eax,4000000h 
0040336A  jl          main+21h (403361h) 

やや最適化されすぎています。このループでは、vect.nはまったく無視されます...ループで例外が発生した場合、vect.nは正しく更新されません。

したがって、答えは次のようになります。Allocatorを使用すると、vcは、vect.nが二度と使用されないことを認識し、無視できるようにします。それは驚くべきことですが、一般的にはそれほど有用で危険ではありません。

于 2012-07-27T21:17:30.863 に答える