它终于(几乎)在我们中间出现了!
终于,在听到 "泛型怎么了 "这个笑话多年之后,这个期待已久的功能将在1.18版本的语言中出现,计划在2022年3月发布。
在这篇文章中,我将做一个使用泛型的例子和一个小的基准来检查一个 "普通 "函数和另一个使用这个新功能的函数之间是否有任何性能差异。
为了证明这一点,我将使用库lo,它是最早使用泛型的库之一,最近因为实现了切片和地图的几个有价值的功能而变得很突出。
第一步是安装Go 1.18,在写这篇文章的时候,它是候选版本1。为此,我按照这个文档并使用了这些命令:
go install golang.org/dl/go1.18rc1@latest
go1.18rc1 download
这些命令在我的用户在macOS上的家中创建了sdk 目录。我们将使用这个目录来配置IDE以识别新的语言版本。我使用Jetbrains的Goland,所以我的设置看起来像这样:
除了创建sdk 目录外,上述命令还在我的macOS用户主页的go/bin 目录中创建了go1.18rc1 二进制文件。我们将使用这个二进制文件来运行测试。
eminetto@MacBook-Pro-da-Trybe ~/D/post-generics [1]> go1.18rc1 version
go version go1.18rc1 darwin/arm64
下一步是创建一个目录和一个main.go 文件:
mkdir post-generics
cd post-generics
go1.18rc1 mod init github.com/eminetto/post-generics
touch main.go
在main.go ,我写了以下代码:
package main
import (
"fmt"
)
func main() {
s := []string{"Samuel", "Marc", "Samuel"}
names := Uniq(s)
fmt.Println(names)
names = UniqGenerics(s)
fmt.Println(names)
i := []int{1, 20, 20, 10, 1}
ids := UniqGenerics(i)
fmt.Println(ids)
}
//from https://github.com/samber/lo/blob/master/slice.go
func UniqGenerics[T comparable](collection []T) []T {
result := make([]T, 0, len(collection))
seen := make(map[T]struct{}, len(collection))
for _, item := range collection {
if _, ok := seen[item]; ok {
continue
}
seen[item] = struct{}{}
result = append(result, item)
}
return result
}
func Uniq(collection []string) []string {
result := make([]string, 0, len(collection))
seen := make(map[string]struct{}, len(collection))
for _, item := range collection {
if _, ok := seen[item]; ok {
continue
}
seen[item] = struct{}{}
result = append(result, item)
}
return result
}
在main 函数中,可以看到泛型的最显著优势:我们使用相同的代码来删除字符串和整数片中的重复条目,而不改变函数。
当运行该代码时,我们可以看到结果:
eminetto@MacBook-Pro-da-Trybe ~/D/post-generics> go1.18rc1 run main.go
[Samuel Marc]
[Samuel Marc]
[1 20 10]
但性能如何呢?加入这个新功能后,我们是否错过了什么?为了尝试回答这个问题,我做了一个小小的基准测试。第一步是安装faker 包,以生成更多的测量数据:
go1.18rc1 get -u github.com/bxcodec/faker/v3
而main_test.go 的代码看起来像这样:
package main
import (
"github.com/bxcodec/faker/v3"
"testing"
)
var names []string
func BenchmarkMain(m *testing.B) {
for i := 0; i < 1000; i++ {
names = append(names, faker.FirstName())
}
}
func BenchmarkUniq(b *testing.B) {
_ = Uniq(names)
}
func BenchmarkGenericsUniq(b *testing.B) {
_ = UniqGenerics(names)
}
运行该基准,我们可以看到结果:
eminetto@MacBook-Pro-da-Trybe ~/D/post-generics> go1.18rc1 test -bench=. -benchtime=100x
goos: darwin
goarch: arm64
pkg: github.com/eminetto/post-generics
BenchmarkMain-8 100 482.1 ns/op
BenchmarkUniq-8 100 1225 ns/op
BenchmarkGenericsUniq-8 100 1142 ns/op
PASS
ok github.com/eminetto/post-generics 0.210s
我运行了几次基准测试,在大多数情况下,用Generics制作的版本性能更好,虽然差别不是很大。
结论
这篇文章不是一个有科学证明的基准的高级研究。它只是一个基本的测试,所以我建议你在做出最终决定之前,查阅更多的资料。不过,第一印象是,我们获得了一个基本的功能,而没有任何明显的性能损失。
我相信我将等待这个功能的最终版本更加成熟,可能是在1.18.x之后,将其投入生产。不过,我还是看到了未来几个月Go应用的重大演变。
兴奋点开始增加了 :)
