在Go中过滤范型集合:性能回顾

devtools/2024/10/18 18:15:20/

在一个真实的 Golang 场景中使用泛型,同时寻找与 Stream filter(Predicate<? super T> predicate)和 Python list comprehension 等同的函数。我没有依赖现有的包,而是选择自己写一个过滤函数,以达到学习的目的

func filterStrings(collection []string, test func(string) bool) (f []string) {for _, s := range collection {if test(s) {f = append(f, s)}}return
}

然而,这只适用于字符串。如果我需要过滤一个整数的集合,那么我就需要另一个极其相似的函数。这对于一个范型函数来说似乎是一个完美的选择。

func filter[T any](collection []T, test func(T) bool) (f []T) {for _, e := range collection {if test(e) {f = append(f, e)}}return
}

让我们来分析类型化和范型版本之间的(少数)差异:

函数名后面是一个范型T的定义。
T被定义为任何类型。
输入 slice 中元素的类型从字符串变成了T
输入、输出的 clice 类型也从字符串变成了T

不多说了,让我们来写一些单元测试。首先,我需要一个随机集合(在我的例子中,是字符串)的生成器。

func generateStringCollection(size, strLen int) []string {var collection []stringfor i := 0; i < size; i++ {collection = append(collection, strings.Repeat(fmt.Sprintf("%c", rune('A'+(i%10))), strLen))}return collection
}

现在我可以写一个测试用例,判断 filterStrings 函数的输出与我的过滤范型器的输出相同。

func TestFilter(t *testing.T) {c := generateStringCollection(1000, 3)t.Run("the output of the typed and generic functions is the same", func(t *testing.T) {predicate := func(s string) bool { return s == "AAA" }filteredStrings := filterStrings(c, predicate)filteredElements := filter(c, predicate)if !reflect.DeepEqual(filteredStrings, filteredElements) {t.Errorf("the output of the two functions is not the same")}})
}
=== RUN   TestFilter
=== RUN   TestFilter/the_output_of_the_typed_and_generic_functions_is_the_same
--- PASS: TestFilter (0.00s)--- PASS: TestFilter/the_output_of_the_typed_and_generic_functions_is_the_same (0.00s)
PASS

考虑新函数在处理大的 slice 时的性能问题。我怎样才能确保它在这种情况下也能表现良好?

答案是:基准测试。用Go编写基准测试与单元测试很相似。

const (CollectionSize = 1000ElementSize    = 3
)func BenchmarkFilter_Typed_Copying(b *testing.B) {c := generateStringCollection(CollectionSize, ElementSize)b.Run("Equals to AAA", func(b *testing.B) {for i := 0; i < b.N; i++ {filterStrings(c, func(s string) bool { return s == "AAA" })}})
}func BenchmarkFilter_Generics_Copying(b *testing.B) {c := generateStringCollection(CollectionSize, ElementSize)b.Run("Equals to AAA", func(b *testing.B) {for i := 0; i < b.N; i++ {filter(c, func(s string) bool { return s == "AAA" })}})
}
go test -bench=. -count=10 -benchmem
goos: darwin
goarch: arm64
pkg: github.com/timliudream/go-test/generic_test
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             718408              1641 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             718148              1640 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             732939              1655 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             723036              1639 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             699075              1639 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             707232              1643 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             616422              1652 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             730702              1649 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             691488              1700 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             717043              1646 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          428851              2754 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          428437              2762 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          430444              2800 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          429314              2757 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          430938              2754 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          429795              2754 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          426714              2755 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          418152              2755 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          431739              2761 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          412221              2755 ns/op            4080 B/op          8 allocs/op
PASS
ok      github.com/timliudream/go-test/generic_test     25.005s

我对这个结果并不满意。看起来我用可读性换取了性能。
此外,我对分配的数量也有点担心。你注意到我的测试名称中的_Copying后缀了吗?那是因为我的两个过滤函数都是将过滤后的项目从输入集合复制到输出集合中。为什么我必须为这样一个简单的任务占用内存?
到最后,我需要做的是过滤原始的收集。我决定先解决这个问题。

func filterInPlace[T any](collection []T, test func(T) bool) []T {var position, size = 0, len(collection)for i := 0; i < size; i++ {if test(collection[i]) {collection[position] = collection[i]position++}}return collection[:position]
}

我不是把过滤后的结果写到一个新的集合中,然后再返回,而是把结果写回原来的集合中,并保留一个额外的索引,以便在过滤后的项目数上 "切 "出一个片断。

我的单元测试仍然通过,在改变了下面这行之后。

filteredStrings := filterStrings(c, predicate)
//filteredElements := filter(c, predicate)filteredElements := filterInPlace(c, predicate) // new memory-savvy function

再添加一个 bench 方法:

func BenchmarkFilter_Generics_InPlace(b *testing.B) {c := generateStringCollection(CollectionSize, 3)b.Run("Equals to AAA", func(b *testing.B) {for i := 0; i < b.N; i++ {filterInPlace(c, func(s string) bool { return s == "AAA" })}})
}

结果是出色的。

go test -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: github.com/timliudream/go-test/generic_test
BenchmarkFilter_Typed_Copying/Equals_to_AAA-8             713928              1642 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Copying/Equals_to_AAA-8          426055              2787 ns/op            4080 B/op          8 allocs/op
BenchmarkFilter_Generics_Inplace/Equals_to_AAA-8          483994              2467 ns/op               0 B/op          0 allocs/op
PASS
ok      github.com/timliudream/go-test/generic_test     4.925s

不仅内存分配归零,而且性能也明显提高。


http://www.ppmy.cn/devtools/97077.html

相关文章

深入理解 Go 并发原语

1. goroutine 基础知识 1.1 进程 进程&#xff08;process) 是一个程序的实例&#xff0c;具有某些专用资源&#xff0c;如内存空间、处理器时间、文件句柄&#xff08;例如&#xff0c;Linux 中的大多数进程都有 stdin、stdout 和 stderr) 和至少一个线程。我们称其为实例&am…

基于FPGA的ASIC prototype验证

在当今快速发展的电子设计自动化&#xff08;EDA&#xff09;领域&#xff0c;专用集成电路&#xff08;ASIC&#xff09;的开发因其高性能、低功耗和定制化的特点而备受青睐。然而&#xff0c;ASIC的设计和制造过程不仅成本高昂&#xff0c;而且周期漫长&#xff0c;一旦进入生…

物联网(IoT)详解

物联网&#xff08;IoT&#xff09;详解 1. IoT定义简介2. IoT工作原理3. IoT关键技术4. 物联网与互联网区别5. IoT使用场景6. 开源物联网平台7. 参考资料 1. IoT定义简介 首先第一个问题&#xff0c;什么是物联网&#xff08;IoT&#xff09;? 物联网&#xff08;英文&#…

文件长度超出芯片容量, 超出部份将被忽略!ch341a编程器报错解决方法

出现这个错误提示&#xff0c;说明你正在刷的是华硕主板的cap格式BIOS文件。 编程器不支持这种文件&#xff0c;需要转换成编程器专用版本BIOS文件。 华硕cap格式BIOS转编程器bios文件&#xff0c;转换工具下载地址&#xff1a;https://download.csdn.net/download/baiseled/88…

【Git】克隆并push远程指定分支

克隆并push远程指定分支 可通过 git branch --list查询分支 克隆指定分支git clone -b <指定分支名> <远程仓库地址> 切换分支git checkout <指定分支名> 上传文件: git add .git commit -m "提交信息"git push -u origin <指定分支名&…

斯坦福UE4 C++课学习补充22:AI行为树-寻路入门

文章目录 一、创建敌对小兵二、寻路行为树三、会与角色保持距离的小兵四、更智能的小兵 一、创建敌对小兵 创建SAICharacter&#xff08;角色&#xff09;和SAIController两个类&#xff08;行为树&#xff09;&#xff0c;然后在UE中创建蓝图类。寻路系统&#xff1a;用于为人…

Linux时间日期指令

目录 date指令-显示当前日期 基本语法 应用实例 date指令- 设置日期 基本语法 应用实例 cal指令 基本语法 应用实例 date指令-显示当前日期 基本语法 中间的减号是分隔符。 应用实例 也可以不写减号&#xff0c;显示的时候就不会显示减号 。 date指令- 设置日期 基…

在已经装过Tomcat机子运行war包

1 检查防火墙&#xff0c;验证是否装有jdk,是否配置有JAVA_HOME: ls /usr/apache-tomcat-9.0.52/webapps/ROOT rm -rf /usr/apache-tomcat-9.0.52/webapps/ROOT* ls /usr/apache-tomcat-9.0.52/webapps/ROOT cd /usr/apache-tomcat-9.0.52/webapps/ROOT ls 把war包拉到ROOT…