清澈如初|1.15 中 var i interface「」 = 3,面试题:Go

说明:题目是这样的
varinint=3//以下有额外内存分配吗?variinterface{}=i在Go中 , 接口被实现为一对指针(请参阅RussCox的Go数据结构:接口[1]):指向有关类型信息的指针和指向值的指针 。 可以简单的表示为:
typeifacestruct{tab*itabdataunsafe.Pointer}其中tab是指向类型信息的指针;data是指向值的指针 。 因此 , 一般来说接口意味着必须在堆中动态分配该值 。
然而 , Go1.15发行说明[2]在runtime部分中提到了一个有趣的改进:
Convertingasmallintegervalueintoaninterfacevaluenolongercausesallocation.
【清澈如初|1.15 中 var i interface「」 = 3,面试题:Go】意思是说 , 将小整数转换为接口值不再需要进行内存分配 。 小整数是指0到255之间的数 。
我们实际简单测试一下 。
创建一个包smallint , 在包中创建文件smallint.go , 加上如下代码:
packagesmallintfuncConvert(valint)[]interface{}{varslice=make([]interface{},100)fori:=0;i为了更好的看到效果 , 函数中进行了100次int到interface的转换 。 写个基准测试smallint_test.go:
packagesmallint_testimport("testing""test/smallint")funcBenchmarkConvert(b*testing.B){fori:=0;i分别使用Go1.14和Go1.15版本进行测试:
$goversiongoversiongo1.14.7darwin/amd64$gotest-bench.-benchmem./...goos:darwingoarch:amd64pkg:test/smallintBenchmarkConvert-85698301966ns/op2592B/op101allocs/opPASSoktest/smallint1.647s$goversiongoversiongo1.15darwin/amd64$gotest-bench.-benchmem./...goos:darwingoarch:amd64pkg:test/smallintBenchmarkConvert-81859451655ns/op1792B/op1allocs/opPASSoktest/smallint2.178s接着讲smallint_test.go中调用Convert的参数由12改为256 , 再次使用Go1.15运行 , 结果如下:
$gotest-bench.-benchmem./...goos:darwingoarch:amd64pkg:test/smallintBenchmarkConvert-85515462049ns/op2592B/op101allocs/opPASSoktest/smallint1.502s证明了上面提到的优化点 。
那么 , 你想过它大概怎么实现的吗?因为上文提到 , Go中接口的实现 , 使用一个指针字段指向接口值 。 现在竟然不再额外进行内存分配 , 说明做了什么特殊的事情 。
其实答案非常简单 。 如果你对Python、Java等语言熟悉 , 应该知道大概如何实现的 。 Go中如何做的 , 可以在GoCL216401[3]中(合并到此提交[4]中了 , GitHub上更易于阅读)找到 。 具体来说就是Go中定义了一个特殊的静态数组 , 该数组由256个整数组成(0到255) 。 当必须分配内存以将整数存储在堆上 , 并将其转换为接口的一部分时 , 它首先检查是否它可以只返回指向数组中适当元素的指针 。 这种经常使用的值的静态分配 , 是一种很常见的优化手段 。 例如 , Python对小整数执行类似的操作 , Java也有常量池 , 进行类似的优化处理 。
实际上 , Go以前有一个优化 , 如果你将0转换为接口值 , 它将返回一个指向特殊静态零值的指针 。 这次新的0-255优化替代了该值 。
对具体实现细节感兴趣的 , 可以阅读下上文提到的提交 。
参考资料[1]
Go数据结构:接口:
[2]
Go1.15发行说明:
[3]
GoCL216401:+/216401
[4]
此提交: