Golang 优化之路——空结构

写在前面

开发 hashset 经常使用的套路:javascript

map[int]int8
map[int]bool复制代码

咱们通常只用 map 的键来保存数据,值是没有用的。因此来缓存集合数据会形成内存浪费。java

空对象

空对象是个神奇的东西。它指的是没有字段的结构类型。git

type Q struct{}复制代码

它牛逼的地方在于:github

  • 能够和普通结构同样操做缓存

    var a = []struct{}{struct{}{}}
      fmt.Println(len(a)) // prints 1复制代码
  • 不占用空间性能

    var s struct{}
      fmt.Println(unsafe.Sizeof(s)) // prints 0复制代码
  • 声明两个空对象,它们指向同一个地址学习

    type A struct{}
      a := A{}
      b := A{}
      fmt.Println(&a == &b) // prints true复制代码

形成这个结果的缘由是 Golang 的编译器会把这种空对象都当成runtime.zerobase处理。优化

var zerobase uintptr复制代码

hashset

有了上面的介绍,就能够利用空结构来优化 hashset 了。ui

var itemExists = struct{}{}

type Set struct {
    items map[interface{}]struct{}
}

func New() *Set {
    return &Set{items: make(map[interface{}]struct{})}
}

func (set *Set) Add(item interface{}) {
    set.items[item] = itemExists
}

func (set *Set) Remove(item interface{}) {
    delete(set.items, item)
}

func (set *Set) Contains(item interface{}) bool {
    if _, contains := set.items[item]; !contains {
        return false
    }
    return true
}复制代码

一个简易的 hashset 实现就完成了。spa

性能比较

func BenchmarkIntSet(b *testing.B) {
    var B = NewIntSet(3)
    B.Set(10).Set(11)
    for i := 0; i < b.N; i++ {
        if B.Exists(1) {

        }
        if B.Exists(11) {

        }
        if B.Exists(1000000) {

        }
    }
}

func BenchmarkMap(b *testing.B) {
    var B = make(map[int]int8, 3)
    B[10] = 1
    B[11] = 1
    for i := 0; i < b.N; i++ {
        if _, exists := B[1]; exists {

        }
        if _, exists := B[11]; exists {

        }
        if _, exists := B[1000000]; exists {

        }
    }
}

BenchmarkIntSet-2       50000000                35.3 ns/op             0 B/op          0 allocs/op
BenchmarkMap-2          30000000                41.2 ns/op             0 B/op          0 allocs/op复制代码

结论

  • 性能,有些提高,但不是特别明显。尤为是线上压力不大的状况性能应该不会有明显变化;
  • 内存占用。咱们的服务缓存较多、占用内存较大,经过这个优化实测能够减小 1.6 GB 的空间。不过这个优化的空间取决于数据量。

参考文献

相关文章
相关标签/搜索