高质量编程与性能调优

507 阅读3分钟

tech.bytedance.net/course-vide…

性能优化建议

  • 简介

    • 性能优化的前提是满足正确可靠、简洁清晰等质量因素
    • 性能优化是综合评估,有时候时间效率和空间效率可能对立
    • 针对Go语言特性,介绍Go相关的性能优化建议

1.3.1 Benchmark

  • 性能表现需要实际数据衡量
  • Go语言提供了支持基准性能测试的benchmark工具 go test -bench=. -benchmem

  • GOMAXPROCS 1.5版本后,默认值为CPU核数

1.3.2 Slice性能优化建议

  • slice预分配内存

    • 尽可能在使用make()初始化切片时提供容量信息

1.3.3 map性能优化建议

  • map预分配内存

    • 分析

      • 不断向map中添加元素的操作会触发map的扩容
      • 提前分配好空间可以减少内存拷贝和Rehash的消耗
      • 建议根据实际需求提前预估好需要的空间
  • 测试代码

go
复制代码
      func NoPreAlloc(size int) {         data := make(map[int]int)         for i := 0; i < size; i++ {             data[i] = 1         }      }      func PreAlloc(size int) {         data := make(map[int]int, size)         for i := 0; i < size; i++ {            data[i] = 1         }      }      func BenchmarkNoPreAlloc(b *testing.B) {         for n := 0; n < b.N; n++ {            NoPreAlloc(1000)         }      }      func BenchmarkPreAlloc(b *testing.B) {         for n := 0; n < b.N; n++ {            PreAlloc(1000)         }      }      

1.3.4 字符串处理

使用strings.Builder

  • 使用+号拼接性能最差,strings.Builder,bytes.Buffer相近,strings.Builder更快

  • 分析

    • 字符串在Go语言中是不可变类型,占用内存大小是固定的
    • 使用+每次都会重新分配内存
    • strings.Builder,bytes.Buffer底层都是[]byte数组
    • 内存扩容策略,不需要每次拼接重新分配内存
    • bytes.Buffer转化为字符串时重新申请了一块空间
    • strings.Builder直接将底层的[]byte转换成了字符串类型返回
    • stirngs.Builder和bytes.Buffer都可以通过预分配内存来提高性能

测试代码

go
复制代码
func Plus(n int, str string) string {   s := ""   for i := 0; i < n; i++ {      s += str   }   return s}func StrBuilder(n int, str string) string {   var builder strings.Builder   for i := 0; i < n; i++ {      builder.WriteString(str)   }   return builder.String()}func ByteBuffer(n int, str string) string {   buf := new(bytes.Buffer)   for i := 0; i < n; i++ {      buf.WriteString(str)   }   return buf.String()}func PreStrBuilder(n int, str string) string {   var builder strings.Builder   builder.Grow(n * len(str))   for i := 0; i < n; i++ {      builder.WriteString(str)   }   return builder.String()}func PreByteBuffer(n int, str string) string {   buf := new(bytes.Buffer)   buf.Grow(n * len(str))   for i := 0; i < n; i++ {      buf.WriteString(str)   }   return buf.String()}func BenchmarkPlus(b *testing.B) {   for n := 0; n < b.N; n++ {      Plus(1000, "string")   }}func BenchmarkStrBuilder(b *testing.B) {   for n := 0; n < b.N; n++ {      StrBuilder(1000, "string")   }}func BenchmarkByteBuffer(b *testing.B) {   for n := 0; n < b.N; n++ {      ByteBuffer(1000, "string")   }}func BenchmarkPreStrBuilder(b *testing.B) {   for n := 0; n < b.N; n++ {      PreStrBuilder(1000, "string")   }}func BenchmarkPreByteBuffer(b *testing.B) {   for n := 0; n < b.N; n++ {      PreByteBuffer(1000, "string")   }}

1.3.5 空结构体

使用空结构体节省内存

  • 空结构体struct{}实例不占据任何的内存空间

  • 可作为各种场景下的占位符使用

    • 节省资源
    • 空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符

测试代码

go
复制代码
func EmptyStructMap(n int) {   m := make(map[int]struct{})   for i := 0; i < n; i++ {      m[i] = struct{}{}   }}func BoolMap(n int) {   m := make(map[int]bool)   for i := 0; i < n; i++ {      m[i] = false   }}func BenchmarkEmptyStructMap(b *testing.B) {   for n := 0; n < b.N; n++ {      EmptyStructMap(10000)   }}func BenchmarkBoolMap(b *testing.B) {   for n := 0; n < b.N; n++ {      BoolMap(10000)   }}

  • 实现Set,可以考虑用map来代替

    • 对于这个场景,只需要用到map的键,而不需要值
    • 即使是将map的值设置为bool类型,也会多占据1个字节空间

1.3.6 atomic包

使用atomic包

  • 锁的实现是通过操作系统来实现,属于系统调用
  • atomic操作是通过硬件实现,效率比锁高
  • sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
  • 对于非数值操作,可以使用atomic.Value,能承载一个interface{}

测试代码

go
复制代码
type atomicCounter struct {   i int32}func AtomicAddOne(c *atomicCounter) {   atomic.AddInt32(&c.i, 1)}type mutexCounter struct {   i int32   m sync.Mutex}func MutexAddOne(c *mutexCounter) {   c.m.Lock()   c.i++   c.m.Unlock()}func BenchmarkAtomicAddOne(b *testing.B) {   for n := 0; n < b.N; n++ {      var counter = atomicCounter{}      AtomicAddOne(&counter)   }}func BenchmarkMutexAddOne(b *testing.B) {   for n := 0; n < b.N; n++ {      var counter = mutexCounter{}      MutexAddOne(&counter)   }}

小结

  • 避免常见的性能陷阱可以保证大部分程序的性能
  • 普通应用代码,不要一味地追求程序地性能
  • 越高级的性能优化手段越容易出现问题
  • 在满足正确可靠,简洁清晰的质量要求的前提下提高程序性能