Golang泛型性能基准测试与比较

Golang泛型性能基准测试与比较 在不同的平台和架构上获得了相似的结果。 这不是语言的问题,对吗?

type Signed interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

type Getter interface {
    Get(int) int
}

type GetObj struct{}

func (o *GetObj) Get(i int) int {
    return i * i
}

type GenGetter[T Signed] interface {
    Get(T) T
}

type GenGetObj[T Signed] struct{}

func (o *GenGetObj[T]) Get(i T) T {
    return i * i
}

func reg(obj *GetObj, i int) int {
    return obj.Get(i)
}

func iface(obj Getter, i int) int {
    return obj.Get(i)
}

func gen[T Signed](obj GenGetter[T], i T) T {
    return obj.Get(i)
}

func BenchmarkReg(b *testing.B) {
    var obj = &GetObj{}
    var i = 0
    for b.Loop() {
        reg(obj, i)
        i++
    }
}

func BenchmarkIface(b *testing.B) {
    var obj = &GetObj{}
    var i = 0
    for b.Loop() {
        iface(obj, i)
        i++
    }
}

func BenchmarkGen(b *testing.B) {
    var obj = &GenGetObj[int]{}
    var i = 0
    for b.Loop() {
        gen(obj, i)
        i++
    }
}

BenchmarkReg-4 441981427 2.743 ns/op BenchmarkReg-4 428439991 2.810 ns/op BenchmarkReg-4 422647483 2.775 ns/op BenchmarkReg-4 414114366 2.839 ns/op BenchmarkReg-4 429933127 2.720 ns/op BenchmarkIface-4 260450607 4.558 ns/op BenchmarkIface-4 258018102 4.488 ns/op BenchmarkIface-4 248518578 4.655 ns/op BenchmarkIface-4 257133632 4.517 ns/op BenchmarkIface-4 272843784 4.386 ns/op BenchmarkGen-4 245088946 4.932 ns/op BenchmarkGen-4 248562715 4.826 ns/op BenchmarkGen-4 245683516 4.812 ns/op BenchmarkGen-4 239086440 4.948 ns/op BenchmarkGen-4 228736507 5.074 ns/op


更多关于Golang泛型性能基准测试与比较的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

在不同平台和架构上得到了类似的结果。 这不是语言问题,对吧?

你具体尝试了什么?你对结果有什么疑问?

更多关于Golang泛型性能基准测试与比较的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从基准测试结果来看,泛型接口的性能确实比直接方法调用稍慢,但差异主要源于Go的运行时机制,而不是语言本身的问题。以下是具体分析:

性能差异原因

  1. 直接方法调用(BenchmarkReg):编译器在编译时就能确定具体类型和方法地址,是最快的调用方式。

  2. 接口调用(BenchmarkIface):涉及接口表查找和动态分派,需要额外的间接调用开销。

  3. 泛型接口调用(BenchmarkGen):在Go 1.18+中,泛型通过单态化实现,但接口约束仍然需要运行时类型检查。

代码示例:优化泛型性能

// 方法1:使用具体类型而非接口
func genDirect[T Signed](obj *GenGetObj[T], i T) T {
    return obj.Get(i)
}

func BenchmarkGenDirect(b *testing.B) {
    var obj = &GenGetObj[int]{}
    var i = 0
    for b.Loop() {
        genDirect(obj, i)
        i++
    }
}

// 方法2:内联优化
type Calculator[T Signed] struct {
    multiplier T
}

func (c *Calculator[T]) Calculate(x T) T {
    return x * c.multiplier
}

func processBatch[T Signed](calc *Calculator[T], data []T) []T {
    result := make([]T, len(data))
    for i, v := range data {
        result[i] = calc.Calculate(v)  // 内联优化
    }
    return result
}

// 方法3:避免不必要的接口约束
func genericAdd[T Signed](a, b T) T {
    return a + b
}

func BenchmarkGenericAdd(b *testing.B) {
    var sum int
    for i := 0; i < b.N; i++ {
        sum = genericAdd(sum, i)
    }
    _ = sum
}

实际测试对比

// 测试不同类型泛型的性能
func BenchmarkGenericInt(b *testing.B) {
    var result int64
    for i := 0; i < b.N; i++ {
        result = maxGeneric(int64(i), int64(i-1))
    }
    _ = result
}

func BenchmarkGenericFloat(b *testing.B) {
    var result float64
    for i := 0; i < b.N; i++ {
        result = maxGeneric(float64(i), float64(i-1))
    }
    _ = result
}

// 泛型max函数
func maxGeneric[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

关键发现

  1. 泛型在编译时进行单态化,为每种具体类型生成专用代码
  2. 接口调用总是比直接调用慢,无论是否使用泛型
  3. 性能差异主要来自Go的接口机制,而非泛型本身
  4. 在复杂算法中,泛型可以消除类型转换开销,可能带来性能提升
// 示例:泛型在复杂场景下的优势
func FilterGeneric[T any](slice []T, predicate func(T) bool) []T {
    result := make([]T, 0, len(slice))
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// 对比非泛型版本需要为每种类型重复实现
func FilterInt(slice []int, predicate func(int) bool) []int {
    result := make([]int, 0, len(slice))
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

结论:性能差异是Go接口机制的固有特性,不是泛型实现的问题。在适当场景下使用泛型,可以通过代码复用和减少运行时类型转换来获得性能收益。

回到顶部