V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
honmaple
V2EX  ›  Go 编程语言

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

  •  
  •   honmaple · 2021-09-06 00:15:28 +08:00 · 2006 次点击
    这是一个创建于 960 天前的主题,其中的信息可能已经有所发展或是发生改变。

    测试代码:

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

    7 条回复    2021-09-06 21:01:12 +08:00
    hijoker
        1
    hijoker  
       2021-09-06 00:21:39 +08:00
    内存逃逸?
    mxT52CRuqR6o5
        2
    mxT52CRuqR6o5  
       2021-09-06 00:50:27 +08:00
    能享受到 cpu 的分支预测吧
    hjc4869
        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 。
    hjc4869
        4
    hjc4869  
       2021-09-06 01:32:09 +08:00
    另外可以反汇编看看编译器有没有做什么优化。
    p1gd0g
        5
    p1gd0g  
       2021-09-06 09:20:15 +08:00
    所有 1 allocs/op 的 bench 都是逃逸(编译器判断 new 的变量有外部引用)。
    不过没搞清楚你要比较的到底是哪两个。🤣
    sujin190
        6
    sujin190  
       2021-09-06 10:46:09 +08:00   ❤️ 1
    是内联优化,BenchmarkInterface 、BenchmarkInterface3 、BenchmarkInterface3 这三个都在内联优化完全被展开了,并没有两次函数调用的行为,所以性能很高,你这个测试是在 1.16 后面跑的吧,1.16 以前内联优化并没有去掉运行时类型检查,所以性能并没那么高
    honmaple
        7
    honmaple  
    OP
       2021-09-06 21:01:12 +08:00
    @hjc4869 @sujin190 非常感谢,我再研究一下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3398 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 11:21 · PVG 19:21 · LAX 04:21 · JFK 07:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.