それがどのように聞こえるかというと、64ビットプロセッサは64ビットにアラインすることを意味します。つまり、そこにUnicode utf-8が格納されている場合、各8ビットチャンクは64ビットのスペースを占有します。これはあまり意味がありませんので、正確に理解するために もう少し を行う必要があると思います キャッシュの整列のしくみ 。
しかし、 メモリアライメントの目的 に対するこのすばらしい回答のおかげで、アライメントがどのように有益であるかがわかるので、 アライメント を Word と cache line を一緒に。
たとえば、utf-8でエンコードされたUnicodeを取り上げます。それをメモリに格納し、Wordの配置とキャッシュラインの配置の観点から最も効率的にアクセスするとしたら、それは/のように見えるのでしょうか。
次に例を示します。
それがどのように聞こえるかから、64ビットマシンでは、これを行うでしょう(私はまだそれを適用する方法に少し混乱しています、これが質問の理由です)、文字はASCIIでエンコードされたUnicodeになります( utf-8の単純なケースでは、asciiを使用するだけです):
a b a b a b ...
それは次のようになります:
01100001 01100010 01100001 01100010 01100001 01100010 ...
より具体的には:
011000010110001001100001011000100110000101100010...
ミックスにさらに文字を追加するには(読みやすくするために改行を追加し、データに追加しないでください):
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
...
abcdefghijklmnopqrstuvwxyz
これは26 x nで、nが100であるとすると、2600の8ビット文字(2600バイト)が互いに積み重ねられます。私の理解では、これらは「8ビット境界」または「1バイト境界」であると言えます。
しかし、今は2つの問題があります。
2600バイトなので、理論的には2600/64 = 40.625≈41キャッシュラインアラインされたチャンクがあり、ワードサイズが2バイトの場合、2600/2 = 1300ワードアラインされたチャンクになります。
今、私は道に迷っています。これらの2つの配置条件(Wordとキャッシュラインの配置)を利用するために、データへのアクセスやデータの整理方法がわかりません。もう少し詳しく説明しようとすると、質問に必要以上の混乱が生じそうです。
だから私の質問は、(a)このutf-8文字列をメモリに編成して、(b)2つの配置条件を利用しながら、(c)データ(個々の文字、またはWordサイズ間のチャンク)にアクセスする方法です。キャッシュラインサイズ、またはキャッシュラインサイズより大きいチャンク)。レジスターがそのように制限されているため、「一度に64ビットを超えるアクセス」が何を意味するのか本当にわかりません。繰り返しになりますが、実際の例として、キャッシュアラインメントがどのように機能するかを実際には理解しておらず、両方の条件に適切にアラインメントする方法を知りたいと思っています。
補足:x86の場合、どの命令を使用するか、何をしないかなど、方法を正確に知る必要はありません(説明が簡単で簡単な場合を除きます)。私は、x86を出発点として使用して、それがどのように機能するかを高いレベルで探しています。
私がこれを見る別の方法は 読む の後にあります:
- 8ビットデータを任意のアドレスに揃える[これは理解できません]
- 整列された4バイトワード内に含まれる16ビットデータを整列
- ベースアドレスが4の倍数になるように32ビットデータを整列します。
- ベースアドレスが8の倍数になるように64ビットデータを整列します。
- ベースアドレスが16の倍数になるように80ビットデータを整列します。
- ベースアドレスが16の倍数になるように128ビットデータを整列します。
正確には従いませんが、8ビットのデータの場合、4バイトのワードに揃えます。つまり、次のようになります。
<letter> <empty> <empty> <empty> <letter> <empty> <empty> <empty> ...
そう:
a---b---a---b---...
無駄なスペースがたくさんあるようです。つまり、プレーンテキストはメモリに格納するためにテキストの実際のサイズの4倍を必要としますが、これは正しくないようです。
最後に、境界に揃えるしない場合、余分なデータがフェッチされる方法について彼らが話しているとき、私は「そうしない」と考え続けますとにかく、空のスペースのフェッチを行います。」メモリがいっぱいであるかどうか、特定の時点で余分なデータをフェッチすることが有害であるかどうかはわかりません。つまり、データが次のようだったとします。
abcdef...
そして、4バイト境界で整列されます。つまり、a
にアクセスすると、実際にはabcd
をフェッチし、b
をフェッチすると、abcd
もフェッチすることになります。これが、このレイアウトでa---
をフェッチするのとどのように異なるか:
a---b---c---d---e---f---...
配置が物事にどのように影響するかを理解するために、より大きなコンテキストを見てみましょう。
まず、お気づきのように、2600バイトのUTF-8(または任意の種類のデータ)は実際に2600バイトかかります。
malloc(2600)
を使用してヒープから2600バイトを割り当てる場合Cでは、mallocはアライメント情報を受け入れないため、個々のバイトのみを格納するつもりであることがわかりません—最悪の場合を想定しています。これは、プロセッサーが使用する最大のネイティブ型にメモリを使用していることを意味しますサポートしています。 64ビットプロセッサの場合は16バイトになるため、かなり大きくなります。
したがって、メモリアロケータは、16バイトアライメント(および長さが少なくとも2600バイト)に一致する空きメモリを見つけます。 malloc
による後のメモリ割り当ても16バイト境界に切り上げられるため、2600は2600の正確な倍数であるため、2600バイトのチャンクとmallocによって返される次のメモリブロックの間に小さなギャップが生じます。 8。ただし16ではありません。(各mallocブロックに関連する他のオーバーヘッドも潜在的にあります。)
LinuxとWindowsの両方が、整列されたmallocを提供します。ただし、Linuxでは、最小の配置はポインターサイズであると明示的に述べています。明言していないWindowsでも、ドキュメントから、作者はより小さな配置ではなく、より大きな配置が要求されることを期待していることは明らかです。
Cは、ターゲットプラットフォームに適切なフィールドアライメントで構造を作成します。つまり、先行するフィールドが適切なアライメントが得られるようにレイアウトされていない場合、struct
内に未使用のパッドバイトを挿入します。例えば:
_struct S {
char c;
int i;
}
_
構造体S
はc
を1バイトの項目として宣言し、構造体のオフセット0にあります。フィールドi
は、たとえば4バイトのアイテムです。 c
の後、次に利用可能なオフセットは1ですが、4バイトの値に対して適切に調整されていないため、コンパイラーは3つのパディングバイトを挿入し、i
にオフセット4を使用して、サイズをstruct sizeof(struct S)
は5バイトの情報しか格納していませんが、8です。
エンディアンについても話しましょう。バイトの文字列がある場合、後続の各バイトは次の上位バイトアドレスに格納されます(次のアドレスに到達するには、アドレスに1を追加するだけです)—ただし、ビッグエンディアンマシンは、 4バイトのWordは、リトルエンディアンマシンから逆転しました。したがって、文字列 "abcd"にWordサイズのアクセスを使用したい場合、ビッグエンディアンマシンとリトルエンディアンマシンの違いがわかります。ビッグエンディアンマシンは 'abcd'を提供しますが、リトルエンディアンマシンは'dcba'を与えます。
要約すると、バイトとワードの両方で同じメモリを(同時に)使用しないことが一般的に最善です。バイトを保持する場合はバイトサイズのアクセスを使用し、ワードを保持する場合はワードサイズのアクセスを使用します。 。これは、元々ポイントしていたもの以外の型へのキャストポインターのような「悪いこと」をしない限り、自然に発生することに注意してください。 (必要になる場合があります。また、memcpy
やmemmove
などのルーチンの作成者は、パフォーマンスのためにいくつかのトリックを実行します。)また、バイトを混合することさえ不可能であることに注意できます。 Java(直列化に頼らずに)のような言語での(同じデータ/オブジェクト/配列に対する)サイズおよびWordサイズのアクセスは、ポインタをキャストする低レベルの機能を提供しないため。
コンパイラーとランタイム(mallocなど)が連携して、データが適切に配置されていることを確認します(おそらく過剰に配置されている場合でも)。たとえば、main
の前のスタックは、最初に(ランタイムによって)少なくとも16バイトの境界に揃えられる必要があり、コンパイラーは16バイトのサイズに切り上げられたスタックフレームを作成できます。そのため、スタックとすべてのローカル変数は、関数呼び出し中に整列されたままになります。グローバル変数も同様に扱われ、ヒープ割り当てについてはすでに説明しました。
あなたの混乱は、いくつかの建築レベルの混同から生じているようです。
64ビットのプロセッサアーキテクチャがあり、64ビットの境界に合わせたチャンクでの作業が「簡単」です。これはウォーキングタイルのようなもので、ステップがタイルと一致する場合、ステップを変更する必要はありません。歩数を変えると速度が低下します。これは、処理プログラム構造、たとえば値タイプに適用されます。処理するものがたくさんある場合は、ステップの変更を防ぐために、より大きな構造の最後に1〜2バイトを追加することは有益です。これは処理パイプラインからのデータの読み取りに役立ちます。
文字列「abcdef」の4バイト境界整列は、
a---b---c---d---e---f---
むしろ
abcdef--
キャッシュメモリは、理由からキャッシュと呼ばれます(非表示を意味するフレンチカシェにちなんでいます)。それは、それがプログラマーに対して透過的であることを意味します。目的は、頻繁に使用されるデータをプロセッサの近くに保つことです。あなたはすでにこれを知っていると思いますが、ポイントは、それがプログラマーとして心配するべきではない非常に低レベルのハードウェアの詳細であるということです。特定のプロセッサ。それはちょうどフェッチし、時間内にプロセッサに近いバイトのチャンクを取得することです。これのほとんどは、プリフェッチメカニズムによって非同期に行われます。どの戦略があなたに利益をもたらすかを予測するのは難しいでしょう。これはプラットフォームに依存し、非常に特定のシナリオでのみ機能します。反復回数が多く、非常に短くて高速なロジックを使用する非常にタイトなループです。
次にUTF-8に戻ります。これは、先ほど説明したキャッシュと比較すると、非常に高いレベルです。これは通常、バイトのストリームとして編成され、レコードアラインメントは適用されません。テキストまたはxmlの処理は、メモリからデータを取得するよりも数桁遅くなります。プロセッサはエンコーディングを気にせず、バイトを与えるだけで、それが文字かエスケープ文字か、文字列の終わりか、次の要素タグの始まりかを決定する必要があります。プロセッサのプリフェッチロジックは、今ではあくびをしているでしょう。エンコーディングはプロセッサレベルでは問題ではありません。
アラインメントの主なポイントは、各操作に必要なメモリ要求の数を減らすことです。
プロセッサは通常、外部メモリと通信するときに、キャッシュラインサイズのチャンクでメモリをフェッチして書き込みます。同様に、実行コアがキャッシュと通信するときは、Wordサイズのチャンクでキャッシュと通信します。さらに、これらのチャンクは通常、サイズの倍数のアドレスでのみアクセスされます。これにより、多くのことが簡単になります。
引用するルールは、単一のオブジェクトが通過するチャンクの数を最小限に抑えるように設計されています。
そして、キャッシュラインのサイズに到達するまで続けます。これは、最後に重要なことです(実際の大きなオブジェクトの場合は別ですが、別のメカニズムを介して同様の理由でページの配置が役立つ場合があります)。
文字列やその他の配列に関しては、ブロックの先頭をキャッシュラインサイズ(または、小さい場合に予想されるサイズ)に揃えることが便利ですが、大量の一括操作を実行することが予想される場合のみ、たとえば、メモリブロックコピー、最適化された文字列比較など。ただし、一度に1つずつ項目にアクセスするだけの場合は、このような配置は役に立ちません。
2のべき乗ではない項目の配列では、各項目が正しく配置されるようにパディングが必要になる場合があります。文字配列には内部パディングは必要ありません。