web-dev-qa-db-ja.com

メモリ調整はどのくらい重要ですか?それでも問題はありますか?

今から、メモリアラインメント、その仕組み、使用方法について多くのことを検索して読んでいます。今のところ私が見つけた最も関連性の高い記事は これ です。

しかし、それでも私はそれについていくつかの質問があります:

  1. 組み込みシステムの外では、メモリ管理の批評をはるかに少なくするために、コンピュータに大量のメモリが存在することがよくあります。私は完全に最適化に取り組んでいますが、今では、同じプログラムをそれはメモリが再配置されずに整列されていませんか?
  2. メモリ配置には他の利点がありますか? CPUが整列されたメモリを使用すると、処理にかかる命令が少なくなるため、CPUがより良い/より速く動作することをどこかで読んだことがあります(それについて記事/ベンチマークへのリンクがある場合)、その場合、違いは本当に重要ですか?これら2つよりも多くの利点はありますか?
  3. 記事のリンクの第5章で、著者は次のように述べています。

    注意:C++では、構造体のように見えるクラスがこのルールに違反する可能性があります。 (それらが機能するかどうかは、基本クラスと仮想メンバー関数の実装方法に依存し、コンパイラーによって異なります。)

  4. この記事では主に構造体について説明していますが、ローカル変数の宣言もこの必要性の影響を受けますか?

    いくつかの違いがあるように見えるので、C++でメモリアラインメントが正確にどのように機能するかについて何か考えがありますか?

この前の質問 には「アライメント」という単語が含まれていますが、上記の質問に対する回答は提供されていません。

16
Kane

はい、データの配置と配置の両方が、数パーセントだけでなく数百パーセントから数百パーセントのパフォーマンスに大きな違いをもたらす可能性があります。

このループを見てください。十分なループを実行するには、2つの指示が重要です。

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

キャッシュあり/なし、および分岐予測でキャッシュトスあり/なしのアラインメントあり、これら2つの命令のパフォーマンスをかなりの量(タイマーティック)だけ変化させることができます。

min      max      difference
00016DDE 003E025D 003C947F

自分で簡単に実行できるパフォーマンステスト。テスト中のコードの周りにnopsを追加または削除し、正確なタイミングを実行し、テスト中の命令を十分な範囲のアドレスに沿って移動して、キャッシュラインの端に触れるなど。

データアクセスについても同様です。一部のアーキテクチャは、データフォールトを与えることにより、非整列アクセス(たとえば、アドレス0x1001で32ビットの読み取りを実行する)について不満を示します。それらのいくつかは、障害を無効にしてパフォーマンスに影響を与えることができます。アライメントされていないアクセスを許可するその他のものは、パフォーマンスに影響します。

それは時々「命令」ですが、ほとんどの場合それはクロック/バスサイクルです。

さまざまなターゲットについて、gccのmemcpy実装を確認してください。 0x43バイトの構造をコピーするとします。1バイトをコピーして0x42を残し、次に0x40バイトを効率の高いチャンクにコピーしてから、最後の0x2が2つの個別のバイトまたは16ビット転送として実行する実装を見つけることがあります。ソースと宛先のアドレスが0x1003と0x2003のように同じアライメント上にある場合、アライメントとターゲットが機能します。1バイト、次に大きなチャンクの0x40、次に0x2を実行できますが、一方が0x1002でもう一方が0x1003の場合は、本当に醜く、本当に遅い。

ほとんどの場合、バスサイクルです。または悪いことに転送の数。 ARMのような64ビット幅のデータバスを備えたプロセッサを取り、アドレス0x1004で4ワードの転送(読み取りまたは書き込み、LDMまたはSTM)を実行します。これはワードアラインアドレスであり、完全に合法ですが、バスが64の場合ビット幅では、この場合、単一の命令が3つの転送に変わる可能性があります。この場合、0x1004で32ビット、0x1008で64ビット、0x100Aで32ビットです。しかし、同じ命令がアドレス0x1008にある場合、アドレス0x1008で単一の4ワード転送を実行できます。各転送にはセットアップ時間が関連付けられています。したがって、0x1004から0x1008へのアドレスの違いは、それ自体で数倍速くなる可能性があり、キャッシュを使用している場合は/ espで、すべてがキャッシュヒットです。

ちなみに、アドレス0x1000と0x0FFCで2つのワードの読み取りを行ったとしても、キャッシュミスのある0x0FFCは2つのキャッシュラインの読み取りを引き起こし、0x1000は1つのキャッシュラインです。アクセス(使用するよりも多くのデータを読み取る)が2倍になります。構造の整列方法やデータ一般、およびそのデータへのアクセス頻度などにより、キャッシュのスラッシングが発生する可能性があります。

データをストライピングして、エビクションを作成できるようにデータをストライピングすると、実際の運が悪くなり、キャッシュの一部のみを使用してしまい、次のデータのブロブが前のブロブと衝突する可能性があります。 。すべてのキャッシュが作成されるわけではないため、コンパイラーがあなたに役立つわけではないので、データを混合したり、ソースコードなどで関数を再配置したりすることで、衝突を作成または削除できます。パフォーマンスのヒットや改善を検出することさえあなたにあります。

パフォーマンスを改善するために追加したすべてのもの、より広いデータバス、パイプライン、キャッシュ、分岐予測、複数の実行ユニット/パスなど。ほとんどの場合は役立ちますが、意図的または偶発的に悪用される可能性のある弱点があります。コンパイラーやライブラリーができることはほとんどありません。パフォーマンスに関心があり、チューニングする必要がある場合、最大のチューニング要因の1つは、32、64、128、256だけでなく、コードとデータのアライメントです。ビット境界だけでなく、物事が相互に関連している場合も、使用頻度の高いループや再利用されたデータが同じキャッシュ方法に到達しないようにし、それぞれが独自のキャッシュを必要とします。コンパイラーは、例えばスーパースカラーアーキテクチャーの命令の順序付け、相互に関係のない命令の再配置を支援し、実行パスを効率的に使用していない場合に大きなパフォーマンスの向上またはヒットをもたらすことができますが、実行しているコンパイラ。

最大の見落としは、プロセッサがボトルネックであるという仮定です。 10年以上の間、真実ではありませんでした。プロセッサーへの供給が問題であり、そこに、アライメントパフォーマンスヒット、キャッシュスラッシングなどの問題が関係しています。ソースコードレベルでも少しの作業で、構造内のデータの再配置、変数/構造体宣言の順序、ソースコード内の関数の順序、およびデータを整列するための少し余分なコードにより、パフォーマンスが数倍またはもっと。

11
old_timer

はい、メモリの調整は依然として重要です。

一部のプロセッサは、実際には非境界整列アドレスの読み取りを実行できません。そのようなハードウェアで実行していて、整数を非整列で格納する場合、実際に使用できるように、さまざまなバイトを適切な場所に配置するために、2つの命令とそれに続くいくつかの命令でそれらを読み取る必要があります。 。そのため、調整されたデータはパフォーマンスに不可欠です。

良いニュースは、あなたがほとんど気にする必要がないということです。ほとんどすべての言語のほとんどすべてのコンパイラは、ターゲットシステムのアライメント要件を尊重するマシンコードを生成します。データのメモリ内表現を直接制御している場合にのみ、考え始める必要があります。これは、以前ほど頻繁には必要ありません。これは興味深いことであり、作成しているさまざまな構造からのメモリ使用量を理解する必要があるかどうか、およびより効率的になるように再構成する方法(パディングを避ける)を知ることは非常に重要です。しかし、その種の制御が必要でない限り(そしてほとんどのシステムでは不要な場合)、それを知らないか気にしないでキャリア全体を楽しく過ごすことができます。

15
Matthew Walton

はい、それでも問題はあります。パフォーマンスが重要な一部のアルゴリズムでは、コンパイラに依存できません。

いくつかの例のみを挙げます。

  1. から この答え

