面试题:Go 1.15 中 var i interface「」 = 3

简介:   说明:题目是这样的  var in int = 3  // 以下有额外内存分配吗?  var i interface{} = i  在 Go 中,接口被实现为一对指针(请参阅 Russ Cox 的 Go 数据结构:接口[1]):指向有关类型信息的指针和指向值的指针。可以简单的表示为:  type iface struct {  tab *itab  data unsafe.Pointer  }  其中 tab 是指向类型信息的指针;data 是指向值的指针。因此,一般来说接口意味着必须在堆中动态分配该值。  然而,Go 1.15 发行说明[2]在 r

  说明:题目是这样的

  var in int = 3

  // 以下有额外内存分配吗?

  var i interface{} = i

  在 Go 中,接口被实现为一对指针(请参阅 Russ Cox 的 Go 数据结构:接口[1]):指向有关类型信息的指针和指向值的指针。可以简单的表示为:

  type iface struct {

  tab *itab

  data unsafe.Pointer

  }

  其中 tab 是指向类型信息的指针;data 是指向值的指针。因此,一般来说接口意味着必须在堆中动态分配该值。

  然而,Go 1.15 发行说明[2]在 runtime 部分中提到了一个有趣的改进:

  Converting a small integer value into an interface value no longer causes allocation.

  意思是说,将小整数转换为接口值不再需要进行内存分配。小整数是指 0 到 255 之间的数。

  我们实际简单测试一下。

  创建一个包 smallint,在包中创建文件 smallint.go,加上大专证书查询如下代码:

  package smallint

  func Convert(val int) []interface{} {

  var slice = make([]interface{}, 100)

  for i := 0; i < 100; i++ {

  slice[i] = val

  }

  return slice

  }

  为了更好的看到效果,函数中进行了 100 次 int 到 interface 的转换。写个基准测试 smallint_test.go:

  package smallint_test

  import (

  "testing"

  "test/smallint"

  )

  func BenchmarkConvert(b *testing.B) {

  for i := 0; i < b.N; i++ {

  result := smallint.Convert(12)

  _ = result

  }

  }

  分别使用 Go1.14 和 Go1.15 版本进行测试:

  $ go version

  go version go1.14.7 darwin/amd64

  $ go test -bench . -benchmem ./...

  goos: darwin

  goarch: amd64

  pkg: test/smallint

  BenchmarkConvert-8 569830 1966 ns/op 2592 B/op 101 allocs/op

  PASS

  ok test/smallint 1.647s

  $ go version

  go version go1.15 darwin/amd64

  $ go test -bench . -benchmem ./...

  goos: darwin

  goarch: amd64

  pkg: test/smallint

  BenchmarkConvert-8 1859451 655 ns/op 1792 B/op 1 allocs/op

  PASS

  ok test/smallint 2.178s

  接着讲 smallint_test.go 中调用 Convert 的参数由 12 改为 256,再次使用 Go1.15 运行,结果如下:

  $ go test -bench . -benchmem ./...

  goos: darwin

  goarch: amd64

  pkg: test/smallint

  BenchmarkConvert-8 551546 2049 ns/op 2592 B/op 101 allocs/op

  PASS

  ok test/smallint 1.502s

  证明了上面提到的优化点。

  那么,你想过它大概怎么实现的吗?因为上文提到,Go 中接口的实现,使用一个指针字段指向接口值。现在竟然不再额外进行内存分配,说明做了什么特殊的事情。

  其实答案非常简单。如果你对 Python、Java 等语言熟悉,应该知道大概如何实现的。Go 中如何做的,可以在 Go CL 216401[3] 中(合并到此提交[4]中了,GitHub 上更易于阅读)找到。具体来说就是 Go 中定义了一个特殊的静态数组,该数组由 256 个整数组成(0 到 255)。当必须分配内存以将整数存储在堆上,并将其转换为接口的一部分时,它首先检查是否它可以只返回指向数组中适当元素的指针。这种经常使用的值的静态分配,是一种很常见的优化手段。例如,Python 对小整数执行类似的操作,Java 也有常量池,进行类似的优化处理。

  实际上,Go 以前有一个优化,如果你将 0 转换为接口值,它将返回一个指向特殊静态零值的指针。这次新的 0-255 优化替代了该值。

  对具体实现细节感兴趣的,可以阅读下上文提到的提交。

目录
相关文章
|
3月前
|
Go
|
3月前
|
Java 编译器 Go
Go语言面试题1
【2月更文挑战第5天】Go语言面试题15个问题
62 2
|
5天前
|
JSON 人工智能 编译器
Go json 能否解码到一个 interface 类型的值
Go json 能否解码到一个 interface 类型的值
11 1
|
17天前
|
Go
Go - struct{} 实现 interface{}
Go - struct{} 实现 interface{}
30 9
|
1月前
|
算法 网络协议 Linux
|
2月前
|
负载均衡 算法 Java
【面试宝藏】Go语言运行时机制面试题
探索Go语言运行时,了解goroutine的轻量级并发及GMP模型,包括G(协程)、M(线程)和P(处理器)。GMP调度涉及Work Stealing和Hand Off机制,实现负载均衡。文章还讨论了从协作到基于信号的抢占式调度,以及GC的三色标记算法和写屏障技术。理解这些概念有助于优化Go程序性能。
45 4
|
2月前
|
存储 安全 Java
【面试宝藏】Go基础面试题其一
Go语言(Golang)结合C的性能和Python的易用性,具有简单语法、高效并发、自动垃圾回收等优点。它支持基本和派生数据类型,通过包进行代码管理。类型转换需显式进行,如将整数转为浮点数。Goroutine是轻量级线程,通过channel进行并发同步。Go接口可嵌套,同步锁用于控制并发访问。Channel提供类型安全的通信,注意避免死锁。Go Convey用于测试,`new`和`make`分别用于值类型和引用类型的初始化。了解这些,有助于更好地掌握Go语言。
28 2
|
2月前
|
存储 缓存 算法
【面试宝藏】Go并发编程面试题
探索Go语言并发编程,涉及Mutex、RWMutex、Cond、WaitGroup和原子操作。Mutex有正常和饥饿模式,允许可选自旋优化。RWMutex支持多个读取者并发,写入者独占。Cond提供goroutine间的同步,WaitGroup等待任务完成。原子操作保证多线程环境中的数据完整性,sync.Pool优化对象复用。了解这些,能提升并发性能。
31 2
|
3月前
|
JavaScript 前端开发
每日一道javascript面试题(六)有var和无var
每日一道javascript面试题(六)有var和无var
|
监控 Java Go
Go相关高频面试题
Go相关高频面试题
101 0