9

この投稿からの議論に続いて、構造体メンバーのアライメントの主な理由はパフォーマンス (およびいくつかのアーキテクチャーの制限) であることがわかりました。

32 ビット x86 用にコンパイルする場合、Microsoft (Visual C++)、Borland/CodeGear (C++-Builder)、Digital Mars (DMC)、および GNU (GCC) を調査する場合: のアライメントintは 4 バイトであり、アライメントされintていない場合は、メモリ バンクの 2 行が読み取られる可能性があります。

私の質問は、なぜdouble4 バイトに揃えないのですか? 4 バイトが整列されdoubleていると、2 行のメモリ バンクの読み取りも発生します。

たとえば、次の例では、doubleは 8 アラインされているため、構造体の実際のサイズは になりますsizeof(char) + (alignment for double padding) + sizeof(int) = 20 bytes

typedef struct structc_tag{
    char        c;
    double      d;
    int         s;
} structc_t;

ありがとうございました

4

4 に答える 4

12

拡張コメント:

に関するGCCのドキュメントによると-malign-double

変数doubleを 2 ワード境界に整列させると、Pentium でより高速に実行されるコードが生成されますが、より多くのメモリが消費されます。

x86-64 では、-malign-doubleデフォルトで有効になっています。

警告: スイッチを使用する場合-malign-double、上記の型を含む構造体は、386 の公開アプリケーション バイナリ インターフェイス仕様とは異なる方法で配置され、そのスイッチを使用せずにコンパイルされたコードの構造体とはバイナリ互換性がありません。

ここでのワードとは、32 ビットの i386 ワードを意味します。

doubleWindows は32 ビット モードでも値の 64 ビット アライメントを使用しますが、SysV i386 ABI 準拠の Unice は 32 ビット アライメントを使用します。32 ビットの Windows API である Win32 は、Windows NT 3.1 に由来します。これは、現在の世代の Windows バージョンとは異なり、Intel i386、Alpha、MIPS、さらにはあいまいな Intel i860 を対象としていました。Alpha や MIPS などのネイティブ RISC システムでは、double値を 64 ビットにアラインする必要があるため (そうしないと、ハードウェア障害が発生します)、移植性が Win32 i386 ABI の 64 ビット アラインメントの背後にある理論的根拠であった可能性があります。

AMD64、x86-64、または x64 とも呼ばれる 64 ビット x86 システムでは、double値を 64 ビットに揃える必要があります。そうしないと、ミスアライメント フォールトが発生し、ハードウェアが高価な「修正」を行い、メモリ アクセスが大幅に遅くなります。そのためdouble、すべての最新の x86-64 ABI (SysV および Win32) で値が 64 ビットに揃えられています。

于 2012-06-19T22:15:24.467 に答える
6

ほとんどのコンパイラは、データ値をプラットフォームのワード サイズまたはデータ型のサイズのいずれか小さい方に自動的に揃えます。コンシューマおよびエンタープライズ プロセッサの大多数は、32 ビット ワード サイズを使用します。(64 ビット システムでも通常はネイティブ ワード サイズとして 32 ビットを使用します)

そのため、構造体のメンバーの順序付けによって、メモリが浪費される可能性があります。あなたの特定のケースでは、大丈夫です。使用されたメモリの実際のフットプリントをコメントに追加します。

typedef struct structc_tag{
          char        c; // 1 byte
                         // 3 bytes (padding)
          double      d; // 8 bytes
          int         s; // 4 bytes
} structc_t;             // total: 16 bytes

このルールは構造体にも適用されるため、構造体を並べ替えて最小のフィールドが最後になるようにしても、構造体は同じサイズ (16 バイト) のままです。

typedef struct structc_tag{
          double      d; // 8 bytes
          int         s; // 4 bytes
          char        c; // 1 byte
                         // 3 bytes (padding)
} structc_t;             // total: 16 bytes

4 バイト未満のフィールドをさらに宣言する場合、それらをサイズ別にグループ化すると、メモリの削減が見られる場合があります。例えば:

typedef struct structc_tag{
          double      d1; // 8 bytes
          double      d2; // 8 bytes
          double      d3; // 8 bytes
          int         s1; // 4 bytes
          int         s2; // 4 bytes
          int         s3; // 4 bytes
          short       s4; // 2 bytes
          short       s5; // 2 bytes
          short       s6; // 2 bytes
          char        c1; // 1 byte
          char        c2; // 1 byte
          char        c3; // 1 byte
                          // 3 bytes (padding)
} structc_t;              // total: 48 bytes

愚かな構造体を宣言すると、コンパイラが要素を並べ替えない限り、多くのメモリを浪費する可能性があります (一般的に、明示的に指示されない限り、そうはなりません)。

typedef struct structc_tag{
          int         s1; // 4 bytes
          char        c1; // 1 byte
                          // 3 bytes (padding)
          int         s2; // 4 bytes
          char        c2; // 1 byte
                          // 3 bytes (padding)
          int         s3; // 4 bytes
          char        c3; // 1 byte
                          // 3 bytes (padding)
} structc_t;              // total: 24 bytes 
                          // (9 bytes wasted, or 38%)
                          // (optimal size: 16 bytes (1 byte wasted))

double は 32 ビットより大きいため、最初のセクションの規則に従って、32 ビットで整列されます。アラインメントを変更するコンパイラ オプションについて誰かが言及し、デフォルトのコンパイラ オプションは 32 ビット システムと 64 ビット システムで異なると述べましたが、これも有効です。したがって、double に関する本当の答えは、プラットフォームとコンパイラに依存するということです。

メモリのパフォーマンスはワードによって制御されます。メモリからのロードは、データの配置に応じて段階的に行われます。データが 1 ワードをカバーする場合 (つまり、ワードでアラインされている場合)、そのワードのみをロードする必要があります。正しく配置されていない場合 (つまり、0x2 の int)、プロセッサはその値を正しく読み取るために 2 ワードをロードする必要があります。同じことが double にも当てはまり、通常は 2 ワードを使用しますが、位置合わせが正しくない場合は 3 を使用します。64 ビット量のネイティブ ロードが可能な 64 ビット システムでは、適切に位置合わせされている場合、32 ビット システムで 32 ビット int のように動作します。の場合、1 回のロードでフェッチできますが、それ以外の場合は 2 回必要になります。

于 2012-06-19T20:16:21.603 に答える
4

まず第一に、アライメント要件を課すのはアーキテクチャであり、アライメントされていないメモリアクセスを許容するものもあれば、許容しないものもあります。

x86-32bitWindows プラットフォームを例に取り ましょう。このプラットフォームでは、intおよびのアライメント要件longはそれぞれです。4 bytes8 bytes

CPU が 1 回のアクセスですべてを読み取ることができるように、intアラインメント要件がである理由は明らかです。4 bytes

doulbeis8 bytesと notのアライメント要件が である理由は4 bytes、この double がアドレスに配置され、キャッシュ ラインのサイズがであっ4 bytesた場合にどうなるかを考えた場合、この場合、プロセッサはメモリから 2 つのキャッシュ ラインをロードする必要があるためです。キャッシュしますが、これが整列されている場合、これは発生しません。この場合、は常に 1 つのキャッシュ ラインの一部であり、2 つに分割されないためです。6064bitsdoubledouble

   ...58 59|60 61 62 63    64 65 66 67|68 69 70 71...
     -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
 ----------+  +  +  +  .  .  +  +  +  +--------------
           |           .  .           |
 ----------+  +  +  +  .  .  +  +  +  +-------------- 
                       .  .         
      Cache Line 1     .  .  Cache Line 2
     -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
于 2014-03-30T16:42:51.837 に答える
0

問題は、CPU アーキテクチャの点でプラットフォーム固有です。たとえば、4 バイト アラインされていないアドレスでの操作にペナルティを与えるアーキテクチャでは、変数 (実際にはそのアドレス) を 4 バイトに合わせることで、そのようなペナルティを回避できます。

コンパイラは、このようなものに非常に優れています。特に、最適化する対象の CPU アーキテクチャをコンパイラに提供すると、コンパイラはほとんどのことを行うことができ、他の多くの最適化も行うことができます。-marchたとえば、CPU アーキテクチャをターゲットにできるGCC のフラグを見てください。

于 2012-06-19T19:57:45.420 に答える