Go 的 interface.Method 性能为什么会比 swith type 差?

2021-09-06 00:15:28 +08:00
 honmaple

测试代码:

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 倍,有大佬研究过这个问题吗?

2010 次点击
所在节点    Go 编程语言
7 条回复
hijoker
2021-09-06 00:21:39 +08:00
内存逃逸?
mxT52CRuqR6o5
2021-09-06 00:50:27 +08:00
能享受到 cpu 的分支预测吧
hjc4869
2021-09-06 01:29:15 +08:00
直接 switch 再 call method 是一次条件跳转再加上一次 call/ret,如果你的 code footprint 很小,这些都能存在 L1 BTB 里,BPU 不需要额外的周期就能访问到。
而 interface method 应该是间接跳转,BPU 需要查 ITA(iBTB)才能预测目标地址,这个操作比直接跳转更加昂贵,因为 ITA 通常比较大,延迟可能更接近 L2 BTB 。
hjc4869
2021-09-06 01:32:09 +08:00
另外可以反汇编看看编译器有没有做什么优化。
p1gd0g
2021-09-06 09:20:15 +08:00
所有 1 allocs/op 的 bench 都是逃逸(编译器判断 new 的变量有外部引用)。
不过没搞清楚你要比较的到底是哪两个。🤣
sujin190
2021-09-06 10:46:09 +08:00
是内联优化,BenchmarkInterface 、BenchmarkInterface3 、BenchmarkInterface3 这三个都在内联优化完全被展开了,并没有两次函数调用的行为,所以性能很高,你这个测试是在 1.16 后面跑的吧,1.16 以前内联优化并没有去掉运行时类型检查,所以性能并没那么高
honmaple
2021-09-06 21:01:12 +08:00
@hjc4869 @sujin190 非常感谢,我再研究一下

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/800041

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX