为什么要内存对齐?

一. 什么是内存对齐(Memory alignment),也叫字节对齐

在计算机中,内存是按 字节(byte, 1byte = 8bit) 划分的,而cpu在读取内存数据时,并不是一字节一字节读取的。实际上是按 块 来读取的。
块的大小可以是1,2,4,8,16等等,这块的大小也称为 内存访问粒度
内存对齐 是将特定的数据类型按照一定的规则摆放在内存上,以此 提高cpu访问内存的速度

看如下示例:

如上图,内存访问颗粒度为4,数据大小为4字节的场景:

场景1:做了内存对齐,index 2-3 补充空字节,这样 数据1,数据2 都刚好只存在一个内存块中,
读取数据时,一次就能将数据读取完毕

场景2:没有内存对齐,数据2 一部分存在内存块1中,一部分存在内存块2中。
读取数据2时,需要将块1的数据0-3读取出来,丢弃0-1字节数据,
再读取块2的数据4-7,丢弃6-7字节的数据,
再组合 2,3,4,5字节才能得到数据2

总结:很明显,场景2读取数据比场景1繁琐许多。
如果不做内存对齐,cpu在读取内存数据时,会增加许多耗时的动作。
而做了内存对齐后,虽然会产生一部分内存碎片,但极大提高了cpu访问内存数据的速度,属于空间换时间的做法

提高访问速度是内存对齐的原因之一,另外一个原因是某些平台(arm)不支持未内存对齐的访问


了解了内存对齐的原理以及优缺点后,下面将内存对齐在实际编程中的应用。
用下面的例子来说明:

// 写法1
type Part1 struct {
    a int8
    b int64
    c int16
}

// 写法2
type Part2 struct {
    a int8
    b int16
    c int64
}

func main() {
    var (
        pBool       bool       = false
        pByte       byte       = '0'
        pSting      string     = "hello"
        pInt8       int8       = 1
        pInt16      int16      = 1
        pInt32      int32      = 1
        pInt64      int64      = 1
        pfloat32    float32    = 1
        pfloat64    float64    = 1
        pComplex64  complex64  = 1
        pComplex128 complex128 = 1
        part1                  = Part1{}
        part2                  = Part2{}
    )

    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pBool, unsafe.Sizeof(pBool), unsafe.Alignof(pBool))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pByte, unsafe.Sizeof(pByte), unsafe.Alignof(pByte))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pSting, unsafe.Sizeof(pSting), unsafe.Alignof(pSting))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pInt8, unsafe.Sizeof(pInt8), unsafe.Alignof(pInt8))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pInt16, unsafe.Sizeof(pInt16), unsafe.Alignof(pInt16))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pInt32, unsafe.Sizeof(pInt32), unsafe.Alignof(pInt32))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pInt64, unsafe.Sizeof(pInt64), unsafe.Alignof(pInt64))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pfloat32, unsafe.Sizeof(pfloat32), unsafe.Alignof(pfloat32))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pfloat64, unsafe.Sizeof(pfloat64), unsafe.Alignof(pfloat64))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pComplex64, unsafe.Sizeof(pComplex64), unsafe.Alignof(pComplex64))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", pComplex128, unsafe.Sizeof(pComplex128), unsafe.Alignof(pComplex128))
    fmt.Println()
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", part1, unsafe.Sizeof(part1), unsafe.Alignof(part1))
    fmt.Printf("%10T 类型 所占字节数量 =%2d, 对齐保证系数=%2d\n", part2, unsafe.Sizeof(part2), unsafe.Alignof(part2))
}
----------------------------------------------------------------------------------------------------------
运行结果如下:
      bool 类型 所占字节数量 = 1, 对齐保证系数= 1
     uint8 类型 所占字节数量 = 1, 对齐保证系数= 1
    string 类型 所占字节数量 =16, 对齐保证系数= 8
      int8 类型 所占字节数量 = 1, 对齐保证系数= 1
     int16 类型 所占字节数量 = 2, 对齐保证系数= 2
     int32 类型 所占字节数量 = 4, 对齐保证系数= 4
     int64 类型 所占字节数量 = 8, 对齐保证系数= 8
   float32 类型 所占字节数量 = 4, 对齐保证系数= 4
   float64 类型 所占字节数量 = 8, 对齐保证系数= 8
 complex64 类型 所占字节数量 = 8, 对齐保证系数= 4
complex128 类型 所占字节数量 =16, 对齐保证系数= 8

main.Part1 类型 所占字节数量 =24, 对齐保证系数= 8
main.Part2 类型 所占字节数量 =16, 对齐保证系数= 8
----------------------------------------------------------------------------------------------------------
总结:
如果一个类型 'T' 的对齐保证系数为 'N(正整数)',则该类型 'T' 的内存占用字节数必须是 'N' 的整数倍

在golang语言中,可以用 'unsafe.Sizeof(T)' 来获取类型T的所占字节数量;
用 ' unsafe.Alignof(T)' 来获取类型T的对齐保证系数;
golang的内存对齐保证系数有1,2,4,8,最大为8

如上示例:结构体part1,part2 拥有相同的字段,而字段顺序不同。
结果占用的字节数 part1为24,part2为16。part2更省内存空间,为什么会这样?
原因是 part1 花费了更多的空间去对齐内存,如下图示:

如上图所示:
由于 类型 'T' 的内存字节数必须是 内存对齐系数'N' 的整数倍
part1 用了 13 个字节去对齐内存,1+2+8+13 = 24
part2 用了 5 个字节去对齐内存,1+2+8+5 = 16

因此,在实际工作中定义结构体时,应调整好字段类型的先后顺序,使得结构体占用更小的内存空间,
这样既减少了内存碎片,还提高了服务性能。

至于具体的优化规则我没能总结出来,从大往小排列或者从小往大排列都不能涵盖所有情况,
感觉要具体情况具体分析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容