21

アライメントされたメモリアクセスとアライメントされていないメモリアクセスの違いは何ですか?

私は TMS320C64x DSP で作業しており、組み込み関数 (アセンブリ命令用の C 関数) を使用したいと考えています。

ushort & _amem2(void *ptr);
ushort & _mem2(void *ptr);

where_amem2は 2 バイトのアライメントされたアクセスを行い、_mem2アライメントされていないアクセスを行います。

いつどれを使用する必要がありますか?

4

6 に答える 6

20

多くのコンピュータ アーキテクチャは、メモリをそれぞれ数バイトの「ワード」で格納します。たとえば、Intel の 32 ビット アーキテクチャは、それぞれ 4 バイトの 32 ビットのワードを格納します。ただし、メモリは 1 バイト レベルでアドレス指定されます。したがって、アドレスは、ワード境界で開始することを意味する「整列」することも、そうでないことを意味する「非整列」にすることもできます。

特定のアーキテクチャでは、特定のメモリ操作が遅くなるか、アラインされていないアドレスで完全に許可されない場合さえあります。

したがって、アドレスが正しいアドレスに配置されていることがわかっている場合は、スピードのために _amem2() を使用できます。それ以外の場合は、_mem2() を使用する必要があります。

于 2009-06-30T14:04:01.590 に答える
20

アラインされたメモリ アクセスとは、ポインター (整数として) がアラインメントと呼ばれる型固有の値の倍数であることを意味します。アラインメントは、型が CPU に格納されなければならない、または (パフォーマンス上の理由などで) 格納されるべきである自然なアドレスの倍数です。たとえば、CPU では、すべての 2 バイトのロードまたはストアが 2 の倍数であるアドレスを介して行われる必要がある場合があります。小さなプリミティブ型 (4 バイト未満) の場合、アラインメントはほとんどの場合、型のサイズになります。構造体の場合、アラインメントは通常、任意のメンバーの最大アラインメントです。

C コンパイラは、宣言した変数を常に「正しい」アラインメントを満たすアドレスに配置します。したがって、ptr がたとえば uint16_t 変数を指している場合、それは整列され、_amem2 を使用できます。_mem2 を使用する必要があるのは、I/O 経由で受信したパックされたバイト配列や、文字列の途中にあるバイトなどにアクセスする場合のみです。

于 2009-06-30T14:07:19.040 に答える
9

これは選択された回答を持つ古い質問であることは知っていますが、アライメントされたメモリアクセスとアライメントされていないメモリアクセスの違いに対する答えを誰も説明していません...

ドラムでも、スラムでも、フラッシュでも、その他でも構いません。簡単な例として sram を取り上げます。これはビットから構築されます。特定の sram は、固定数のビット幅と固定数の行の深さから構築されます。幅が 32 ビットで、深さが数行または数行あるとしましょう。

この sram のアドレス 0x0000 に 32 ビットの書き込みを行うと、この sram 周辺のメモリ コントローラーは行 0 に対して 1 回の書き込みサイクルを実行できます。

この sram のアドレス 0x0001 に 32 ビットの書き込みを行う場合、それが許可されていると仮定すると、コントローラーは行 0 の読み取りを行い、3 つのバイトを変更して 1 つを保持し、それを行 0 に書き込み、次に行を読み取る必要があります。 1 1 バイトを変更し、残りの 3 バイトは見つかったままにし、それを書き戻します。どのバイトが変更されるか、またはシステムのエンディアンとは関係ありません。

前者はアライメントされており、後者はアライメントされていません。明らかにパフォーマンスの違いに加えて、4 つのメモリ サイクルを実行し、バイト レーンをマージできるように追加のロジックが必要です。

アドレス 0x0000 から 32 ビットを読み取る場合、行 0 の 1 回の読み取りで完了です。しかし、0x0001 から読み取ると、row0 と row1 の 2 つの読み取りを行う必要があり、システム設計によっては、これらの 64 ビットをプロセッサに送り返すだけで、バス クロックは 1 つではなく 2 つになる可能性があります。または、メモリ コントローラーに余分なロジックがあり、1 つのバス サイクルで 32 ビットがデータ バス上で整列されます。

16ビットの読み取りは少し優れています。0x0000、0x0001、および0x0002からの読み取りはrow0からの読み取りにすぎず、システム/プロセッサの設計に基づいてこれらの32ビットを送り返すことができ、プロセッサはそれらを抽出するか、メモリコントローラーでシフトします。プロセッサが回転する必要がないように、特定のバイトレーンに着陸します。両方ではないにしても、どちらか一方が必要です。0x0003 からの読み取りは上記のようなものですが、行 0 と行 1 を読み取る必要があるのは、それぞれにバイトが 1 つずつあるためです。その後、プロセッサが抽出するために 64 ビットを送り返すか、メモリ コントローラがビットを 1 つの 32 ビット バス応答に結合します (これらの例では、プロセッサとメモリ コントローラ間のバスが 32 ビット幅であると想定しています)。

この例の sram では、16 ビットの書き込みは常に少なくとも 1 つの読み取り-変更-書き込みで終了します。アドレス 0x0003 を読み取り、2 つの行をそれぞれ 1 バイトずつ変更して書き戻します。

8ビットでは、そのバイトを含む1行を読み取るだけで済みますが、書き込みは1行の読み取り-変更-書き込みです。

トラップを無効にすることはできますが、armv4 は unaligned を好みませんでした。結果は上記のようにはなりませんが、重要ではありません。現在の arm は unaligned を許可し、上記の動作を提供します。制御レジスタで少し変更すると、unaligned で中止されます。転送します。mips は以前は許可していませんでしたが、今は何をしているのかわかりません。x86、68K などは許可されており、メモリ コントローラーが最も多くの作業を行う必要があった可能性があります。

明らかにそれを許可しない設計は、プログラマーの負担であると言う人もいれば、プログラマーにとって余分な作業がない、またはプログラマーにとってより簡単であると言う人もいるかもしれませんが、パフォーマンスとロジックの削減のためです。アライメントされているかどうかに関係なく、8ビット変数を作成してメモリを節約しようとせず、32ビットワードまたはレジスタまたはバスの自然なサイズを焼き付けたほうがよい理由もわかります。数バイトの小さなコストでパフォーマンスが向上する場合があります。言うまでもなく、32ビットレジスタが8ビット変数、マスキング、および場合によっては符号拡張を模倣するようにするために、コンパイラが追加する必要がある余分なコードは言うまでもありません。レジスタのネイティブ サイズを使用する場合、これらの追加の命令は必要ありません。

コンパイラが常にターゲットに合わせてデータを正しく配置することに同意しません。それを破る方法があります。また、ターゲットが unaligned をサポートしていない場合は、障害が発生します。プログラマーがこれについて話す必要はありません。コンパイラーが、あなたが思いつく可能性のある合法的なコードに基づいて常に正しいことを行っていれば、パフォーマンスのためでない限り、この質問をする理由はありません。void ptr アドレスをアラインするかどうかを制御しない場合は、常に mem2() アラインされていないアクセスを使用するか、ptr の値に基づいてコードで if-then-else を nik として実行する必要があります。指摘した。void として宣言することにより、C コンパイラはアラインメントを正しく処理する方法がなくなり、保証されなくなります。char *prt を取得してこれらの関数に渡すと、 mem2() 関数内またはこれら 2 つの関数の外側にコードを追加しなくても、コンパイラが正しく処理できるようになります。あなたの質問に書かれているように、 mem2() が唯一の正解です。

デスクトップ/ラップトップで使用される DRAM は、64 または 72 (ecc を使用) ビット幅になる傾向があり、それらへのすべてのアクセスが整列されます。メモリ スティックは、実際には 8 ビット幅または 16 または 32 ビット幅のチップで構成されていますが。(これは、さまざまな理由で電話/タブレットで変更される可能性があります) メモリ コントローラーと、理想的には少なくとも 1 つのキャッシュがこのドラムの前に配置され、バス幅よりも小さいアライメントされていない、またはアライメントされたアクセスが処理されます。はるかに高速なキャッシュ sram を使用し、dram アクセスはすべて全バス幅アクセスにアラインされます。ドラムの前にキャッシュがなく、コントローラーが全幅アクセス用に設計されている場合、それは最悪のパフォーマンスです。バイトレーンを個別にライトアップするように設計されている場合(8ビット幅のチップを想定)、読み取り-変更-書き込みはありませんが、より複雑なコントローラーがあります。典型的なユース ケースがキャッシュを使用する場合 (設計にキャッシュがある場合)、各バイト レーンに対してコントローラーで追加の作業を行うのは意味がないかもしれませんが、フル バス幅サイズの転送を行う方法を知っているだけです。または倍数。

于 2017-08-24T05:04:13.587 に答える
6

アラインされたアドレスは、問題のアクセス サイズの倍数のアドレスです。

  • 4 の倍数であるアドレスの 4 バイト ワードのアクセスは整列されます。
  • アドレス (たとえば) 3 から 4 バイトのアクセスは非境界整列アクセスになります

アラインされていないアクセスでも機能する_mem2関数は、そのコードで正しいアラインメントを機能させるには最適ではない可能性が非常に高くなります。これは、_mem2関数がその_amem2バージョンよりもコストがかかる可能性が高いことを意味します。

そのため、パフォーマンスが必要な場合 (特にアクセス レイテンシが高いことがわかっている場合) は、整列アクセスをいつ使用できるかを特定するのが賢明です。_amem2は、まさにこの目的のために存在します。つまり、アクセスが整列されていることがわかっているときにパフォーマンスを提供するためです。

2 バイト アクセスに関しては、アラインされた操作を識別するのは非常に簡単です。
操作のすべてのアクセス アドレスが「偶数」(つまり、それらの LSB がゼロ) の場合、2 バイト アラインメントがあります。これは、

if (address & 1) // is true
    /* we have an odd address; not aligned */
else
    /* we have an even address; its aligned to 2-bytes */
于 2009-06-30T17:34:04.773 に答える
3

_mem2 はより一般的です。ptr が整列されているかどうかに関係なく機能します。_amem2 はより厳密です: ptr が整列されている必要があります (ただし、おそらくわずかに効率的です)。そのため、ptr が常にアラインされていることを保証できない限り、_mem2 を使用してください。

于 2009-06-30T13:57:51.883 に答える
3

多くのプロセッサには、メモリ アクセスに関するアライメント制限があります。アラインされていないアクセスは、例外割り込みを生成するか (ARM など)、単に遅くなります (x86 など)。

_mem2おそらく、2バイトをフェッチし、シフトまたはビットごとの操作を使用して、それらから16ビットのushortを作成するように実装されています。

_amem2おそらく、指定されたptrから16ビットのushortを読み取るだけです。

TMS320C64x については具体的にはわかりませんが、16 ビット メモリ アクセスには 16 ビット アライメントが必要だと思います。したがって、常に使用できます_mem2が、パフォーマンスが低下_amem2し、ptr が偶数アドレスであることを保証できる場合に使用できます。

于 2009-06-30T14:04:51.103 に答える