ここでstd::copy
は、わずかな、ほとんど知覚できないパフォーマンスの低下をもたらす一般的な知識に反対するつもりです。私はテストを行ったところ、それが正しくないことがわかりました。パフォーマンスの違いに気づきました。しかし、勝者はでしたstd::copy
。
C ++ SHA-2実装を作成しました。私のテストでは、4つのSHA-2バージョン(224、256、384、512)すべてを使用して5つの文字列をハッシュし、300回ループします。Boost.timerを使用して時間を測定します。その300ループカウンターは、結果を完全に安定させるのに十分です。テストは、memcpy
バージョンとバージョンを交互に5回ずつ実行しましたstd::copy
。私のコードは、(他の多くの実装がで動作可能な限りチャンクの大きいようにつかむデータを利用するchar
/ char *
Iは、で動作するのに対し、T
/ T *
(ここでT
正しいオーバーフロー挙動を有するユーザの実装で最大の種類がある)、上のように高速メモリアクセスを私ができる最大のタイプは私のアルゴリズムのパフォーマンスの中心です。これらは私の結果です:
SHA-2テストの実行を完了するまでの時間(秒)
std::copy memcpy % increase
6.11 6.29 2.86%
6.09 6.28 3.03%
6.10 6.29 3.02%
6.08 6.27 3.03%
6.08 6.27 3.03%
std :: copyのmemcpyに対する平均平均速度増加:2.99%
私のコンパイラはFedora 16 x86_64上のgcc 4.6.3です。私の最適化フラグは-Ofast -march=native -funsafe-loop-optimizations
です。
SHA-2実装のコード。
MD5実装でもテストを実行することにしました。結果ははるかに安定していなかったので、10回実行することにしました。しかし、最初の数回の試行の後、実行ごとに大きく異なる結果が得られたため、何らかのOSアクティビティが発生していると思います。最初からやり直すことにしました。
同じコンパイラ設定とフラグ。MD5のバージョンは1つしかなく、SHA-2より高速であるため、同様の5つのテスト文字列のセットで3000ループを実行しました。
これらは私の最後の10件の結果です。
MD5テストの実行を完了するまでの時間(秒)
std::copy memcpy % difference
5.52 5.56 +0.72%
5.56 5.55 -0.18%
5.57 5.53 -0.72%
5.57 5.52 -0.91%
5.56 5.57 +0.18%
5.56 5.57 +0.18%
5.56 5.53 -0.54%
5.53 5.57 +0.72%
5.59 5.57 -0.36%
5.57 5.56 -0.18%
std :: copyのmemcpy全体の平均速度低下:0.11%
MD5実装のコード
これらの結果は、私のSHA-2テストでstd :: copyを使用してstd::copy
いて、MD5テストでは使用できないいくつかの最適化があることを示唆しています。SHA-2テストでは、両方の配列がstd::copy
/ を呼び出した同じ関数で作成されましたmemcpy
。MD5テストでは、配列の1つが関数パラメーターとして関数に渡されました。
もう少しstd::copy
速くするために何ができるかを確認するために、もう少しテストを行いました。答えは簡単であることがわかりました。リンク時の最適化を有効にします。LTOをオンにした結果(gccのオプション-flto):
-fltoを使用してMD5テストの実行を完了するまでの時間(秒)
std::copy memcpy % difference
5.54 5.57 +0.54%
5.50 5.53 +0.54%
5.54 5.58 +0.72%
5.50 5.57 +1.26%
5.54 5.58 +0.72%
5.54 5.57 +0.54%
5.54 5.56 +0.36%
5.54 5.58 +0.72%
5.51 5.58 +1.25%
5.54 5.57 +0.54%
std :: copyのmemcpyに対する平均平均速度増加率:0.72%
要約すると、を使用してもパフォーマンスが低下することはないようstd::copy
です。実際、パフォーマンスが向上しているようです。
結果の説明
では、なぜstd::copy
パフォーマンスが向上するのでしょうか?
まず、インライン化の最適化がオンになっている限り、どのような実装でもそれが遅くなることはないと思います。すべてのコンパイラーは積極的にインライン化します。それは他の多くの最適化を可能にするので、それはおそらく最も重要な最適化です。std::copy
引数が簡単にコピー可能であり、メモリが順番に配置されていることを検出できます(実際の実装ではすべてそうなります)。これは、最悪の場合、memcpy
合法である場合、std::copy
パフォーマンスが低下しないことを意味します。些細な実装std::copy
するために、その延期memcpy
「速度や大きさのために最適化する際、常にこれをインライン」のコンパイラの基準を満たす必要があります。
ただし、std::copy
その情報も多く保持されます。を呼び出すstd::copy
と、関数は型をそのまま保持します。memcpy
はで動作しvoid *
、ほとんどすべての有用な情報を破棄します。たとえば、の配列を渡した場合std::uint64_t
、コンパイラまたはライブラリの実装者はで64ビットアライメントを利用できるかもしれませんstd::copy
が、でそれを行うのはより難しい場合がありますmemcpy
。このようなアルゴリズムの多くの実装では、最初に範囲の最初の位置合わせされていない部分、次に位置合わせされた部分、最後に位置合わせされていない部分を処理します。すべてが整列していることが保証されている場合、コードはよりシンプルで高速になり、プロセッサーのブランチ予測子が正しく取得しやすくなります。
時期尚早の最適化?
std::copy
面白い位置にいます。memcpy
最新の最適化コンパイラよりも遅くなることはなく、時には速くなることを期待しています。さらに、できることなら何でもmemcpy
できstd::copy
ます。memcpy
バッファーのオーバーラップは許可されませんが、std::copy
サポートは一方向のオーバーラップをサポートします(オーバーラップstd::copy_backward
のもう一方の方向で)。memcpy
、ポインタ上で動作だけでstd::copy
任意のイテレータ(上で動作しstd::map
、std::vector
、std::deque
、または独自のカスタムタイプ)。つまり、std::copy
データのチャンクをコピーする必要がある場合にのみ使用する必要があります。
char
実装に応じて、署名付きまたは署名なしの場合があることに注意してください。バイト数が128以上の場合unsigned char
、バイト配列に使用します。((int *)
キャスト(unsigned int *)
もとして安全です。)