在Go中测试泛型的超详细指南(附代码)

120 阅读3分钟

它终于(几乎)在我们中间出现了!

终于,在听到 "泛型怎么了 "这个笑话多年之后,这个期待已久的功能将在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,所以我的设置看起来像这样:

generics_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应用的重大演变。

兴奋点开始增加了 :)