Help us understand the problem. What is going on with this article?

図解 MemoryLayout<T>で解き明かす型のメモリー構成

More than 1 year has passed since last update.

プロパティの宣言順番によってメモリサイズが異る

以下のFooとBarは、どちらもInt型とInt8型,Int32型のプロパティだけを持つ値型ですが、
それぞれ確保されるメモリサイズが異なります。

※ 1.3 GHz Intel Core i5 (64bit)の場合

foo.swift
struct Foo {
    let a: Int
    let b: Int8
    let c: Int32
}
print(MemoryLayout<Foo>.size) // 16
bar.swift
struct Bar {
    let b: Int8
    let a: Int
    let c: Int32
}
print(MemoryLayout<Bar>.size) // 20

どうしてこのような結果になるのか、
MemoryLayoutで定義されている以下の3つの型プロパティをもとに考えたいと思います。

各プロパティについて

以下リファレンスを自分なりに意訳してみました。

name 説明
alignment 型Tのデフォルトのメモリアライメント値。単位はバイト。
unsafe pointerを使ってメモリーを確保する際は、このalimentプロパティを使用すること。
この値は常に正。
size 型Tが必要とするメモリ容量(バイト数)。

この値には、動的に生成されたメモリや離れた箇所へのメモリ領域は含まれない。
とりわけ、型Tがclassの場合は、定義されているストアドプロパティの数は関係しない。

またunsafe pointerを使用して、複数のTインスタンスのメモリーを確保する際には、sizeの代わりにstrideプロパティを使用すること。
stride Array<T>もしくは連続して型Tインスタンスを確保した際に、型Tインスタンスの開始から次のインスタンスまでのバイト数。
この値は、UnsafePointer<T>インスタンスがインクリメントされる時に移動されるバイト数と同一である。

型Tは効率的なメモリ空間と引き換えに、最適なランタイムパフォーマンスを提供する最小のアライメント値を持つ。

疑問

リファレンスの説明は難しい。。。以下僕に生じた疑問です。
1. alignmentの説明が分からない。そもそもalignmentとは何なのか?
2. なぜ連続して型Tインスタンスのメモリーを確保する時は、sizeではなくstrideなのか?

まずalignementについて調べようと思います。
実はこれが前述したFooとBarのサイズが異る理由になっていました。

疑問1 alignmentとは

alignmentの必要性を理解するためには、
CPUがメインメモリからどのようにデータを読み込んでいるかを理解する必要があります。
alignmentは、この読み込みを最適化し、かつメモリリソースの効率化を実現するための仕組みだからです。

CPUがメインメモリーからデータを読み取る仕組みについて

CPUがメインメモリーからデータを読み取るまでのフロー

[CPU]メモリからデータの読み込みイメージ図.png

  1. CPUはメインメモリーに対してアドレスバスを通じて読み取りたいデータのアドレスを伝える。
  2. メインメモリーは1の要求を受けてデータバスに該当のメモリアドレスに格納されているデータを出力する。
  3. CPUはデータバスを通して、データを受け取る。

つまりCPUとメインメモリーに関して以下のことがいえます。

CPUとメインメモリーについて

  • メモリアドレスは64bitの場合、8バイトずつ割り振られている。(32bitは4バイト、16bitは2バイトずつ。またこの値は アドレス長 と表現される。)
  • CPUはメモリーに対してアドレス長毎にしかデータを取得(or格納)できない。
    もし下図のように、4バイトのデータaと8バイトのデータbを詰めてメモリーに格納してしまった場合、
    データbの値を取得するのにCPUは、メインメモリーから2回データを読み取る必要が発生する。
    データa(4)とb(8)が詰めて格納された場合.png

  • アドレス長(ここでは8バイト)毎にデータを格納すれば、一度に大きな値を取得(or格納)することが出来る。

アドレス長ごとにデータを格納すれば.png

  • 闇雲にアドレス長毎にデータを格納していくと、 小さなデータは無駄にメモリをがめる事になる。 abcが1バイトずつ.png

また上図の場合、データa,b,cを同一のアドレス位置に格納しても、データbの取得は一回で行え、また省メモリにもつながる。

abcが同じメモリアドレスに.png

そこで各型において、 CPUからの読み取り回数を減らし、かつメモリーもなるべく収まるような最適な位置にデータが割り当てるための値 を考えます。

これがalignment値です。

alignmentとは

つまりalignmentは、CPUの最適なパフォーマンスとなるべくデータの省メモリー化を実現するための兼ね合いから考え出された妥協の産物だといえます。

ここでSwift Standard Libraryで用意されている基本型のaliment値を確認すると以下の用になっていました。

※ 1.3 GHz Intel Core i5 (64bit)の場合

alignment size stride
Int8 1 1 1
Int16 2 2 2
Int32 4 4 4
Int64 8 8 8
Int 8 8 8

つまりInt8はalignmentの値が1なので、メモリーアドレス値が 1の倍数 の位置に格納されるということになります。
またInt16は 2の倍数, Int32は 4の倍数, Int64は 8の倍数 の位置に。

これはsizeの値と一緒に考えると、
例えば型Intの場合、sizeは8バイトなので、alignment値が8であれば、メインメモリーに対して8の倍数となる位置に読み込まれるため、一つのメモリアドレス空間にデータを格納できます。
※ SwiftのInt型のsizeはアドレス長です。

このaliment値から、FooとBarのメモリ構成は以下のようになっていると考えれれます。

FooとBarのメモリ構成

Fooのメモリ構成

foo.swift
struct Foo {
    let a: Int
    let b: Int8
    let c: Int32
}
print(MemoryLayout<Foo>.size) // 16

Fooのメモリ構成.png
占めているメモリ容量 = a(8) + b(1) + 灰色(3) + c(4) = 16

Barのメモリ構成

bar.swift
struct Bar {
    let b: Int8
    let a: Int
    let c: Int32
}
print(MemoryLayout<Bar>.size) // 20

Barのメモリ構成.png
占めているメモリ容量 = b(1) + 灰色(7) + a(8) + c(4) = 20

それぞれMemoryLayout<T>.sizeで取得した値になっています。

疑問2 Strideの役割

sizeプロパティの説明にて、

型Tインスタンスを連続して生成する際は、sizeではなく、stride値を使用すること

とあり、またstrideの定義は以下のように説明されています。

Array<T>もしくは連続して型Tインスタンスを確保した際に、型Tインスタンスの開始から次のインスタンスまでのバイト数。
この値は、UnsafePointer<T>インスタンスがインクリメントされる時に移動されるバイト数と同一である。



このStride値について、再度型Barを用いて考えたいと思います。

MemoryLayout<Bar> 各プロパティ値

bar.swift
struct Bar {
    let b: Int8
    let a: Int
    let c: Int32
}

MemoryLayout<Bar>における各値

alignment size stride
8 20 24

つまり型Barのalignment値は8なので、型Barインスタンスは、アドレス値が8の倍数の位置にしか格納出来ないことが分かる。
従って、size値を用いて連続的にBarインスタンスを確保することが出来ない。
そこでalignmentが考慮されたstride値が必要となると考えられる。(下図)

[Bar]のメインメモリー上のメモリ構成 .png

最後に

FooとBarのメモリ構成は最適化を指定して実行した際も異なっていました。
つまり型を定義する際に、alignment値を考慮すれば、ある程度は省メモリ化することが出来るということがいえます。

参考サイト

アライメントについて

MemoryLayout<T>について

  • Swiftのメモリレイアウトを調べる
    データをダンプしてその実態まで調査されている力に脱帽です。 今回、自分がバイナリ値を直接読み取る力がないため、図にしてみようと思ったきっかけになった記事です。
  • MemoryLayout and Data in Swift3
    MemoryLayout<T>の使い方に非常に参考にさせていただきました。
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away