测试代码:
package main
import (
"testing"
)
type InterfaceA interface {
Name() string
}
type InterfaceB interface {
Name() string
Add()
}
type A struct {
v int
}
func (*A) Name() string {
return "A"
}
func (a *A) Add() {
a.v += 1
}
type B struct {
A
}
func (*B) Name() string {
return "B"
}
func BenchmarkNormal(b *testing.B) {
switchFunc := func(v *A) {
v.Add()
}
for i := 0; i < b.N; i++ {
v := new(A)
switchFunc(v)
}
}
func BenchmarkInterface(b *testing.B) {
switchFunc := func(v interface{}) {
switch n := v.(type) {
case *A:
n.Add()
case *B:
n.Add()
}
}
for i := 0; i < b.N; i++ {
v := new(A)
switchFunc(v)
}
}
func BenchmarkInterface1(b *testing.B) {
switchFunc := func(v InterfaceA) {
switch v.Name() {
case "A":
v.(*A).Add()
case "B":
v.(*B).Add()
}
}
for i := 0; i < b.N; i++ {
v := new(A)
switchFunc(v)
}
}
func BenchmarkInterface2(b *testing.B) {
switchFunc := func(v interface{}) {
v.(InterfaceB).Add()
}
for i := 0; i < b.N; i++ {
v := new(A)
switchFunc(v)
}
}
func BenchmarkInterface3(b *testing.B) {
switchFunc := func(v InterfaceB) {
v.Add()
}
for i := 0; i < b.N; i++ {
v := new(A)
switchFunc(v)
}
}
func BenchmarkInterface4(b *testing.B) {
switchFunc := func(v InterfaceB) {
v.Name()
}
for i := 0; i < b.N; i++ {
v := new(A)
switchFunc(v)
}
}
func BenchmarkInterface5(b *testing.B) {
switchFunc := func(v InterfaceB) {
v.Name()
v.Add()
}
for i := 0; i < b.N; i++ {
v := new(A)
switchFunc(v)
}
}
测试结果:
└──╼ go test -test.bench=".*" . -benchmem
goos: darwin
goarch: amd64
pkg: org
cpu: Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz
BenchmarkNormal-8 1000000000 0.2542 ns/op 0 B/op 0 allocs/op
BenchmarkInterface-8 1000000000 0.8415 ns/op 0 B/op 0 allocs/op
BenchmarkInterface1-8 72095432 15.48 ns/op 8 B/op 1 allocs/op
BenchmarkInterface2-8 55137806 21.07 ns/op 8 B/op 1 allocs/op
BenchmarkInterface3-8 799164643 1.449 ns/op 0 B/op 0 allocs/op
BenchmarkInterface4-8 767046265 1.519 ns/op 0 B/op 0 allocs/op
BenchmarkInterface5-8 72075118 15.82 ns/op 8 B/op 1 allocs/op
PASS
ok org 7.915s
还有就是 interface 接口单独测试 Name()或者 Add()性能都差不多,可一旦两个一起调用,性能几乎减少了 10 倍,有大佬研究过这个问题吗?
1
hijoker 2021-09-06 00:21:39 +08:00
内存逃逸?
|
2
mxT52CRuqR6o5 2021-09-06 00:50:27 +08:00
能享受到 cpu 的分支预测吧
|
3
hjc4869 2021-09-06 01:29:15 +08:00 2
直接 switch 再 call method 是一次条件跳转再加上一次 call/ret,如果你的 code footprint 很小,这些都能存在 L1 BTB 里,BPU 不需要额外的周期就能访问到。
而 interface method 应该是间接跳转,BPU 需要查 ITA(iBTB)才能预测目标地址,这个操作比直接跳转更加昂贵,因为 ITA 通常比较大,延迟可能更接近 L2 BTB 。 |
4
hjc4869 2021-09-06 01:32:09 +08:00
另外可以反汇编看看编译器有没有做什么优化。
|
5
p1gd0g 2021-09-06 09:20:15 +08:00
所有 1 allocs/op 的 bench 都是逃逸(编译器判断 new 的变量有外部引用)。
不过没搞清楚你要比较的到底是哪两个。🤣 |
6
sujin190 2021-09-06 10:46:09 +08:00 1
是内联优化,BenchmarkInterface 、BenchmarkInterface3 、BenchmarkInterface3 这三个都在内联优化完全被展开了,并没有两次函数调用的行为,所以性能很高,你这个测试是在 1.16 后面跑的吧,1.16 以前内联优化并没有去掉运行时类型检查,所以性能并没那么高
|