メモリアライメント

メモリアライメント#

x86の64bitアーキテクチャでは、8byteごとにCPU間とメモリ転送が行われます。

このサイズを跨いでメモリ上にデータが配置されていると、その跨いだデータをCPUに読み書きするために、2回分の転送が必要になり非効率的です。このため、この境界を跨がないように適度に空きを入れてメモリ上に配置する仕組みをメモリアライメントといいます。

たとえば、下記の構造体の2番目の要素であるiValueは2byte占有しますが、その前のbTest1は実際には1Byteしか占有しません。ここで詰めて配置してしまうと3Byte占有することとなり、その後データ配列によっていずれ8Byteの境界を跨いでしまいます。

TYPE test_structure :
STRUCT
    bTest1 :BOOL;// 1byte だけど、次のINT型は2byte アライメントなので、このあと1byteは空きができる
    iValue : UINT;// 2byte
    bTest3 : BOOL; // 1byte(ここまで 5byte を消費。8byteアライメントだと残り 3byte 空き。)
    diValue2 : UDINT; // 4byte
END_STRUCT
END_TYPE

これを防止するため、bTest1の後に1Byteの空きを作ってから、次のiValueを配置する、といった調整を行います。

同様に、bTest3のあとには、4Byte占有するdiValue2が並んでいます。それまでに5Byte占有していますので、ここで4Byteを詰めて配置すると境界を跨いでしまいます。よって、3Byte空きを挿入してからdiValue2のデータを配置します。

この構造体データをUNIONでByte配列と共用体で宣言してみます。

TYPE u_test :
UNION
    st_test : test_structure;
    b_raw : ARRAY [0..15] OF BYTE;
END_UNION
END_TYPE

st_testの各要素変数に最大値(全てbitの1が立った状態)をセットした場合のバイト配列の状態は次の通りとなります。

bTest1はBOOL型なので1Byte占有しますが、その後のUINT型の変数iValueは、そのサイズに合わせて1Byteの空きの後に配置されます。また、bTest3も同様に1Byte占有しますが、その後diValue2は4Byte占有しますので、3Byteの空きの後配置されます。

pack_mode 指定#

メモリアライメントを任意の値に指定する属性指定が、pack_modeです。構造体の先頭に、{attribute 'pack_mode' := '<アライメントバイト数>'}とすることで、指定のアライメントが行われます。

次の例では、1byte アライメントを指定された例です。

{attribute 'pack_mode' := '1'}
TYPE test_structure :
STRUCT
    bTest1 :BOOL;// pack_mode=1なのでこのあと空きはできない。
    iValue : INT;// 2byte
    bTest2 : BOOL; // 1byte(追加)
    bTest3 : BOOL; // 1byte(pack_mode=1なのでこのあと空きはできない。)
    diValue2 : DINT; // 4byte
END_STRUCT
END_TYPE

先ほどと異なり、ワード語長を跨いだサイズでもバイトごとに配置され、空きは発生しません。

警告

1バイトアライメントを使用すると、メモリ占有サイズを最小化することができますが、代わりにワード語長を跨いだ変数へのアクセスに余分なCPU処理回数が求められます。少しの演算負荷の増加でもリアルタイム性能に影響するようなシステムにおいては多用しないようにご注意ください。