メモリーアライメントの目的


195

確かにわかりません。あなたが1バイトの長さのメモリワードを持つメモリを持っているとしましょう。アライメントされたアドレスの場合のように、アライメントされていないアドレス(つまり、4で割り切れない)の単一メモリアクセスで4バイトの変数にアクセスできないのはなぜですか?


17
追加のグーグル処理を行った後、この素晴らしいリンクを見つけました。これは問題を本当によく説明しています。
ARK

これを学び始める人々のためにこの小さな記事をチェックしてください: blog.virtualmethodstudio.com/2017/03/memory-alignment-run-fools
darkgaze

3
@arkリンクが壊れています
John Jiang

2
@JohnJiang私はここに新しいリンクを見つけたと思います:developer.ibm.com/technologies/systems/articles/pa-dalign
ejohnso49

回答:


62

これは、多くの基盤となるプロセッサの制限です。通常、1つの効率的なワードフェッチではなく4つの非効率的なシングルバイトフェッチを実行することで回避できますが、多くの言語指定者は、それらを非合法化してすべてを強制的に整列させる方が簡単だと判断しました。

このリンクには、OPが発見したより多くの情報があります。


310

最新のプロセッサのメモリサブシステムは、そのワードサイズの細分性と配置でのメモリへのアクセスに制限されています。これにはいくつかの理由があります。

速度

最近のプロセッサには、データをプルスルーする必要がある複数レベルのキャッシュメモリがあります。シングルバイトの読み取りをサポートすると、メモリサブシステムのスループットが実行ユニットのスループットにしっかりとバインドされます(別名CPUバインド)。これは、ハードドライブの同じ理由の多くが原因で、PIOモードがDMAをどのように上回ったかを思い起こさせます。

CPUは常にそのワードサイズ(32ビットプロセッサでは4バイト)で読み取るため、アラインされていないアドレスアクセス(それをサポートするプロセッサ上)を実行すると、プロセッサは複数のワードを読み取ります。CPUは、要求されたアドレスがまたがるメモリの各ワードを読み取ります。これにより、要求されたデータにアクセスするために必要なメモリトランザクションの数が最大2倍に増幅されます。

このため、4バイトよりも2バイトの読み込みが非常に遅くなる可能性があります。たとえば、メモリに次のような構造体があるとします。

struct mystruct {
    char c;  // one byte
    int i;   // four bytes
    short s; // two bytes
}

32ビットプロセッサでは、次のように配置される可能性があります。

構造レイアウト

プロセッサは、これらの各メンバーを1つのトランザクションで読み取ることができます。

構造体のパックされたバージョンがあったとしましょう。おそらくそれは、伝送効率のためにパックされたネットワークからのものです。次のようになります。

パックドストラクト

最初のバイトの読み取りは同じになります。

プロセッサに0x0005から16ビットを与えるように要求すると、0x0004からワードを読み取り、1バイト左にシフトして16ビットレジスタに配置する必要があります。いくつかの余分な作業がありますが、ほとんどは1つのサイクルで処理できます。

0x0001から32ビットを要求すると、2倍の増幅が得られます。プロセッサは0x0000から結果レジスタに読み込み、左に1バイトシフトし、次に0x0004から一時レジスタに再度読み込み、右に3バイトシフトORしてから、結果レジスタを使用します。

範囲

特定のアドレス空間で、アーキテクチャが2つのLSBが常に0であると想定できる場合(たとえば、32ビットマシン)、4倍以上のメモリ(2つの保存されたビットは4つの異なる状態を表すことができます)または同じ量にアクセスできます。フラグなどの2ビットのメモリ。アドレスから2つのLSBを削除すると、4バイトのアライメントが得られます。4バイトのストライドとも呼ばれます。アドレスがインクリメントされるたびに、実質的にビット0ではなくビット2がインクリメントされます00。つまり、最後の2ビットは常に継続します。

これは、システムの物理設計にも影響を与える可能性があります。アドレスバスに必要なビットが2つ少ない場合、CPUのピンが2つ少なくなり、回路基板のトレースが2つ少なくなります。

原子性

CPUはメモリの整列されたワードをアトミ​​ックに操作できます。つまり、他の命令がその操作を中断することはできません。これは、多くのロックフリーデータ構造およびその他の同時実行パラダイムの正しい操作に不可欠です。

結論

プロセッサのメモリシステムは、ここで説明するよりもかなり複雑で複雑です。x86プロセッサが実際にメモリアドレス指定する方法についての議論が役立つ場合があります(多くのプロセッサは同様に動作します)。

このIBMの記事で読むことができる、メモリー調整を順守することには、さらに多くの利点があります。

コンピューターの主な用途は、データの変換です。現代のメモリアーキテクチャとテクノロジーは数十年にわたって最適化され、信頼性の高い方法で、より高速な実行ユニット間で、より多くのデータを出し入れできます。

ボーナス:キャッシュ

私が以前に言及したもう1つのパフォーマンス調整は、(たとえば、一部のCPUで)64Bであるキャッシュラインの調整です。

キャッシュを活用することでどの程度のパフォーマンスが得られるかについて詳しくは、Gallery of Processor Cache Effectsをご覧ください。キャッシュラインサイズに関するこの質問から

キャッシュラインの理解は、特定の種類のプログラム最適化にとって重要です。たとえば、データの整列により、操作が1つまたは2つのキャッシュラインに触れるかどうかが決まります。上記の例で見たように、これは簡単に調整不良の場合、操作が2倍遅くなることを意味します。


次の構造体xyzのサイズは異なります。各メンバーのルールは、そのサイズの倍数であるアドレスで開始する必要があり、strcutは、構造体のメンバーの最大サイズの倍数であるアドレスで終了する必要があるためです。struct x {short s; // 2バイトと2つのパディングタイトint i; // 4バイト文字c; // 1バイトと3パディングバイトlong long l; }; struct y {int i; // 4バイトchar c; // 1バイトと1パディングバイトの短いs; // 2バイト}; struct z {int i; // 4バイトの短いs; // 2バイト文字c; // 1バイトと1パディングバイト};
Gavin、2014年

1
私が正しく理解すれば、なぜコンピュータが整列されていない単語を1ステップで読み取ることができないのかは、アデスが32ビットではなく30ビットを使用するためです。
GetFree

1
@chuxはい、それは本当です、絶対的なものは決してありません。8088は速度とコストの間のトレードオフの興味深い研究で、基本的に16ビット8086(完全な16ビットの外部バスを備えていた)でしたが、バスラインが半分で生産コストを節約しました。このため、完全な16ビットワードを取得するために2回の読み取りを実行する必要があったため、8088は8086の2倍のクロックサイクルでメモリにアクセスしました。興味深い部分は、8086は1サイクルで16ビットのワードアラインメントリードを実行でき、アラインメントされていないリードは2を要します。
joshperry 14年

2
@joshperry:わずかな修正:8086はワード境界で16ビットの読み取りを4サイクルで実行できますが、アライメントされていない読み取りは8サイクルかかります。メモリインターフェイスが遅いため、通常、8088ベースのマシンでの実行時間は、命令フェッチが支配的です。「MOV AX、BX」のような命令は、「XCHG AX、BX」よりも名目上1サイクル高速ですが、コードバイトごとに実行に4サイクル以上かかる命令の前または後にない場合は、4サイクル長くかかります。実行します。8086ではコードのフェッチが実行に追いつくことが時々ありますが、8088では使用しない限り...
supercat

1
@martin、本当にそうです。これらのパディングバイトを省略して、構造内ディスカッションに焦点を当てましたが、おそらくそれらを含める方が良いでしょう。
joshperry

22

一部のプロセッサでは可能ですが(nehalemではこれが可能です)、以前はすべてのメモリアクセスが64ビット(または32ビット)のラインで調整されていました。バスが64ビット幅であるため、一度に64ビットをフェッチする必要がありました。 、これらを64ビットの整列された「チャンク」でフェッチする方がはるかに簡単でした。

したがって、1バイトを取得したい場合は、64ビットのチャンクをフェッチしてから、不要なビットをマスクします。バイトが右端にある場合は簡単かつ高速ですが、64ビットチャンクの中央にある場合は、不要なビットをマスクしてから、データを正しい場所にシフトする必要があります。さらに悪いことに、2バイトの変数が必要だが、2つのチャンクに分割されている場合、必要なメモリアクセスは2倍必要でした。

したがって、メモリは安価であると誰もが思うように、メモリを浪費してコードをより高速かつ効率的に実行できるように、コンパイラのデータをプロセッサのチャンクサイズに合わせます。


5

基本的に、その理由は、メモリバスにはメモリサイズよりもはるかに小さい、特定の長さがあるためです。

そのため、CPUはオンチップのL1キャッシュから読み取ります。これは、最近の多くの場合32KBです。ただし、L1キャッシュをCPUに接続するメモリバスは、キャッシュラインサイズの幅が非常に小さくなります。これは、128 ビット程度です。

そう:

262,144 bits - size of memory
    128 bits - size of bus

誤って調整されたアクセスは、2つのキャッシュラインをオーバーラップすることがあり、データを取得するためにまったく新しいキャッシュの読み取りが必要になります。それは、DRAMまでのすべての行方を見逃す可能性さえあります。

さらに、CPUの一部は、それぞれがデータの一部を持っているこれらの2つの異なるキャッシュラインから単一のオブジェクトを組み立てるために、頭の上に立つ必要があります。一方の行では、非常に高次のビットになり、もう一方の行では、非常に低次のビットになります。

パイプラインに完全に統合されたCPUデータバスの必要なビットへのオブジェクトの移動を処理する専用ハードウェアがありますが、正しく最適化された速度で高速化するためにこれらのトランジスタを使用する方がおそらく理にかなっているため、このようなハードウェアはミスアライメントされたオブジェクトには欠けている可能性がありますプログラム。

いずれの場合も、必要な2番目のメモリ読み取りは、ミスアライメントされたメモリ操作のパッチの適用に専用のハードウェアが(仮説的に、愚かに)どれだけ専用であっても、パイプラインを遅くします。


5

@joshperryがこの質問に対して優れた回答を示しました。彼の回答に加えて、説明された効果、特に2X増幅をグラフで示すいくつかの数値があります。これは、さまざまな単語の配置の効果がどのように見えるかを示すGoogleスプレッドシートへのリンクです。さらに、ここにテスト用のコードを含むGithub gistへのリンクがあります。テストコードは @ joshperryが参照したJonathan Rentzschが書いた記事を基にしています。テストは、クアッドコア2.8 GHz Intel Core i7 64ビットプロセッサと16GBのRAMを搭載したMacbook Proで実行されました。

ここに画像の説明を入力してください


4
何をxしてy座標の意味は?
shuva '10 / 10/18

1
何世代のコアi7?(コードへのリンクを投稿していただきありがとうございます!)
Nick Desaulniers

2

バイトアドレス指定可能なメモリを備えたシステムに32ビット幅のメモリバスがある場合、同じアドレスを読み書きするためにすべて配線されている事実上4つのバイト幅のメモリシステムがあることを意味します。整列された32ビットの読み取りでは、4つのメモリシステムすべてで同じアドレスに情報を格納する必要があるため、すべてのシステムが同時にデータを提供できます。アラインされていない32ビットの読み取りでは、一部のメモリシステムが1つのアドレスからデータを返す必要があり、一部は次に高いアドレスからデータを返す必要があります。そのような要求を満たすことができるように最適化されたメモリシステムがいくつかありますが(それらのアドレスに加えて、指定よりも1つ高いアドレスを使用させる「プラス1」信号を効果的に持っています)、このような機能はかなりのコストを追加しますとメモリシステムの複雑さ。


2

32ビットデータバスを使用している場合、メモリに接続されているアドレスバスアドレスラインはA 2から始まるため、1つのバスサイクルでアクセスできるのは32ビットアライメントのアドレスのみです。

したがって、ワードがアドレスアラインメント境界にまたがる場合(16/32ビットデータのA 0または32ビットデータのA 1がゼロでない場合)、データを取得するには2バスサイクルが必要です。

一部のアーキテクチャ/命令セットは、非境界整列アクセスをサポートせず、そのような試みで例外を生成します。そのため、コンパイラが生成する非境界整列アクセスコードは、追加のバスサイクルだけでなく、追加の命令も必要とし、効率をさらに低下させます。


0

PowerPCでは、奇数アドレスから整数を問題なくロードできます。

SparcとI86および(私はそう思います)Itatniumは、これを試行するとハードウェア例外を発生させます。

1つの32ビットロードと4つの8ビットロードでは、ほとんどの最新のプロセッサで大きな違いはありません。データがすでにキャッシュにあるかどうかは、はるかに大きな影響を及ぼします。


Sparcでは、これは「バスエラー」だったため、Peter Van der Lindenの「Expert C Programming:Deep C Secrets」の「Bus error、Take the train」の章
jjg
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.