Go Language Practical Notes (26) | Memory Layout of Go unsafe Package

Unsafe, as the name suggests, is unsafe. Go defines this package name as well. Let us try not to use it as much as possible. If you use it and see the name, you will think of not using it as much as possible, or be more careful. Use it.

Although this package is not safe, it also has its advantages, that is, it can bypass Go's memory safety mechanism and directly read and write memory. So sometimes because of performance needs, you will take some risks to use this package to perform memory operations. operating.

Sizeof function

SizeofThe function can return the size of the memory occupied by a type. This size is only related to the type and has nothing to do with the size of the variable storage corresponding to the type. For example, the bool type occupies one byte, and int8 also occupies one byte.

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)))
}
   

For integer types, the number of bytes occupied means the size of the range of numbers stored by this type. For example, int8 occupies one byte, which is 8bit, so the size range it can store is -128~~127, which is −2 ^(n-1) to 2^(n-1)-1, n means bit, int8 means 8bit, int16 means 16bit, and so on.

For the int type related to the platform, this depends on whether the platform is 32-bit or 64-bit, and the largest is taken. For example, when I test it myself, the above output will find that the sizes of int and int64 are the same, because my computer is a 64-bit platform.

func Sizeof(x ArbitraryType) uintptr
   

The above is Sizeofthe function definition, which receives a ArbitraryTypetype of parameter and returns a uintptrtype of value. ArbitraryTypeDon't worry about it here , it's just a placeholder. The type is exported for documentation considerations, but it is generally not used. We only need to know that it represents any type, that is, our function can receive any type of data.

// 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 function

AlignofReturns a type of alignment value, which can also be called alignment factor or alignment multiple. The alignment value is a value related to memory alignment. Reasonable memory alignment can improve the performance of memory read and write. For the knowledge of memory alignment, please refer to related documents, which will not be introduced here.

   

From the output of the above example, it can be seen that the alignment value is generally 2^n, and the maximum will not exceed 8 (for the reason, see the memory alignment rules below). AlignofThe function definition Sizeofis basically the same. It should be noted here that the results of each person's computer may be different, similar to each other.

func Alignof(x ArbitraryType) uintptr
   

In addition, the function of the reflection package can also be used to obtain the alignment value, that is to say: unsafe.Alignof(x)equivalent to reflect.TypeOf(x).Align().

Offsetof function

OffsetofThe function is only applicable to the offset of the field in the struct structure relative to the memory location of the structure. The offset of the first field of the structure is 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
}
   

The offset of the field is the starting position of the field in the memory layout of the struct structure (the memory location index starts from 0). According to the offset of the field, we can locate the fields of the structure, and then we can read and write the fields of the structure, even if they are private, does it feel like a hacker. The concept of offset, we will introduce in detail in the next summary.

In addition, it is unsafe.Offsetof(u1.i)equivalent toreflect.TypeOf(u1).Field(i).Offset

Interesting struct size

We define a struct, this struct has 3 fields, their types are byte, int32and int64, but the order of these three fields can be arranged arbitrarily, so according to the order, there are a total of 6 combinations.

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
}
   

According to these 6 combinations, 6 structs are defined, which are user1, user2,..., user6. Now, let's guess how much memory these 6 types of struct occupy, that is unsafe.Sizeof()the value.

You may guess 1+4+8=13, because the size of byte is 1, the size of int32 is 4, and the size of int64 is 8, and struct is actually a combination of fields, so it is normal to guess that the struct size is the sum of the field sizes.

But, however, I can clearly say that this is wrong.

Why is it wrong? Because there is memory alignment and the compiler uses memory alignment, then the final size result is different. Now we formally verify the values ​​of these structs.

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))
}
   

As you can see from the above output, the result is:

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

The result came out (the result of my computer, Mac64 bit, yours may be different), 4 16 bytes, 2 24 bytes, neither the same nor the same, this shows:

  1. Memory alignment affects the size of struct
  2. The field order of struct affects the size of struct

Combining the above two points, we can know that different field orders ultimately determine the memory size of the struct, so sometimes a reasonable field order can reduce memory overhead .

Memory alignment will affect the memory footprint of the struct. Now we will analyze in detail why the different order of field definitions will cause the memory footprint of the struct to be different.

Before analyzing, let's look at the rules of memory alignment:

  1. For specific types, the alignment value = min (the compiler default alignment value, type size Sizeof length) . That is, between the default alignment value and the memory footprint of the type, the minimum value is the alignment value of the type. My computer defaults to 8, so the maximum value will not exceed 8.
  2. After each field of struct is aligned in memory, it also needs to be aligned, alignment value = min (default alignment value, maximum type length of the field) . This article is also easy to understand. Among all the fields of the struct, the smallest type between the length of the largest type and the default alignment value is taken.

The above two rules must be well understood, and only then can the following struct structure be analyzed. Here again, the alignment value is also called alignment coefficient, alignment multiple, and alignment modulus. This means that the offset of each field in memory is a multiple of the alignment value .

We know that the alignment values ​​of byte, int32, and int64 are 1, 4, and 8, respectively, and the memory size is also 1, 4, and 8. Then for the first struct user1, its field order is byte, int32, int64, we first use the first memory alignment rule to perform memory alignment, and its memory structure is as follows.

bxxx|iiii|jjjj|jjjj

user1Type, the first field byte, alignment value 1, size 1, so it is placed in the first position in the memory layout.

The second field is int32, the alignment value is 4, and the size is 4, so its memory offset value must be a multiple of 4. In the current user1one, it cannot start from the second position, and must start from the fifth position, which is the offset The shift amount is 4. Bits 2, 3, and 4 are filled by the compiler, and are generally value 0, which is also called a memory hole. So the 5th to 8th bits are the second field i.

In the third field, the alignment value is 8, and the size is also 8. Because the user1first two fields have been ranked to the 8th position, the offset of the next one is exactly 8, which is a multiple of the alignment value of the third field. Without padding, you can arrange the third field directly, that is, from the 9th Bit to the 16th bit is the third field j.

 

Now that the memory length is 16 bytes after the first memory alignment rule, we start to use the second memory alignment rule for alignment. According to the second rule, the default alignment value is 8, and the maximum type length in the field is also 8, so the alignment value bit of the structure is calculated. Our current memory length is 16, which is a multiple of 8, and alignment has been achieved.

So so far, user1the memory footprint of the structure is 16 bytes.

Now we analyze another user2type, its size is 24, but the order of the fields i and j is changed, it takes up 8 bytes, let's see why? Or first use our memory first rule analysis.

bxxx|xxxx|jjjj|jjjj|iiii

According to the alignment value and the size it occupies, the first field b offset is 0, occupies 1 byte, and is placed in the first position.

The second field j is int64, the alignment value and size are both 8, so start from offset 8, that is, the 9th to 16th bits are j, which means that the 2nd to 8th bits are filled by the compiler .

At present, the entire memory layout has been shifted by 16 bits, which is exactly a multiple of 4 of the alignment value of the third field i, so it can be arranged directly without filling, and the 17th to 20th bits are i.

 

Now that all the fields are aligned, the entire memory size is 1+7+8+4=20 bytes. We start to use the second rule of memory alignment, which is the alignment of the structure, through the default alignment value and the largest field Size, find the alignment value of the structure to 8.

Now our entire memory layout size is 20, not a multiple of 8, so we need to fill the memory to make up to a multiple of 8, the smallest is 24, so the entire memory layout after alignment is

bxxx|xxxx|jjjj|jjjj|iiii|xxxx

So this is why we finally got user2a size of 24. Based on the above method, we can get the memory layout of several other structs.

user3

iiii|bxxx|jjjj|jjjj

user4

iiii|xxxx|jjjj|jjjj|bxxx|xxxx

user5

jjjj|jjjj|bxxx|iiii

user6

jjjj|jjjj|iiii|bxxx

The answer is given above, you can refer to user1and user2try it when you push it. In the next article, we will introduce the operation of memory through unsafe.Pointer and the reading and writing of memory

Guess you like

Origin blog.csdn.net/qq_32907195/article/details/112464102