2013-04-12 98 views
22

私はstd::vector<std::uint8_t>を複製する必要があります。これは、単にコピーコンストラクタを呼び出すことによって行われます。`std :: vector <std :: uint8_t>の高速コピー

私のプロファイリングの結果は、Microsoft Visual C++(msvc100)の実装ではstd::uninitialized_copyを内部的に使用していることを示しています。これは、すべての要素を1つずつコピーします。この場合、一度にメモリブロック全体をコピーすることで、より効率的なコピーを実行できます(memcpyのように)。

つまり、これは重要な最適化である可能性があります。このような最適化されたメソッドをベクトルに使用させる方法はありますか?

注:私はstd::basic_string<std::uint8_t>を使用しようとしましたが、パフォーマンスは向上しますが、その他の問題があります。

+5

通常の 'std :: copy'を試しましたか? – Rapptz

+6

最適化されたビルドでテストしましたか? – StackedCrooked

+1

std :: copyを使用してみませんか? –

答えて

1

提案された解決策に基づいて、小さなベンチマークをまとめることにしました。これは次の出力を生成

完全に最適化されmsvc100でのx64用にコンパイルされた私のPCで
#include <cstdint> 
#include <cstring> 
#include <ctime> 
#include <iostream> 
#include <random> 
#include <vector> 

using namespace std; 

int main() 
{ 
    random_device seed; 
    mt19937 rnd(seed()); 
    uniform_int_distribution<uint8_t> random_byte(0x00, 0xff); 

    const size_t n = 512 * 512; 

    vector<uint8_t> source; 
    source.reserve(n); 
    for (size_t i = 0; i < n; i++) source.push_back(random_byte(rnd)); 

    clock_t start; 
    clock_t t_constructor1 = 0; uint8_t c_constructor1 = 0; 
    clock_t t_constructor2 = 0; uint8_t c_constructor2 = 0; 
    clock_t t_assign = 0;  uint8_t c_assign = 0; 
    clock_t t_copy = 0;   uint8_t c_copy = 0; 
    clock_t t_memcpy = 0;  uint8_t c_memcpy = 0; 

    for (size_t k = 0; k < 4; k++) 
    { 
    start = clock(); 
    for (size_t i = 0; i < n/32; i++) 
    { 
     vector<uint8_t> destination(source); 
     c_constructor1 += destination[i]; 
    } 
    t_constructor1 += clock() - start; 

    start = clock(); 
    for (size_t i = 0; i < n/32; i++) 
    { 
     vector<uint8_t> destination(source.begin(), source.end()); 
     c_constructor2 += destination[i]; 
    } 
    t_constructor2 += clock() - start; 

    start = clock(); 
    for (size_t i = 0; i < n/32; i++) 
    { 
     vector<uint8_t> destination; 
     destination.assign(source.begin(), source.end()); 
     c_assign += destination[i]; 
    } 
    t_assign += clock() - start; 

    start = clock(); 
    for (size_t i = 0; i < n/32; i++) 
    { 
     vector<uint8_t> destination(source.size()); 
     copy(source.begin(), source.end(), destination.begin()); 
     c_copy += destination[i]; 
    } 
    t_copy += clock() - start; 

    start = clock(); 
    for (size_t i = 0; i < n/32; i++) 
    { 
     vector<uint8_t> destination(source.size()); 
     memcpy(&destination[0], &source[0], n); 
     c_memcpy += destination[i]; 
    } 
    t_memcpy += clock() - start; 
    } 

    // Verify that all copies are correct, but also prevent the compiler 
    // from optimising away the loops 
    uint8_t diff = (c_constructor1 - c_constructor2) + 
       (c_assign - c_copy) + 
       (c_memcpy - c_constructor1); 

    if (diff != 0) cout << "one of the methods produces invalid copies" << endl; 

    cout << "constructor (1): " << t_constructor1 << endl; 
    cout << "constructor (2): " << t_constructor2 << endl; 
    cout << "assign:   " << t_assign << endl; 
    cout << "copy    " << t_copy << endl; 
    cout << "memcpy   " << t_memcpy << endl; 

    return 0; 
} 

、、、:

constructor (1): 22388 
constructor (2): 22333 
assign:   22381 
copy    2142 
memcpy   2146 

結果は非常に明確である:両方のコンストラクタのに対しstd::copy行なうだけでなく、std::memcpy、およびassignは1桁小さいです。もちろん、正確な数値と比率はベクトルサイズに依存しますが、msvc100の結論は明らかです。suggested by Rapptzとしてstd::copyを使用してください。

編集:結論は他のコンパイラにとって明らかではありません。私は64ビットLinuxでも同様にテストを行い、Clangの次の結果を得ました。3.2

GCC 4.8は同様の出力を示します。WindowsのGCCの場合、memcpycopyはコンストラクターよりわずかに遅く、assignですが、その差は小さかったです。しかし、私の経験は、GCCはWindows上で非常にうまく最適化されないということです。私もmsvc110をテストしましたが、結果はmsvc100に似ていました。

+1

Linux/64bitでgcc 4.6.3を測定し、コンストラクタ(1):530000、コンストラクタ(2):530000、割り当て:550000、コピー830000、memcpy 840000(CLOCKS_PER_SECはおそらく異なる)。それはまったく逆です。あなたのコードがportable_でないことが意図されているならば、copyを使うことは確かに素晴らしい回避策です。 – Jacob

+0

すごい! VS2012Expressでこれをチェックしたところ、基本的に同じでした。どういうわけか私はそれを実装のバグと呼んでいます。 –

6

この回答はmsvc100に固有のものではありません。

あなたは

std::vector<uint8_t> newVect(otherVect); 

に似コピーコンストラクタを使用している場合otherVectのアロケータオブジェクトは、STLの実装では、それがパフォーマンスを得るために多くの努力を必要とするだけでなく、コピー(および使用)する必要があります。あなただけの内容をコピーしたい場合は

otherVectのは、newVectのデフォルトのアロケータを使用しています

std::vector<uint8_t> newVect(otherVect.begin(), otherVect.end()); 

を使用しています。

別の可能性は、この場合では良いSTLの実装にMEMMOVE/memcpyのに煮詰める必要があります(otherVectは、デフォルトのアロケータを使用してコピーconstuctor含む)

std::vector<uint8_t> newVect; nevVect.assign(otherVect.begin(), otherVect.end()); 

それらのすべてです。 otherVectはnewVectと全く同じ要素タイプ( 'char'や 'int8_t'などではない)を持っていることに注意してください。

一般的なアルゴリズムを使用するよりもコンテナのメソッドを使用するほうが効率的です。したがって、vector :: resize()とstd :: copy()またはmemmove()/ memcpy()の組み合わせは、ベンダーがコンテナを十分に最適化しなかった場合

+0

'memmove' ?!私は 'memcpy'を意味すると思います。私は初期値の損失を引き起こすためにベクトル(r値参照ではない)のコピーが嫌いです。 – MvG

+2

memmoveが最初のデータを失う原因はなぜだと思いますか? – jcoder

+0

@ jcoder:私は元のデータが保存されているという保証はないと思いました。私はまた、memmoveがアドレス変換テーブルを操作してページサイズのブロックを移動させるかもしれないと考えました。しかし、マニュアルページにはコピーがあるので、間違っているようです。それでも、['memmove'](http://sourceware.org/git/?p=glibc.git;a=blob;f=string/memmove.c;h=9dcd2f1f680b8b166af65b1a954f19a480758257;hb=HEAD)は、正しい方向は 'memcpy'ではないので、後者はもっと速くなければなりません。 – MvG

関連する問題