Go言語の実用上の注意(26)| Goの安全でないパッケージのメモリレイアウト

安全ではないという名前が示すように、安全ではありません。Goはこのパッケージ名も定義しています。できるだけ使用しないようにしましょう。使用して名前を見ると、できるだけ使用しないと思うでしょう。 、またはもっと注意してください。それを使用してください。

このパッケージは安全ではありませんが、Goのメモリ安全メカニズムをバイパスしてメモリを直接読み書きできるという利点もあります。そのため、パフォーマンスのニーズのために、このパッケージを使用してメモリ操作を実行するリスクがあります。 。動作中。

関数のサイズ

Sizeofこの関数は、型が占めるメモリのサイズを返すことができます。このサイズは型にのみ関連し、型に対応する変数ストレージのサイズとは関係ありません。たとえば、bool型は1バイトを占め、 int8も1バイトを占めます。

func main() {
	fmt.Println(unsafe.Sizeof(true))
	fmt.Println(unsafe.Sizeof(int8(0)))
	fmt.Println(unsafe.Sizeof(int16(10)))
	fmt.Println(unsafe.Sizeof(int32(10000000)))
	fmt.Println(unsafe.Sizeof(int64(10000000000000)))
	fmt.Println(unsafe.Sizeof(int(10000000000000000)))
}
   

整数型の場合、占有されるバイト数は、この型によって格納される数値の範囲のサイズを意味します。たとえば、int8は1バイト(8ビット)を占有するため、格納できるサイズ範囲は-128 ~~ 127です。 −2 ^(n-1)から2 ^(n-1)-1、nはビットを意味し、int8は8ビットを意味し、int16は16ビットを意味します。

プラットフォームに関連するint型の場合、これはプラットフォームが32ビットか64ビットかによって異なり、最大のものが使用されます。たとえば、自分でテストすると、私のコンピューターは64ビットプラットフォームであるため、上記の出力ではintとint64のサイズが同じであることがわかります。

func Sizeof(x ArbitraryType) uintptr
   

上記はSizeof関数定義でありArbitraryType、パラメーターのタイプを受け取り、uintptr値のタイプを返します。ここArbitraryTypeでは気にしないでください。これは単なるプレースホルダーです。タイプはドキュメントの考慮のためにエクスポートされますが、通常は使用されません。関数が任意のタイプのデータを受信できることを知っておく必要があります。 。

// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
type ArbitraryType int
   

Alignof関数

Alignofアラインメント値のタイプを返します。これは、アラインメント係数またはアラインメントマルチプルとも呼ばれます。アラインメント値は、メモリアラインメントに関連する値です。合理的なメモリアラインメントは、メモリの読み取りと書き込みのパフォーマンスを向上させることができます。メモリアラインメントの知識については、ここでは紹介しない関連ドキュメントを参照してください。

   

上記の例の出力から、アライメント値は通常2 ^ nであり、最大値は8を超えないことがわかります(理由については、以下のメモリアライメントルールを参照してください)。Alignof関数の定義Sizeofは基本的に同じです。ここで、各人のコンピュータの結果は、互いに類似して、異なる場合があることに注意する必要があります。

func Alignof(x ArbitraryType) uintptr
   

さらに、リフレクションパッケージの関数を使用して、アライメント値を取得することもできます。つまり、とunsafe.Alignof(x)同等reflect.TypeOf(x).Align()です。

関数のオフセット

Offsetofこの関数は、構造体のメモリ位置に対する構造体構造体のフィールドのオフセットにのみ適用できます。構造体の最初のフィールドのオフセットは0です。

func main() {
	var u1 user1

	fmt.Println(unsafe.Offsetof(u1.b))
	fmt.Println(unsafe.Offsetof(u1.i))
	fmt.Println(unsafe.Offsetof(u1.j))
}


type user1 struct {
	b byte
	i int32
	j int64
}
   

フィールドのオフセットは、構造体構造体のメモリレイアウトにおけるフィールドの開始位置です(メモリ位置インデックスは0から始まります)。フィールドのオフセットに応じて、構造体のフィールドを見つけることができます。次に、構造体のフィールドを読み書きできます。プライベートであっても、ハッカーのように感じますか。オフセットの概念については、次の要約で詳しく紹介します。

また、それはあるunsafe.Offsetof(u1.i)に相当reflect.TypeOf(u1).Field(i).Offset

興味深い構造体サイズ

我々は、この構造体は、それらのタイプがあり、3つのフィールドを有し、構造体を定義しbyteint32そしてint64、しかし順序に従って、6つの組合せの合計が存在するように、これらの三つのフィールドの順序は、任意に配置することができます。

type user1 struct {
	b byte
	i int32
	j int64
}

type user2 struct {
	b byte
	j int64
	i int32
}

type user3 struct {
	i int32
	b byte
	j int64
}

type user4 struct {
	i int32
	j int64
	b byte
}

type user5 struct {
	j int64
	b byte
	i int32
}

type user6 struct {
	j int64
	i int32
	b byte
}
   

これらの6つの組み合わせに従って、user1、user2、...、user6の6つの構造体が定義されます。次に、これらの6つのタイプの構造体が占めるメモリunsafe.Sizeof()、つまり値を推測します。

バイトのサイズが1、int32のサイズが4、int64のサイズが8であり、structが実際にはフィールドの組み合わせであるため、1 + 4 + 8 = 13と推測できます。したがって、通常は次のように推測します。構造体サイズは、フィールドサイズの合計です。

しかし、これは間違いだとはっきりと言えます。

なぜそれが間違っているのですか?メモリアライメントがあり、コンパイラはメモリアライメントを使用するため、最終的なサイズの結果は異なります。ここで、これらの構造体の値を正式に検証します。

func main() {
	var u1 user1
	var u2 user2
	var u3 user3
	var u4 user4
	var u5 user5
	var u6 user6

	fmt.Println("u1 size is ",unsafe.Sizeof(u1))
	fmt.Println("u2 size is ",unsafe.Sizeof(u2))
	fmt.Println("u3 size is ",unsafe.Sizeof(u3))
	fmt.Println("u4 size is ",unsafe.Sizeof(u4))
	fmt.Println("u5 size is ",unsafe.Sizeof(u5))
	fmt.Println("u6 size is ",unsafe.Sizeof(u6))
}
   

上記の出力からわかるように、結果は次のとおりです。

u1 size is  16
u2 size is  24
u3 size is  16
u4 size is  24
u5 size is  16
u6 size is  16
   

結果が出ました(私のコンピューターの結果、Mac64ビット、あなたのものは異なるかもしれません)、4 16バイト、2 24バイト、同じでも同じでもない、これは次のことを示しています:

  1. メモリアライメントは構造体のサイズに影響します
  2. 構造体のフィールド順序は、構造体のサイズに影響します

上記の2つのポイントを組み合わせると、異なるフィールド順序が最終的に構造体のメモリサイズを決定することがわかりますしたがって、適切なフィールド順序でメモリオーバーヘッドを削減できる場合があります

メモリアライメントは構造体のメモリフットプリントに影響します。次に、フィールド定義の順序が異なると構造体のメモリフットプリントが異なる理由を詳細に分析します。

分析する前に、メモリアライメントのルールを見てみましょう。

  1. 特定のタイプの場合、アラインメント値= min(コンパイラーのデフォルトのアラインメント値、タイプサイズSizeof length)つまり、デフォルトのアライメント値とタイプのメモリフットプリントの間の最小値は、タイプのアライメント値です。私のコンピューターのデフォルトは8なので、最大値は8を超えません。
  2. 構造体の各フィールドがメモリ内で整列された後、それも整列される必要があります。整列値= min(デフォルトの整列値、フィールドの最大タイプ長)この記事も理解しやすいです。構造体のすべてのフィールドの中で、最大の型の長さとデフォルトの配置値の間の最小の型が使用されます。

