Go1.18 已经发布了,泛型终于正式进入了 Go 语言。那泛型将如何影响性能?让我们通过对几个用例进行基准测试来弄清楚。
图片由 Percy Bolmér 拍摄。Takuya Ueda 的 Gopher,Renée French 的原创 Go Gopher(CC BY 3.0)
关于 Go1.18 新特性的文章有很多,讨论也不少。其中一个讨论是我想写的一个主题,即泛型对性能有什么影响?许多读者担心泛型会降低性能,但我的观点是泛型会提高性能。我的观点背后的原因是泛型将允许我们在运行时跳过类型转换、断言和反射,而是依赖编译器在编译时决定这个问题。
在我关于学习泛型[1]的文章中,我解释了泛型的用法,两个主要好处是减少了基于数据类型的重复函数并避免了interface{}
. 这些是我们将在本文中进行基准测试的用例,以发现更改的性能。
说明下:我不是基准测试专家。我只是一个基准测试菜鸟。在我看来,基准测试非常困难。
为了做出公平的基准测试,我们将为每个用例设置一个测试用例。这将意味着我们将
interface{}
进行基准测试我们将重用学习泛型[2]中的一些代码,在其中,我们有一个Subtract
函数可以减去三种Subtractable
数据类型之间的值。
我们将要确定哪些 Subtract 方法性能最好。可以在 Playground[3] 尝试一下。
package functions
// Subtract will subtract the second value from the first
func SubtractInt(a, b int) int {
return a - b
}
// Subtract64 will subtract the second value from the first
func SubtractInt64(a, b int) int {
return a - b
}
// SubtractFloat32 will subtract the second value from the first
func SubtractFloat32(a, b float32) float32 {
return a - b
}
// SubtractTypeSwitch is used to subtract using interfaces
func SubtractTypeSwitch(a, b interface{}) interface{} {
switch a.(type) {
case int:
return a.(int) - b.(int)
case int64:
return a.(int64) - b.(int64)
case float32:
return a.(float32) - b.(float32)
default:
return nil
}
}
// Subtract will subtract the second value from the first
func Subtract[V int64 | int | float32](a, b V "V int64 | int | float32") V {
return a - b
}
在那里,我们将开始对功能进行基准测试。它们应该相当容易理解,并且我们涵盖了减法、基于数据类型、类型切换和泛型的可能解决方案。
创建一个常规的测试文件,我们可以在其中存储基准,如果你熟悉 Go 中的基准,你可以阅读这里的教程[4]。
在基准测试的顶部,我将生成两个切片,一个随机整数切片,一个随机 float32 切片。这些随机切片将用作减法方法的输入参数。
然后我们创建一个b.Run
函数,它会一次触发一个函数,次数与我们设置为基准测试器的次数一样多,使用-benchtime
标志运行。对于这个基准测试,我将强制基准测试器运行每个函数 1000000000 次。如果你未指定运行函数的次数,则基准测试程序会在特定时间内尽可能多次地运行该函数。这将以它们没有运行相同数量的操作而告终,我希望它们这样做。
这就是我最终的基准测试的样子。
用于执行基准测试以确定泛型性能影响的测试文件。
package functions
import (
"math/rand"
"testing"
"time"
)
// Benchmark_Subtract is used to determine the most performant solution to subtraction
func Benchmark_Subtract(b *testing.B) {
// Create a slice of random numbers based on the number of iterations set
// to test the performance of the function
// Default iterations for me is 1000000000
// b.N is always 1 so we can use that to set the number of iterations
numbers := make([]int, 1000000001)
floatNumbers := make([]float32, 1000000001)
// Create a random seed
seed := rand.NewSource(time.Now().UnixNano())
// Give the seed to the random package
randomizer := rand.New(seed)
for i := 0; i < b.N; i++ {
// randomize numbers between 0-100
numbers[i] = randomizer.Intn(100)
floatNumbers[i] = float32(randomizer.Intn(100))
}
// run a benchmark for regular Ints
b.Run("SubtractInt", func(b *testing.B) {
for i := 0; i < b.N; i++ {
SubtractInt(numbers[i], numbers[i+1])
}
})
// run a benchmark for regular Floats
b.Run("SubtractFloat", func(b *testing.B) {
for i := 0; i < b.N; i++ {
SubtractFloat32(floatNumbers[i], floatNumbers[i+1])
}
})
// run a benchmark for TypeSwitched Ints
b.Run("Type_Subtraction_int", func(b *testing.B) {
for i := 0; i < b.N; i++ {
SubtractTypeSwitch(numbers[i], numbers[i+1])
}
})
// run a benchmark for TypeSwitched Floats
b.Run("Type_Subtraction_float", func(b *testing.B) {
for i := 0; i < b.N; i++ {
SubtractTypeSwitch(floatNumbers[i], floatNumbers[i+1])
}
})
// run a benchmark for Generic Ints
b.Run("Generic_Subtraction_int", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Subtract[int](numbers[i], numbers[i+1] "int")
}
})
// run a benchmark for Generic Floats
b.Run("Generic_Subtraction_float", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Subtract[float32](floatNumbers[i], floatNumbers[i+1] "float32")
}
})
// run a benchmark where generic type is infered
b.Run("Generic_Inferred_int", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Subtract(numbers[i], numbers[i+1])
}
})
}
在泛型基准测试中,基准测试将测试所有用例中int
和float32
的减法函数,我添加了第三个选项,推断数据类型。我还想确定如果我们让泛型函数将数据类型推断为int
会有怎样的表现.
要运行基准测试,请使用以下命令。请注意,该-count 5
参数用于将每个基准测试运行 5 次。这是因为如果你运行每个基准测试一次,你可能会得到不公平的结果。
go test -v -bench=Benchmark -benchtime=1000000000x -count 5
基准测试将与正在运行的函数的名称一起输出,我们可以使用它来识别不同的函数。第二个值是运行的操作数,在我们的例子中,我们将其设置为固定数字,因此所有行都应该显示相同。
第三个输出很有趣,它是每次操作的纳秒数 (ns/op)。这是显示函数平均速度的指标。
Go 测试工具的基准测试结果。
goos: windows
goarch: amd64
pkg: programmingpercy/benchgeneric
cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
Benchmark_Subtract
Benchmark_Subtract/SubtractInt
Benchmark_Subtract/SubtractInt-4 1000000000 0.9002 ns/op
Benchmark_Subtract/SubtractInt-4 1000000000 0.8904 ns/op
Benchmark_Subtract/SubtractInt-4 1000000000 0.8277 ns/op
Benchmark_Subtract/SubtractInt-4 1000000000 0.8290 ns/op
Benchmark_Subtract/SubtractInt-4 1000000000 0.8266 ns/op
Benchmark_Subtract/SubtractFloat
Benchmark_Subtract/SubtractFloat-4 1000000000 0.8591 ns/op
Benchmark_Subtract/SubtractFloat-4 1000000000 0.8033 ns/op
Benchmark_Subtract/SubtractFloat-4 1000000000 0.8108 ns/op
Benchmark_Subtract/SubtractFloat-4 1000000000 0.8168 ns/op
Benchmark_Subtract/SubtractFloat-4 1000000000 0.8040 ns/op
Benchmark_Subtract/Type_Subtraction_int
Benchmark_Subtract/Type_Subtraction_int-4 1000000000 1.597 ns/op
Benchmark_Subtract/Type_Subtraction_int-4 1000000000 1.711 ns/op
Benchmark_Subtract/Type_Subtraction_int-4 1000000000 1.607 ns/op
Benchmark_Subtract/Type_Subtraction_int-4 1000000000 1.570 ns/op
Benchmark_Subtract/Type_Subtraction_int-4 1000000000 1.588 ns/op
Benchmark_Subtract/Type_Subtraction_float
Benchmark_Subtract/Type_Subtraction_float-4 1000000000 1.320 ns/op
Benchmark_Subtract/Type_Subtraction_float-4 1000000000 1.311 ns/op
Benchmark_Subtract/Type_Subtraction_float-4 1000000000 1.323 ns/op
Benchmark_Subtract/Type_Subtraction_float-4 1000000000 1.424 ns/op
Benchmark_Subtract/Type_Subtraction_float-4 1000000000 1.321 ns/op
Benchmark_Subtract/Generic_Subtraction_int
Benchmark_Subtract/Generic_Subtraction_int-4 1000000000 0.8251 ns/op
Benchmark_Subtract/Generic_Subtraction_int-4 1000000000 0.8288 ns/op
Benchmark_Subtract/Generic_Subtraction_int-4 1000000000 0.8420 ns/op
Benchmark_Subtract/Generic_Subtraction_int-4 1000000000 0.8377 ns/op
Benchmark_Subtract/Generic_Subtraction_int-4 1000000000 0.8357 ns/op
Benchmark_Subtract/Generic_Subtraction_float
Benchmark_Subtract/Generic_Subtraction_float-4 1000000000 0.7952 ns/op
Benchmark_Subtract/Generic_Subtraction_float-4 1000000000 0.7987 ns/op
Benchmark_Subtract/Generic_Subtraction_float-4 1000000000 0.7877 ns/op
Benchmark_Subtract/Generic_Subtraction_float-4 1000000000 0.8037 ns/op
Benchmark_Subtract/Generic_Subtraction_float-4 1000000000 0.8283 ns/op
Benchmark_Subtract/Generic_Inferred_int
Benchmark_Subtract/Generic_Inferred_int-4 1000000000 0.8297 ns/op
Benchmark_Subtract/Generic_Inferred_int-4 1000000000 0.8283 ns/op
Benchmark_Subtract/Generic_Inferred_int-4 1000000000 0.8319 ns/op
Benchmark_Subtract/Generic_Inferred_int-4 1000000000 0.8366 ns/op
Benchmark_Subtract/Generic_Inferred_int-4 1000000000 0.8623 ns/op
PASS
ok programmingpercy/benchgeneric 37.114s
从结果中,我们可以确定类型断言函数要慢得多。它*慢了大约 50-90%*。在这个测试用例中,这似乎很荒谬,因为我们谈论的是半纳秒。
泛型函数的执行与特定于数据类型的函数大致相同,但速度略有提高。速度的这种小幅提高可能是由于我计算机上运行的其他软件。以我的心态,我认为编译器完成其工作后,泛型函数调用应该与常规函数调用相同。
我们可以在结果中看到的另一个要点是int
减法比float32
减法更耗时。常规int
减法的平均速度为 0,85478 ns/op,常规float32
减法的平均速度为0,8188 ns/op。这意味着在我的基准测试中,float32
减法大约快 5% 。
因此,该基准的关键要点是:
Float32
减法比int
快让我们比较一个真实的场景。在用例中,我们有两个有 Move 的结构Person
,Car
。这两个结构都有一个Move
接受距离的函数,但是,Person 距离被传递为float32
而 Car 接受一个int
。
这两种结构都在同一个工作流中处理,因此我们希望在同一个函数中处理它们。
对此的泛型解决方案是创建泛型结构,我们可以在其中定义要在创建时使用的数据类型。接口解决方案是接受结构作为输入,并对它们进行类型断言并转换正确的数据类型。我们不能为它们提供共享接口,因为数据类型不一样。
在代码示例中,有一个泛型和旧类型断言解决方案的实现,类型断言带有后缀Regular
,因此我们可以更容易地知道什么与什么解决方案相关。
在具有不同数据类型的Cars和Persons 上执行 Move 的泛型解决方案。
package benchmarking
// Subtractable is a type constraint that defines subtractable datatypes to be used in generic functions
type Subtractable interface {
int | int64 | float32
}
// Moveable is the interace for moving a Entity
type Moveable[S Subtractable] interface {
Move(S)
}
// Car is a Generic Struct with the type S to be defined
type Car[S Subtractable] struct {
Name string
DistanceMoved S
}
// Person is a Generic Struct with the type S to be defined
type Person[S Subtractable] struct {
Name string
DistanceMoved S
}
// Person is a struct that accepts a type definition at initialization
// And uses that Type as the data type for meters as input
func (p *Person[S]) Move(meters S) {
p.DistanceMoved += meters
}
func (c *Car[S]) Move(meters S) {
c.DistanceMoved += meters
}
// Move is a generic function that takes in a Generic Moveable and moves it
func Move[S Subtractable, V Moveable[S]](v V, meters S "S Subtractable, V Moveable[S]") {
v.Move(meters)
}
类型断言方案的 Move:
package benchmarking
// Below is the Type casting based Solution
//
type CarRegular struct {
Name string
DistanceMoved int
}
type PersonRegular struct {
Name string
DistanceMoved float32
}
func (p *PersonRegular) Move(meters float32) {
p.DistanceMoved += meters
}
func (c *CarRegular) Move(meters int) {
c.DistanceMoved += meters
}
func MoveRegular(v interface{}, distance float32) {
switch v.(type) {
case *PersonRegular:
v.(*PersonRegular).Move(distance)
case *CarRegular:
v.(*CarRegular).Move(int(distance))
default:
// Handle Unsupported types, not needed by Generic solution as Compiler does this for you
}
}
现在我们已经有了解决方案,是时候开始基准测试了。我将在基准测试之前创建 Persons 和 Cars,我们将测量Move
和MoveRegular
的性能。
package benchmarking
import "testing"
func Benchmark_Structures(b *testing.B) {
// Init the structs
p := &Person[float32]{Name: "John"}
c := &Car[int]{Name: "Ferrari"}
pRegular := &PersonRegular{Name: "John"}
cRegular := &CarRegular{Name: "Ferrari"}
// Run the test
b.Run("Person_Generic_Move", func(b *testing.B) {
for i := 0; i < b.N; i++ {
// generic will try to use float64 if we dont tell it is a float32
Move[float32](p, 10.2 "float32")
}
})
b.Run("Car_Generic_Move", func(b *testing.B) {
for i := 0; i < b.N; i++ {
Move(c, 10)
}
})
b.Run("Person_Regular_Move", func(b *testing.B) {
for i := 0; i < b.N; i++ {
MoveRegular(pRegular, 10.2)
}
})
b.Run("Car_Regular_Move", func(b *testing.B) {
for i := 0; i < b.N; i++ {
MoveRegular(cRegular, 10)
}
})
}
我使用以下命令运行测试
go test -v -bench=Benchmark_Structures -benchtime=1000000000x -count 5
运行基准测试的结果
goos: windows
goarch: amd64
pkg: programmingpercy/benchgeneric
cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
Benchmark_Structures
Benchmark_Structures/Person_Generic_Move
Benchmark_Structures/Person_Generic_Move-4 1000000000 4.690 ns/op
Benchmark_Structures/Person_Generic_Move-4 1000000000 4.668 ns/op
Benchmark_Structures/Person_Generic_Move-4 1000000000 4.727 ns/op
Benchmark_Structures/Person_Generic_Move-4 1000000000 4.664 ns/op
Benchmark_Structures/Person_Generic_Move-4 1000000000 4.699 ns/op
Benchmark_Structures/Car_Generic_Move
Benchmark_Structures/Car_Generic_Move-4 1000000000 3.176 ns/op
Benchmark_Structures/Car_Generic_Move-4 1000000000 3.188 ns/op
Benchmark_Structures/Car_Generic_Move-4 1000000000 3.296 ns/op
Benchmark_Structures/Car_Generic_Move-4 1000000000 3.144 ns/op
Benchmark_Structures/Car_Generic_Move-4 1000000000 3.156 ns/op
Benchmark_Structures/Person_Regular_Move
Benchmark_Structures/Person_Regular_Move-4 1000000000 4.694 ns/op
Benchmark_Structures/Person_Regular_Move-4 1000000000 4.634 ns/op
Benchmark_Structures/Person_Regular_Move-4 1000000000 4.677 ns/op
Benchmark_Structures/Person_Regular_Move-4 1000000000 4.660 ns/op
Benchmark_Structures/Person_Regular_Move-4 1000000000 4.626 ns/op
Benchmark_Structures/Car_Regular_Move
Benchmark_Structures/Car_Regular_Move-4 1000000000 2.560 ns/op
Benchmark_Structures/Car_Regular_Move-4 1000000000 2.555 ns/op
Benchmark_Structures/Car_Regular_Move-4 1000000000 2.553 ns/op
Benchmark_Structures/Car_Regular_Move-4 1000000000 2.579 ns/op
Benchmark_Structures/Car_Regular_Move-4 1000000000 2.560 ns/op
PASS
ok programmingpercy/benchgeneric 75.830s
看到类型断言解决方案比泛型解决方案更快,我有点惊讶。我确保多次运行的基准测试,它不是偶然的。
我们可以从基准中看到,基于 Cars 的 Int
解决方案都比基于 Person 的 float32
的更快。
Person
move 方法具有相同的性能,无论是泛型解决方案还是常规解决方案。但是,你可以看到 Cars 的不同之处,类型断言的 Cars 是最快的。类型断言执行比泛型快 20%。
因此,该基准的关键要点如下。
Float32
加法比 int
慢所以,我们现在已经测试了一些我可以看到泛型有用的用例。
老实说,我确实希望第二个基准也能证明泛型更快。这将进一步证明我的说法,即泛型由于是在编译时而不是运行时决定的,因此性能更高。
通过使用泛型或特定于数据类型的函数,我们可以在第一个用例中看到相当大的性能提升。我知道几纳秒可能看起来很荒谬,但是在某些用例中,这些类型的极端优化很重要。我曾经做过一个高性能的网络嗅探器,它必须实时处理大量的网络数据。编写这样的软件将需要所有的优化。
我们已经看到,选择正确的数据类型会对性能产生很大影响。但是,我认为我们可以说,那些表示担心泛型会拖慢软件速度的读者可以冷静下来。从好的方面来说,我看到泛型解决方案允许我们更轻松地交换数据类型,从而提高性能。
另一方面,Go 中的类型断言和类型转换似乎具有超强的性能。
正如我们所看到的,许多因素都会对结果产生影响,例如使用的算术运算符[5]、数据类型等。在我的基准测试中可能会出现我不知道的错误。
[1]学习泛型: https://programmingpercy.tech/blog/learning-generics-in-go
[2]学习泛型: https://programmingpercy.tech/blog/learning-generics-in-go
[3]Playground: https://go.dev/play/p/BLU8pHOzmvS
[4]教程: https://betterprogramming.pub/we-measure-the-power-of-cars-computers-and-cellphones-but-what-about-code-91ed5583f298
[5]算术运算符: https://www.techopedia.com/definition/25582/arithmetic-operator
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/6fTcw0wyaDS5d0fbFbZmOQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。