Go中过滤范型集合性能示例详解
作者:洛天枫 发布时间:2024-04-27 15:27:35
最近,我有机会在一个真实的 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 []string
for 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 = 1000
ElementSize = 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
不仅内存分配归零,而且性能也明显提高。
来源:https://juejin.cn/post/7207091794357190693


猜你喜欢
- Request 对象在 scrapy 中 Request 对象代表着请求,即向服务器发送数据,该对象的构造函数原型如下所示:def __in
- 需求:NMS中的IOU相关,是选择一个最大或者可信度最高的框框保留。而我们现在试需要将重叠框框合并为一个大的框框,所以不能直接用上面的。并且
- 相信很多小伙伴想着自己的移动端项目能够自动转换为rem,这才符合前端的潮流,如果用自己手写或者编辑器插件来改动十分不方便还容易出错,我在网上
- 自动依赖注入在 AbpBase.Web 的 AbpBaseWebModule 中,添加一个函数:此函
- 本文列举了兼容 IE 和 FF 的换行 CSS 推荐样式,详细介绍了word-wrap同word-break的区别。兼容 IE 和 FF 的
- 数模比赛中,常常需要对数据进行处理和分析,但
- 前言我原本是学C\C++,这是本人第一篇关于python的文章。请多多关照!对于python为什么要打包成exe文件,是因为传输源文件以及源
- 问题参考自:https://www.zhihu.com/question/440066129/answer/1685329456 ,mysq
- 如何在pytorch中指定CPU和GPU进行训练,以及cpu和gpu之间切换由CPU切换到GPU,要修改的几个地方:网络模型、损失函数、数据
- 方法一:单表导入(1)打开"SQL Server 外围应用配置器"-->"功能的外围应用配置器"
- 数据库连接:<% set conn=server.createobject("adodb.connection&q
- win2000注册表程序 regedt32.exe下面是解决IIS出现Active Server Pages错误&
- 一、问题描述使用idea操作代码进行VCS很是方便,向github进行push和pull操作非常方便,但是,最近频繁提示需要重新输入用户名和
- 安装时建议你为MySQL管理创建一个用户和组。由该组用户运行mysql服务器并执行管理任务。(也可以以root身份运行服务器,但是不推荐)第
- 写在前面:在做表格的时候,难免会碰到做统计的时候。由于在项目中涉及到做统计的功能比较简单,之后也就没有过多的去研究更复杂的,这里简单记录下。
- 项目应用中,曾有以下一个场景:接口中要求发送一个int类型的流水号,由于多线程模式,如果用时间戳,可能会有重复的情况(当然概率很小)。所以想
- # 写在前面,这篇文章的原创作者是Charles我只是在他这个程序的基础上边进行加工,另外有一些自己的改造# 并都附上了注释和我自己的理解,
- 我见到有的网站好像可以把数据库的记录读到表格里去,是这样的吗?如何做到的?可能是这样的,因为我们确实能把数据库里的记录用表格来储存,看看下面
- objectobject 是 Python 为所有对象提供的父类,默认提供一些内置的属性、方法;可以使用 dir 方法查看新式类以 obje
- 数据库中内置函数的使用该篇主要介绍数据库中内置函数的使用,主要有日期函数,字符串函数,数学函数。(一)日期函数select current_