go 内存对齐


文章来源

内存对齐是指首地址对齐,而不是说每个变量大小对齐

为减少内存对齐带来的 padding 浪费. 构建结构体时,先写大的成员

操作系统并非一个字节一个字节访问内存,而是按2, 4, 8这样的字长来k访问。
因此,当 CPU 从存储器读数据到寄存器,或者从寄存器写数据到存储器,IO 的数据长度通常是字长。 如:
* 32 位系统访问粒度是 4 字节(bytes),
* 64 位系统的是 8 字节。

当被访问的数据长度为 n 字节且该数据地址为n字节对齐,那么操作系统就可以高效地一次定位到数据,无需多次读取、处理对齐运算等额外操作。
数据结构应该尽可能地在自然边界上对齐。如果访问未对齐的内存,CPU 需要做两次内存访问。

没有内存对齐,cpu读取两次,然后在寄存器中合并.

平台对齐系数

计算机在加载和保存数据时,如果内存地址合理地对齐的将会更有效率。
* 2字节大小的int16类型的变量地址应该是偶数,
* 一个4字节大小的rune类型变量的地址应该是4的倍数,
* 一个8字节大小的float64、uint64或64-bit指针类型变量的地址应该是8字节对齐的。
* 但是对于再大的地址对齐倍数则是不需要的,即使是complex128等较大的数据类型最多也只是8字节对齐。

unsafe.Sizeof

unsafe.Sizeof函数返回操作数在内存中的字节大小
* 参数可以是任意类型的表达式
* 并且它并不会对表达式进行求值。

unsafe.Alignof

unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数.
和 Sizeof 类似, Alignof 也是返回一个常量表达式, 对应一个常量.
通常情况下布尔和数字类型需要对齐到它们本身的大小(最多8个字节), 其它的类型对齐到机器字大小.

unsafe.Offsetof

unsafe.Offsetof 函数的参数必须是一个字段 x.f, 然后返回 f 字段相对于 x 起始地址的偏移量, 包括可能的空洞

go string 类型 大小 16.

  • go语言的string是一种数据类型,这个数据类型占用16字节空间,
  • 前8字节是一个指针,指向字符串值的地址,后八个字节是一个整数,标识字符串的长度;
  • 注意go语言的字符串内部并不以’\0’作为结尾,而是通过一个长度域来表示字符串的长度。

go 数组类型, 测试过 []int8 和 []int 都是 24

  • 在Go语言中数组是一个值类型value type
  • 是真真实实的数组,而不是一个指向数组内存起始位置的指针,也不能和同类型的指针进行转化,这一点严重不同于C语言
  • 所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作
    (这让我想起, channel 传递对象的时候,也是值拷贝. 需要避免拷贝巨大对象)
  • 如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。

demo

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

type empty_struct struct {
}

type empty_struct_with_name1 struct {
    flag    bool
    name    string
    empty_s empty_struct
}

type empty_struct_with_name2 struct {
    a bool
    b empty_struct
    c string
    d empty_struct
}

func main() {
    s_val := "test"
    int_arr_val := []int{0, 1, 2, 3, 4, 5}
    var empty_s empty_struct

    fmt.Println("type:\t", reflect.TypeOf(s_val), "\tsize of:\t", unsafe.Sizeof(s_val), "\talign of:\t", unsafe.Alignof(s_val), "\tval:\t", s_val)
    fmt.Println("type:\t", reflect.TypeOf(int_arr_val), "\tsize of:\t", unsafe.Sizeof(int_arr_val), "\talign of:\t", unsafe.Alignof(int_arr_val), "\tval:\t", int_arr_val)

  //空 struct 正常size of 是 0
    fmt.Println("type:\t", reflect.TypeOf(empty_s), "\tsize of:\t", unsafe.Sizeof(empty_s), "\talign of:\t", unsafe.Alignof(empty_s), "\tval:\t", empty_s)

  // 空 struct 由于需要内存对齐,占了 8 字节空间
    var empty_s_name1 empty_struct_with_name1
    fmt.Println("type:\t", reflect.TypeOf(empty_s_name1), "\tsize of:\t", unsafe.Sizeof(empty_s_name1), "\talign of:\t", unsafe.Alignof(empty_s_name1), "\tval:\t", empty_s_name1)

  //第一个空 struct 由于需要内存对齐和 bool a 一起共同占了 8 字节空间
  //第二个空 struct 由于需要内存对齐和 独自占了 8 字节空间
    var empty_s_name2 empty_struct_with_name2
    fmt.Println("type:\t", reflect.TypeOf(empty_s_name2), "\tsize of:\t", unsafe.Sizeof(empty_s_name2), "\talign of:\t", unsafe.Alignof(empty_s_name2), "\tval:\t", empty_s_name2)
    fmt.Println("struce empty_s_name2 size of:\t", unsafe.Sizeof(empty_s_name2),
        "\ta offset of:\t", unsafe.Offsetof(empty_s_name2.a), "\tb offset of:\t", unsafe.Offsetof(empty_s_name2.b),
        "\tc offset of:\t", unsafe.Offsetof(empty_s_name2.c), "\td offset of:\t", unsafe.Offsetof(empty_s_name2.d))

    /*
        output :
        type:    string     size of:     16     align of:    8  val:     test
        type:    []int  size of:     24     align of:    8  val:     [0 1 2 3 4 5]
        type:    main.empty_struct  size of:     0  align of:    1  val:     {}
        type:    main.empty_struct_with_name1   size of:     32     align of:    8  val:     {false  {}}
        type:    main.empty_struct_with_name2   size of:     32     align of:    8  val:     {false {}  {}}
        struce empty_s_name2 size of:    32     a offset:    0  b offset of:     1  c offset of:     8  d offset of:     24
    */
}

,

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: