19

環境:

少し前に、Alexandrescu による 2001 年の DDJ の記事を見つけました: http://www.ddj.com/cpp/184403799

バッファを何らかの値に初期化するさまざまな方法を比較することです。「memset」がシングルバイト値に対して行うことと同様です。彼はさまざまな実装 (memcpy、明示的な "for" ループ、duff のデバイス) を比較しましたが、すべてのデータセット サイズとすべてのコンパイラで最適な候補を実際に見つけることはできませんでした。

見積もり:

このすべての根底には、非常に深く悲しい認識があります。2001 年は空間オデッセイの年です。(...) 箱から出して、私たちを見てください — 50 年経った今でも、私たちはメモリを埋めたりコピーしたりするのが苦手です。

質問:

  1. 誰かがこの問題に関するより最近の情報を持っていますか? 最近の GCC および Visual C++ の実装は、7 年前よりもパフォーマンスが大幅に向上していますか?
  2. 私は、寿命が 5 年以上 (おそらく 10 年以上) あり、数バイトから数百メガバイトまでの配列のサイズを処理するコードを書いています。今の選択が 5 年後も最適であるとは思えません。私は何をすべきか:
    • a)システムのmemset(または同等のもの)を使用して、最適なパフォーマンスを忘れるか、ランタイムとコンパイラがこれを処理すると想定します。
    • b)さまざまな配列サイズとコンパイラで一度だけベンチマークし、実行時にいくつかのルーチン間で切り替えます。
    • c) プログラムの初期化時にベンチマークを実行し、正確な (?) データに基づいて実行時に切り替えます。

編集:私は画像処理ソフトウェアに取り組んでいます。私の配列アイテムはPODであり、ミリ秒ごとにカウントされます!

編集2:最初の回答をありがとう、ここにいくつかの追加情報があります:

  • バッファーの初期化は、一部のアルゴリズムの合計実行時間の 20% ~ 40% を占める場合があります。
  • プラットフォームは今後 5 年以上で変化する可能性がありますが、「DELL から購入できる最速の CPU」カテゴリにとどまるでしょう。コンパイラは、GCC および Visual C++ の何らかの形式になります。レーダーに埋め込まれたものやエキゾチックなアーキテクチャはありません
  • 「SSE2015」が利用可能になったときに同じことをしなければならないので、MMXとSSEが登場したときにソフトウェアを更新しなければならなかった人々の話を聞きたいです... :)

4

12 に答える 12

10

DDJ の記事では、memset が最良の答えであり、彼が達成しようとしていたものよりもはるかに高速であることを認めています。

C のメモリ操作関数 memset、memcpy、および memcmp には神聖なものがあります。これらはコンパイラ ベンダーによって高度に最適化されている可能性が高く、コンパイラがこれらの関数の呼び出しを検出し、インライン アセンブラ命令に置き換える可能性があります。これは MSVC の場合です。

したがって、 memset が機能する場合 (つまり、1 バイトで初期化する場合) は、それを使用してください。

すべてのミリ秒がカウントされる場合がありますが、実行時間の何パーセントが設定メモリに失われるかを確認する必要があります。あなたにも役立つ仕事があることを考えると、それはおそらく非常に低いです(1または2%??). 最適化の取り組みは、他の場所ではるかに優れた収益率を持つ可能性が高いことを考えると.

于 2008-10-05T12:58:45.010 に答える
8

MASM フォーラムには、この問題を完全に打ち破った驚くべきアセンブリ言語プログラマー/愛好家が多数います (The Laboratory を参照してください)。結果は、Christopher の応答に非常に似ていました。SSE は、整列された大規模なバッファーには信じられないほど優れていますが、最終的には、基本的なforループが同じくらい高速になるほど小さなサイズに到達します。

于 2008-10-05T19:19:55.023 に答える
5

memset/memcpy は、ほとんどが基本的な命令セットを念頭に置いて作成されているため、特殊な SSE ルーチンの方が優れたパフォーマンスを発揮する可能性があります。

しかし、それをリストに減らすには:

  1. データセット <= 数百キロバイトの場合、memcpy/memset は、モックアップできるものよりも高速に実行されます。
  2. データセット > メガバイトの場合、memcpy/memset の組み合わせを使用してアライメントを取得し、独自の SSE 最適化ルーチン/Intel などから最適化されたルーチンへのフォールバックを使用します。
  3. 起動時にアライメントを強制し、独自の SSE ルーチンを使用します。

このリストは、パフォーマンスが必要な場合にのみ有効です。小さすぎる/または一度初期化されたデータセットは、手間をかける価値がありません。

これは AMD のmemcpyの実装です。コードの背後にある概念を説明した記事が見つかりません。

于 2008-10-05T13:53:19.767 に答える
4

d) 初期化で「ジェダイ マインド トリック」を実行しようとすると、不明瞭だが高速な方法と明白で明確な方法との間の累積的なミリ秒単位の差よりも多くのプログラマー時間が失われることにつながることを受け入れます。

于 2008-10-05T12:48:37.947 に答える
4

それはあなたが何をしているかによります。非常に特殊なケースがある場合、多くの場合、memset と memcpy のシステム libc (および/またはコンパイラのインライン化) を大幅に上回ることができます。

