Go Slice 最大容量大小是怎么来的

image

原文地址:Go Slice 最大容量大小是怎么来的

前言

在《深入理解 Go Slice》中,我们提到了 “根据其类型大小去获取能够申请的最大容量大小” 的处理逻辑。今天我们将更深入地去探究一下,底层到底做了什么东西,涉及什么知识点?

Go Slice 对应代码如下:

func makeslice(et *_type, len, cap int) slice {
    maxElements := maxSliceCap(et.size)
    if len < 0 || uintptr(len) > maxElements {
        ...
    }

    if cap < len || uintptr(cap) > maxElements {
        ...
    }

    p := mallocgc(et.size*uintptr(cap), et, true)
    return slice{p, len, cap}
}

根据想要追寻的逻辑,定位到了 maxSliceCap 方法,它会根据当前类型的大小获取到了所允许的最大容量大小来进行阈值判断,也就是安全检查。这是浅层的了解,我们继续追下去看看还做了些什么?

maxSliceCap

func maxSliceCap(elemsize uintptr) uintptr {
    if elemsize < uintptr(len(maxElems)) {
        return maxElems[elemsize]
    }
    return maxAlloc / elemsize
}

maxElems

var maxElems = [...]uintptr{
    ^uintptr(0),
    maxAlloc / 1, maxAlloc / 2, maxAlloc / 3, maxAlloc / 4,
    maxAlloc / 5, maxAlloc / 6, maxAlloc / 7, maxAlloc / 8,
    maxAlloc / 9, maxAlloc / 10, maxAlloc / 11, maxAlloc / 12,
    maxAlloc / 13, maxAlloc / 14, maxAlloc / 15, maxAlloc / 16,
    maxAlloc / 17, maxAlloc / 18, maxAlloc / 19, maxAlloc / 20,
    maxAlloc / 21, maxAlloc / 22, maxAlloc / 23, maxAlloc / 24,
    maxAlloc / 25, maxAlloc / 26, maxAlloc / 27, maxAlloc / 28,
    maxAlloc / 29, maxAlloc / 30, maxAlloc / 31, maxAlloc / 32,
}

maxElems 是包含一些预定义的切片最大容量值的查找表,索引是切片元素的类型大小。而值看起来 “奇奇怪怪” 不大眼熟,都是些什么呢。主要是以下三个核心点:

  • ^uintptr(0)
  • maxAlloc
  • maxAlloc / typeSize

^uintptr(0)

func main() {
    log.Printf("uintptr: %v\n", uintptr(0))
    log.Printf("^uintptr: %v\n", ^uintptr(0))
}

输出结果:

2019/01/05 17:51:52 uintptr: 0
2019/01/05 17:51:52 ^uintptr: 18446744073709551615

我们留意一下输出结果,比较神奇。取反之后为什么是 18446744073709551615 呢?

uintptr 是什么

在分析之前,我们要知道 uintptr 的本质(真面目),也就是它的类型是什么,如下:

type uintptr uintptr

uintptr 的类型是自定义类型,接着找它的真面目,如下:

#ifdef _64BIT
typedef uint64      uintptr;
#else
typedef uint32      uintptr;
#endif

通过对以上代码的分析,可得出以下结论:

  • 在 32 位系统下,uintptr 为 uint32 类型,占用大小为 4 个字节
  • 在 64 位系统下,uintptr 为 uint64 类型,占用大小为 8 个字节

^uintptr 做了什么事

^ 位运算符的作用是按位异或,如下:

func main() {
    log.Println(^1)
    log.Println(^uint64(0))
}

输出结果:

2019/01/05 20:44:49 -2
2019/01/05 20:44:49 18446744073709551615

接下来我们分析一下,这两段代码都做了什么事情呢

^1

二进制:0001

按位取反:1110

该数为有符号整数,最高位为符号位。低三位为表示数值。按位取反后为 1110,根据先前的说明,最高位为 1,因此表示为 -。取反后 110 对应十进制 -2

^uint64(0)

二进制:0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

按位取反:1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111

该数为无符号整数,该位取反后得到十进制值为:18446744073709551615

这个值是不是看起来很眼熟呢?没错,就是 ^uintptr(0) 的值。也印证了其底层数据类型为 uint64 的事实 (本机为 64 位)。同时它又代表如下:

  • math.MaxUint64
  • 2 的 64 次方减 1

maxAlloc

const GoarchMips = 0
const GoarchMipsle = 0
const GoarchWasm = 0

...

_64bit = 1 << (^uintptr(0) >> 63) / 2

heapAddrBits = (_64bit*(1-sys.GoarchWasm))*48 + (1-_64bit+sys.GoarchWasm)*(32-(sys.GoarchMips+sys.GoarchMipsle))

maxAlloc = (1 << heapAddrBits) - (1-_64bit)*1

maxAlloc允许用户分配的最大虚拟内存空间。在 64 位,理论上可分配最大 1 << heapAddrBits 字节。在 32 位,最大可分配小于 1 << 32 字节

在本文,仅需了解它承载的是什么就好了。具体的在以后内存管理的文章再讲述

注:该变量在 go 10.1 为 _MaxMem,go 11.4 已改为 maxAlloc。相关的 heapAddrBits 计算方式也有所改变

maxAlloc / typeSize

我们再次回顾 maxSliceCap 的逻辑代码,这次重点放在控制逻辑,如下:

// func makeslice
maxElements := maxSliceCap(et.size)

...

// func maxSliceCap
if elemsize < uintptr(len(maxElems)) {
    return maxElems[elemsize]
}
return maxAlloc / elemsize

通过这段代码和 Slice 上下文逻辑,可得知在想得到该类型的最大容量大小时。会根据对应的类型大小去查找表查找索引(索引为类型大小,摆放顺序是有考虑原因的)。“迫不得已的情况下” 才会手动的计算它的值,最终计算得到的内存字节大小都为该类型大小的整数倍

查找表的设置,更像是一个优化逻辑。减少常用的计算开销 :)

总结

通过本文的分析,可得出 Slice 所允许申请的最大容量大小,与当前值类型和当前平台位数有直接关系

最后

本文与《有点不安全却又一亮的 Go unsafe.Pointer》一同属于《深入理解 Go Slice》的关联章节。如果你在阅读源码时,对这些片段有疑惑。记得想尽办法深究下去,搞懂它

短短的一句话其实蕴含着不少知识点,希望这篇文章恰恰好可以帮你解惑

注:本文 Go 代码基于版本 11.4

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

推荐阅读更多精彩内容

  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,426评论 1 46
  • 1.编译程序(1)gcc xx.c,他会默认生成一个a.out的可执行文件,在a.out所在目录,执行./a.o...
    萌面大叔2阅读 1,279评论 0 1
  • 安装和环境配置 自行百度解决 go项目的目录结构 go命令依赖一个重要的环境变量:$GOPATH一般的,一个Go项...
    名字刚好七个字阅读 363评论 0 0
  • Go入门 Go介绍 部落图鉴之Go:爹好还这么努力? 环境配置 安装 下载源码编译安装 下载相应平台的安装包安装 ...
    齐天大圣李圣杰阅读 4,595评论 0 26
  • 深受各地朋友们的喜欢。最出名的还要数四川的麻辣火锅。火锅不仅仅是一道美食,吃火锅时,亲朋好友围着热气腾腾的火锅,把...
    小补文知阅读 277评论 0 0