通常、マイクロコードはメモリから適切な4バイトの量をフェッチしますが、整列されていない場合、メモリから2つの4バイトの場所をフェッチし、2つの場所の適切なバイトから目的の4バイトの量を再構築する必要があります

  1. SSE命令セットには特別な境界整列が必要です。それが満たされない場合は、特別な関数を使用して、境界整列されていないメモリにデータをロードおよび格納する必要があります。これは、2つの追加の命令を意味します。

パフォーマンスが重要なアルゴリズムに取り組んでいない場合は、メモリアライメントを忘れてください。通常のプログラミングでは実際には必要ありません。

3
BЈовић

重要な状況は避ける傾向があります。それが重要であれば、それは重要です。たとえば、バイナリデータを処理するときに、位置合わせされていないデータが発生していましたが、今日では回避されているようです(人々はXMLまたはJSONをよく使用しています)。

何らかの方法で整数の非整列配列を作成できた場合、一般的なIntelプロセッサでは、その配列を処理するコードは、整列されたデータよりも少し遅く実行されます。 ARMプロセッサでは、データが整列されていないことをコンパイラに通知すると、プロセッサのモデルと動作に応じて、実行速度が少し遅くなるか、間違った結果になる可能性があります。コンパイラーに指示せずに非境界整列データを使用する場合。

C++への参照の説明:Cでは、構造体のすべてのフィールドはメモリの昇順で格納する必要があります。したがって、char/double/charフィールドがあり、すべてを整列させたい場合は、1バイト文字、7バイト未使用、8バイトダブル、1バイト文字、7バイト未使用になります。 C++構造体では、互換性のために同じです。ただし、構造体の場合、コンパイラはフィールドを並べ替える可能性があるため、1つのバイト文字、別のバイト文字、6バイトが未使用、8バイトが2つある場合があります。 24バイトではなく16バイトを使用します。 C構造体では、通常、開発者はそのような状況を回避し、最初にフィールドの順序を変更します。

1
gnasher729

上記の回答では、多くの良い点がすでに述べられています。メモリのパフォーマンスとアクセス時間のデータ検索/マイニングを処理する非組み込みシステムでも追加するだけで非常に重要なので、アラインメント以外のアセンブリコードが同じように記述されます。

私も読む価値があることをお勧めします: http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf

1
Varun Mishra

メモリ調整はどのくらい重要ですか?それでも問題はありますか?

はい。いいえ、場合によります。

組み込みシステムの外では、メモリ管理の批評をはるかに少なくするために、コンピュータに大量のメモリが存在することがよくあります。私は完全に最適化に取り組んでいますが、今では、同じプログラムをそれはメモリが再配置されずに整列されていませんか?

アプリケーションのメモリフットプリントは小さくなり、適切に調整されていると、動作が速くなります。典型的なデスクトップアプリケーションでは、それはまれな/異例のケース(アプリケーションが常に同じパフォーマンスボトルネックで終了し、最適化を必要とするなど)以外では問題になりません。つまり、アプリは適切に調整されていれば、小さくて高速になりますが、実際のほとんどの場合、ユーザーに何らかの影響を与えることはありません。

メモリ配置には他の利点がありますか? CPUが整列されたメモリを使用すると、処理にかかる命令が少なくなるため、CPUがより良い/より速く動作することをどこかで読んだことがあります(それについて記事/ベンチマークへのリンクがある場合)、その場合、違いは本当に重要ですか?これら2つよりも多くの利点はありますか?

かもね。コードを書くときに(おそらく)覚えておくべきことですが、ほとんどの場合、それは重要ではありません(つまり、メンバー変数をメモリフットプリントとアクセス頻度で整理します-これにより、キャッシュが容易になりますが、使いやすさ/キャッシュの目的ではなく、コードの読み取りとリファクタリング)。

いくつかの違いがあるように見えるので、C++でメモリアラインメントが正確にどのように機能するかについて何か考えがありますか?

私はalignofのものが出てきたときにそれについて読みました(C++ 11?)それ以来、気になりませんでした(私は最近、主にデスクトップアプリケーションとバックエンドサーバーの開発を行っています)。

1
utnapistim