上記の2つのルールを十分に理解する必要があります。そうして初めて、次の構造体構造を分析できます。ここでも、アライメント値は、アライメント係数、アライメント倍数、およびアライメント係数とも呼ばれます。これは、メモリ内の各フィールドのオフセットがアライメント値の倍数であること意味します

byte、int32、およびint64のアライメント値はそれぞれ1、4、および8であり、メモリサイズも1、4、および8であることがわかっています。したがって、最初の構造体のuser1場合、そのフィールド順序はbyte、int32、int64であり、最初に最初のメモリアライメントルールを使用してメモリアライメントを実行します。そのメモリ構造は次のとおりです。

bxxx|iiii|jjjj|jjjj

user1タイプ、最初のフィールドバイト、アライメント値1、サイズ1なので、メモリレイアウトの最初の位置に配置されます。

2番目のフィールドはint32、アライメント値は4、サイズは4であるため、メモリオフセット値は4の倍数である必要があります。現在のフィールドではuser1、2番目の位置から開始できず、5番目の位置から開始する必要があります。 、オフセットです。シフト量は4です。ビット2、3、および4はコンパイラによって埋められ、通常は値0であり、メモリホールとも呼ばれます。したがって、5番目から8番目のビットは2番目のフィールドiです。

3番目のフィールドでは、配置値は8で、サイズも8です。user1最初の2つのフィールドは8番目の位置にランク付けされているため、次のフィールドのオフセットは正確に8であり、これは3番目のフィールドの配置値の倍数です。パディングなしで、3番目のフィールドを直接配置できます。 9番目のビットから16番目のビットまでは3番目のフィールドjです。

 

最初のメモリアライメントルールの後のメモリ長が16バイトになったので、アライメントに2番目のメモリアライメントルールの使用を開始します。2番目のルールによれば、デフォルトのアラインメント値は8であり、フィールドの最大タイプ長も8であるため、構造体のアラインメント値が計算されます。現在のメモリ長は16であり、8の倍数です。アラインメントが達成されました。

これまでのところ、構造体のuser1メモリフットプリントは16バイトです。

別のuser2タイプを分析します。サイズは24ですが、フィールドiとjの順序が変更され、8バイトを使用します。理由を見てみましょう。または、最初にメモリファーストルール分析を使用します。

bxxx|xxxx|jjjj|jjjj|iiii

アラインメント値とそれが占めるサイズに応じて、最初のフィールドbオフセットは0であり、1バイトを占め、最初の位置に配置されます。

2番目のフィールドjはint64で、アライメント値とサイズは両方とも8なので、オフセット8から開始します。つまり、9番目から16番目のビットはjです。これは、2番目から8番目のビットがコンパイラによって埋められることを意味します。

現在、メモリレイアウト全体が16ビットシフトされています。これは、3番目のフィールドiのアライメント値の4の倍数であるため、パディングなしで直接配置できます。17〜20ビットはiです。

 

すべてのフィールドが整列されたので、全体のメモリサイズは1 + 7 + 8 + 4 = 20バイトです。デフォルトの整列値とデフォルトの整列値を使用して、構造の整列であるメモリ整列の2番目のルールの使用を開始します。最大フィールドサイズ、構造のアライメント値を8に見つけます。

これで、メモリレイアウト全体のサイズは8の倍数ではなく20になります。したがって、メモリを8の倍数にする必要があります。最小値は24です。したがって、位置合わせ後のメモリレイアウト全体は

bxxx|xxxx|jjjj|jjjj|iiii|xxxx

これが、最終的にuser2サイズが24になっ理由です。上記の方法に基づいて、他のいくつかの構造体のメモリレイアウトを取得できます。

user3

iiii|bxxx|jjjj|jjjj

user4

iiii|xxxx|jjjj|jjjj|bxxx|xxxx

user5

jjjj|jjjj|bxxx|iiii

user6

jjjj|jjjj|iiii|bxxx

答えは上にあります、あなたはそれを押すときにそれを参照しuser1user2試すことができます次の記事では、unsafe.Pointerによるメモリの操作とメモリの読み取りと書き込みについて紹介します。

おすすめ

転載: blog.csdn.net/qq_32907195/article/details/112464102