美文网首页
Go常见的一些性能优化

Go常见的一些性能优化

作者: 迪克dike | 来源:发表于2021-04-28 14:37 被阅读0次

[]byte和string

转换

  • 尽量避免[]byte和string的互相转换,go的string是不可变类型,标准实现中和[]byte的互转均为值拷贝
  • 多数场景下都可以优先选择强转换方式进行互转
//强转换
func stringToBytes(s string) []byte {
   x := (*[2]uintptr)(unsafe.Pointer(&s))
   b := [3]uintptr{x[0], x[1], x[1]}
   return *(*[]byte)(unsafe.Pointer(&b))
}

func bytesToString(b []byte) string {
   return *(*string)(unsafe.Pointer(&b))
}

仅在只读场景下使用强转换

内存申请

提前预估容量

  • slice/map初始化尽量估计好长度,能有效减少内存分配次数,优化很明显
  • 尽量规避使用append,因为需要值拷贝,且涉及到重新申请内存,可能会发生逃逸(Mac环境下测试:当append之后的slice长度大于8时会被分配到堆上)
  • 如果无法预估,一般场景下可以考虑申请足够大的空间,并在场景允许的情况下优先考虑复用slice
func useCap1() {
   arr := make([]int, 0, 2048)
   for i := 0; i < 2048; i++ {
      arr = append(arr, i)
   }
}

func useCap2() {
   arr := make([]int, 2048)
   for i := 0; i < 2048; i++ {
       arr[i] = i
   }
}

func noCap() {
   var arr []int
   for i := 0; i < 2048; i++ {
      arr = append(arr, i)
   }
}

Benchmark

goos: darwin
goarch: amd64
BenchmarkUseCap1-12       966577              1212 ns/op               0 B/op          0 allocs/op
BenchmarkUseCap2-12      2398420               499 ns/op               0 B/op          0 allocs/op
BenchmarkNoCap-12         192712              6016 ns/op           58616 B/op         14 allocs/op

slice扩容的主要代码,常规场景下的扩容逻辑为cap<1024时每次翻倍,cap>1024时每次增长25%,此处也可以对应上benchmark中noCap()分配在了堆上,并经过了14次扩容

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
   newcap = cap
} else {
   if old.len < 1024 {
      newcap = doublecap
   } else {
      // Check 0 < newcap to detect overflow
      // and prevent an infinite loop.
      for 0 < newcap && newcap < cap {
         newcap += newcap / 4
      }
      // Set newcap to the requested cap when
      // the newcap calculation overflowed.
      if newcap <= 0 {
         newcap = cap
      }
   }
}

优先在栈上分配

func BenchmarkHeap(b *testing.B) {
   m := make([]*string, 1000)
   for i := 0; i < b.N; i++ {
      for i := 0; i < 1000; i++ {
         s := "test"
         m[i] = &s
      }
   }
}

func BenchmarkStack(b *testing.B) {
   m := make([]string, 1000)
   for i := 0; i < b.N; i++ {
      for i := 0; i < 1000; i++ {
         s := "test"
         m[i] = s
      }
   }
}

Benchmark

goos: darwin
goarch: amd64
BenchmarkHeap-12           44640         23033 ns/op       16000 B/op       1000 allocs/op
BenchmarkStack-12        4650966           252 ns/op           0 B/op          0 allocs/op

Map/Slice

Map中简单结构尽量不使用指针

map[int]*int

func gcTime() time.Duration {
    start := time.Now()
    runtime.GC()
    return time.Since(start)
}

func f1() {
    s := make(map[int]int, 5e7)
    for i := 0; i < 5e7; i++ {
        s[i] = i
    }
    fmt.Printf("With %T, GC took %s\n", s, gcTime())
    _ = s[0]
}

func f2() {
    s := make(map[int]*int, 5e7)
    for i := 0; i < 5e7; i++ {
        s[i] = &i
    }
    fmt.Printf("With %T, GC took %s\n", s, gcTime())
    _=s[0]
}

Output:

With map[int]int, GC took 31.956029ms
With map[int]*int, GC took 184.174966ms

不包含指针的map在gc中不需要scanObject
另外根据map的实现(关键词搜索bmap),当元素值大于128byte时,还是需要scanObject

type BigStruct struct {
   C01 int
   C02 int
   //...
   C16 int // 128byte gc scan临界点
   C17 int //136byte
 }
 
 func f3() {
   s := make(map[int]BigStruct, N)
   for i := 0; i < N; i++ {
      s[i] = BigStruct{}
   }
   fmt.Printf("With %T, GC took %s\n", s, timeGC())
   _ = s[0]
}

Output:

With map[int]main.BigStruct, GC took 1.628134832s
With map[int]main.NoBigStruct, GC took 44.708865ms

BigStruct 多了一个C17,GC时间大幅增加

对比[]*int, []int和[]BigStruct

func f4() {
    s := make([]*int, N)
    for i := 0; i < N; i++ {
        s[i] = &i
    }
    fmt.Printf("With %T, GC took %s\n", s, gcTime())
    _ = s[0]
}

func f5() {
    s := make([]int, N)
    for i := 0; i < N; i++ {
        s[i] = i
    }
    fmt.Printf("With %T, GC took %s\n", s, gcTime())
    _ = s[0]
}

func f6() {
    s := make([]BigStruct, N)
    for i := 0; i < N; i++ {
        s[i] = BigStruct{}
    }
    fmt.Printf("With %T, GC took %s\n", s, gcTime())
    _ = s[0]
}

Output:

With []*int, GC took 137.308395ms
With []int, GC took 211.862µs
With []main.BigStruct, GC took 173.504µs

slice包含指针的时候同理需要scanObject,但不包含指针时不受元素大小影响,且gc效率要比map高很多
上面的优化受到很多条条框框限制,比如map[int]string其实是包含指针的(见string定义),无法享受高效的gc,看上去不实用,但是基于此有一种应用较多的优化方式,即把大型的map结构转换为map[int]int(索引)+slice的方式,把gc压力转移到slice上(比map gc开销低),典型例子如bigcache

相关文章

  • Go常见的一些性能优化

    []byte和string 转换 尽量避免[]byte和string的互相转换,go的string是不可变类型,标...

  • Awesome Extra

    性能优化 性能优化模式 常见性能优化策略的总结 Spark 性能优化指南——基础篇 Spark 性能优化指南——高...

  • iOS开发中常见的性能优化技巧

    iOS开发中常见的性能优化技巧 iOS开发中常见的性能优化技巧

  • go性能优化

    程序各种指标 是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减...

  • pprof 的使用

    基本介绍 pprof 是在做性能优化前的性能分析工具。 安装: go get -u github.com/goog...

  • Win2003 IIS6.0性能优化指南

    Win2003 IIS6.0性能优化篇,从网站找一些常见的优化方法,大家可以参考下。 问:好多asp.net程序,...

  • go-redis 源码分析:连接池

    笔者最近在项目中基于 go-redis 实现 Redis 缓存优化性能。go-redis 是一个 Go 语言实现的...

  • 性能优化随想

    大家老说性能,老说性能优化。那什么是性能?如何做性能优化?如何做好性能优化? 性能是主观的,一般通过一些客观指标来...

  • 12、大表如何优化?

    大表如何优化? 当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下: 1. 限定...

  • 不要一把梭了,这才是SQL优化的正确姿势

    全文内容预览: 所以,在开始之前(MySQL 优化),咱们先来聊聊性能优化的一些原则。 性能优化原则和分类 性能优...

网友评论

      本文标题:Go常见的一些性能优化

      本文链接:https://www.haomeiwen.com/subject/reqvrltx.html