メモリのアライメントはどのくらい重要ですか?まだ重要ですか?


15

しばらくしてから、メモリアライメント、それがどのように機能し、どのように使用するかについて、多くのことを検索して読みました。私が今見つけた最も関連性の高い記事はこれです。

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

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

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

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

    C ++でメモリアラインメントが正確に機能する仕組みについて、何か違いがあるように思えますか?

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


C ++コンパイラは、これを行う傾向があります(必要または有益な場所にパディングを挿入します)。言及したリンクから、セクション12「ツール」で使用できるものを探します。
-rwong

回答:


11

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

このループを取ります。十分なループを実行する場合、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の場合ビット幅の場合、単一の命令は、この場合0x1004で32ビット、0x1008で64ビット、0x100Aで32ビットの3つの転送に変わる可能性があります。ただし、アドレス0x1008で同じ命令を持っている場合、アドレス0x1008で単一の4ワード転送を実行できます。各転送にはセットアップ時間が関連付けられています。そのため、0x1004から0x1008までのアドレスの違いは、キャッシュを使用しているときに偶数/ espであり、すべてがキャッシュヒットである場合、それ自体で数倍速くなる可能性があります。

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

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

パフォーマンスを改善するために追加したすべてのもの、より広いデータバス、パイプライン、キャッシュ、分岐予測、複数の実行ユニット/パスなどが最も役立ちます。コンパイラーまたはライブラリーがそれについてできることはほとんどありません。調整する必要があるパフォーマンスに関心がある場合、最大の調整要因の1つは、32、64、128、256だけでなく、コードとデータの調整ですビット境界だけでなく、物事が相互に関連している場合、頻繁に使用されるループまたは再使用されるデータが同じキャッシュ方法で着陸しないようにし、それぞれが独自のものを必要とします。コンパイラは、たとえば、スーパースカラアーキテクチャの命令の順序付け、相互に関係のない命令の再配置、重要ではない、

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


最終段落のみの場合は+1。メモリ帯域幅は、命令数ではなく、今日高速コードを記述しようとする人にとって最も重要な問題です。そしてこれは、多くの状況でアライメントを変更することで実現できる、キャッシュミスを減らすための最適化が非常に重要であることを意味します。
ジュール

コードとデータがキャッシュされ、そのデータに対して十分なループ/サイクルを実行する場合、命令カウントと、命令がフェッチライン内にある場所、分岐が依存するものに対してパイプ内で分岐する場所が重要です。しかし、ドラムおよび/またはフラッシュベースのシステムでは、最初にプロセッサの供給について心配する必要があります。
old_timer

15

はい、メモリの配置は重要です。

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

良いニュースは、ほとんど気にする必要がないということです。ほぼすべての言語のほぼすべてのコンパイラが、ターゲットシステムのアライメント要件を尊重するマシンコードを生成します。データのメモリ内表現を直接制御している場合にのみ、それについて考える必要があります。これは、以前ほど頻繁には必要ありません。知っておくべき興味深いことであり、作成しているさまざまな構造からメモリ使用量を理解するかどうかを知ることは絶対に重要です。しかし、そのような制御が必要でない限り(そしてほとんどのシステムでは必要ありません)、それを知らないか気にしないキャリア全体を喜んで経験することができます。


1
特に、ARMは非境界整列アクセスをサポートしていません。そして、それはモバイルが使用するほぼすべてのCPUです。
1月Hudec

また、Linuxは一部のランタイムコストで非境界整列アクセスをエミュレートしますが、Windows(CEおよび電話)はそうではなく、非境界整列アクセスを試みると、単にアプリケーションがクラッシュすることに注意してください。
ジャン・ヒューデック

2
これはほとんど正しいですが、一部のプラットフォーム(x86を含む)は、使用する命令に応じて異なるアライメント要件を持っていることに注意してください。特定の操作(たとえば、多くが16バイトのアライメントを必要とするSSE命令)は、一部の操作に使用できます。また、頻繁に使用される2つのアイテムが同じキャッシュライン(16バイトも)で発生するようにパディングを追加すると、パフォーマンスに大きな影響を与えることがあり、自動化されません。
ジュール

3

はい、それはまだ重要であり、一部のパフォーマンスクリティカルなアルゴリズムでは、コンパイラに頼ることができません。

いくつかの例をリストします。

  1. この答えから:

通常、マイクロコードはメモリから適切な4バイト量を取得しますが、アライメントされていない場合、メモリから2つの4バイト位置を取得し、2つの位置の適切なバイトから目的の4バイト量を再構築する必要があります

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

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


1

重要な状況は避ける傾向があります。それが重要な場合、それは重要です。アライメントされていないデータは、たとえば現在は回避されているように思われるバイナリデータの処理などで発生します(XMLまたはJSONを頻繁に使用します)。

どういうわけか整数の非整列配列を作成する場合、典型的なインテルプロセッサでは、その配列を処理するコードは、整列データよりも少し遅くなります。ARMプロセッサでは、コンパイラにデータがアライメントされていないことを伝えると、少し遅くなります。コンパイラに通知せずにアライメントされていないデータを使用すると、プロセッサモデルとオペレーティングシステムに応じて、ひどく、ひどく遅く実行されるか、間違った結果を与える可能性があります。

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


1
アライメントされていないデータはメモリ内で発生します。適切にパックされたデータ構造を持たないプログラムは、一見取るに足らない値の順序でさえ、大きなパフォーマンスのペナルティを被る可能性があります。たとえば、lthreadedコードでは、1つのキャッシュラインに2つの値があると、2つのスレッドが同時にアクセスすると大量のパイプラインが停止します(もちろん、スレッドの安全性の問題は無視されます)。
-greyfade

C ++コンパイラは、特定の条件下でのみフィールドの順序を変更する場合がありますが、これらのルールを認識していない場合は満たされない可能性があります。その上、私は実際にこの自由を使用するC ++コンパイラを知りません。
シェード

1
Cコンパイラがフィールドを並べ替えるのを見たことがありません。私は多くの挿入パディングとchars /
intsの

1

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

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


1

メモリのアライメントはどのくらい重要ですか?まだ重要ですか?

はい。違います。

組み込みシステムでは、コンピューターに大量のメモリが割り当てられていることが多く、メモリ管理の評論を減らすことができます。私は完全に最適化に取り組んでいますが、今では、同じプログラムを比較したり、メモリーの再配置と調整なしで?

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

メモリアライメントには他の利点がありますか?私はどこかでCPUがアライメントされたメモリでより良く/より速く動作することを読んだのですが、それは処理するための命令をより少なくします(あなたの誰かがそれに関する記事/ベンチマークへのリンクを持っている場合)、その場合、違いは本当に重要ですか?これら2つ以上の利点がありますか?

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

C ++でメモリアラインメントが正確に機能する仕組みについて、何か違いがあるように思えますか?

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.