たとえば、私が取り組んでいるプログラムでは、小さなデータ サイズ用に設計された 16 バイト アラインされた memcpy と memset を作成しました。memcpy は 64 以上の 16 の倍数のサイズ (データが 16 にアラインされている) 用にのみ作成され、memset は 128 の倍数のサイズのみに作成されました。これらの制限により、私は非常に高速になりました。アプリケーションを制御したので、必要なものに合わせて機能を調整し、必要なすべてのデータを調整するようにアプリケーションを調整することもできました。

memcpy は、Windows ネイティブの memcpy の約 8 ~ 9 倍の速度で実行され、460 バイトのコピーをわずか 50 クロック サイクルにまで削減しました。memset は約 2.5 倍高速で、ゼロのスタック配列を非常に高速に埋めました。

これらの関数に興味がある場合は、ここで見つけることができます。memcpy と memset の 600 行付近までドロップダウンします。それらはかなり些細なことです。これらは、キャッシュにあるはずの小さなバッファー用に設計されていることに注意してください。キャッシュをバイパスしながらメモリ内の膨大な量のデータを初期化したい場合、問題はより複雑になる可能性があります。

于 2008-10-05T13:26:22.083 に答える
2

liboil を見ることができます。彼らは、同じ関数の異なる実装を (しようとして) 提供し、初期化で最速のものを選択します。Liboil はかなりリベラルなライセンスを持っているので、プロプライエタリなソフトウェアにも使用できます。

http://liboil.freedesktop.org/

于 2008-10-05T18:51:48.230 に答える
1

メモリを割り当てて初期化する必要がある場合は、次のようにします。

  • mallocの代わりにcallocを使用する
  • デフォルト値をできるだけゼロに変更します(例:デフォルトの列挙値をゼロにします。ブール変数のデフォルト値が「true」の場合は、その逆の値を構造に格納します)

この理由は、callocがメモリをゼロ初期化するためです。これにはメモリをゼロにするためのオーバーヘッドが伴いますが、ほとんどのコンパイラはこのルーチンを高度に最適化する可能性があります。memcpyを呼び出すことでmalloc/newよりも最適化されます。

于 2008-10-05T23:33:04.277 に答える
1

これはすべて、問題のドメインと仕様に依存します。パフォーマンスの問題に遭遇したり、タイミングの期限を守れなかったり、すべての悪の根源として memset を特定したりしたことがありますか? これがあれば、memset の調整を検討できる唯一のケースです。

次に、memset は、実行されているプラ​​ットフォームのハードウェアによって異なることに注意してください。この 5 年間、ソフトウェアは同じプラットフォームで実行されますか? 同じアーキテクチャで?その結論に達したら、「独自の」memset を試してみることができます。通常は、アーキテクチャで最もパフォーマンスが高いものに応じて、32 ビット値を一度にゼロにします。

memcmpt についても、アラインメントのオーバーヘッドが原因で問題が発生したことがありますが、通常、これは奇跡にはならず、たとえあったとしてもわずかな改善にすぎません。要件を大幅に満たしていない場合、これ以上のことはできません。

于 2008-10-05T13:06:00.160 に答える
1

この種の質問によくあることですが、問題は制御できない要因、つまりメモリの帯域幅によって制限されます。そして、ホスト OS がメモリのページングを開始することを決定した場合、事態はさらに悪化します。Win32 プラットフォームでは、メモリはページングされ、ページは最初の使用時にのみ割り当てられるため、OS が使用するページを見つける間、ページ境界ごとに大きな一時停止が発生します (これには、別のプロセスのページをディスクにページングする必要がある場合があります)。

ただし、これはこれmemsetまでに書かれた絶対的な速さです。

void memset (void *memory, size_t size, byte value)
{
}

何かをしないことが常に最速の方法です。初期を回避するためにアルゴリズムを作成する方法はありますmemsetか? 使用しているアルゴリズムは何ですか?

于 2008-10-06T08:19:37.850 に答える
1

メモリが問題にならない場合は、必要なサイズの静的バッファを事前に作成し、値に初期化してください。私の知る限り、これらのコンパイラは両方とも最適化コンパイラであるため、単純な for ループを使用すると、コンパイラは最適なアセンブラ コマンドを生成してバッファをコピーする必要があります。

メモリに問題がある場合は、小さいバッファを使用し、sizeof(..) オフセットで交差するコピーを新しいバッファにコピーします。

HTH

于 2008-10-05T13:44:53.217 に答える
1

私は常に、使用しているランタイムまたは OS (memset) の一部である初期化方法を選択します (最悪の場合、使用しているライブラリの一部である方法を選択します)。

理由: 独自の初期化を実装している場合、現在はわずかに優れたソリューションになる可能性がありますが、数年後にはランタイムが改善されている可能性があります。また、ランタイムを保守している担当者と同じ作業を行いたくありません。

ランタイムの改善がわずかである場合、これはすべて有効です。memset と独自の初期化の間に桁違いの違いがある場合、コードを実行することは理にかなっていますが、私はこのケースを本当に疑っています。

于 2008-10-05T14:50:47.747 に答える
0

年はもう 2001 年ではありません。それ以来、Visual Studio の新しいバージョンが登場しました。私はそれらの memset を研究するのに時間をかけました。memset には SSE を使用します (もちろん、利用可能な場合)。古いコードが正しかった場合、統計的にはより高速になります。しかし、不幸なコーナーケースに遭遇するかもしれません。私はコードを勉強していませんが、GCC にも同じことを期待しています。これは明らかな改善であり、オープンソースのコンパイラです。誰かがパッチを作成します。

于 2008-10-06T14:35:44.533 に答える