go语言性能优化 | 青训营笔记

114 阅读2分钟
  • slice

    • 内存预分配

      • 尽可能在使用make()初始化切片时提供容量信息
      • 因为slice本质是一个包含了数组、当前长度和容量的数据结构,在新增元素超出容量后,会导致内存重分配和原有元素的移动,频繁的扩容操作会导致性能的损耗,所以如果提前预知切片容量并指定的话,会减少不必要的扩容
    • 大内存未释放

      • 在已有的很大的切片上创建很小的切片,可能会导致原始切片无法释放
      • 因为在已有的切片基础上创建切片不会创建新的底层数组,所以在原始大切片上新建小切片的时候,原始切片在内存上有引用,无法释放
      • 可以使用copy代替re-slice
  • map

    • 内存预分配

      • 原理与slice预分配类似
  • 字符串处理

    • 使用strings.Buffer而不是+
    • 因为字符串时不可变类型,使用+时每次都会重新分配内存,而strings.Buffer底层时[]byte数组,内存扩容策略,不需要每次拼接重新分配内存
  • 空结构体struct{}占位

    • 空结构体实例本身不占据任何的内存空间,可作为各种场景下的占位符使用
    • 例如可以使用map来实现set,因为对于这个场景下,只需要使用map的键,即使将map的值设置为bool类型,每个键值对也会多占据一个字节空间
  • 使用atomic包

    • 在涉及竞态的时候,对一个共享变量,使用atomic比加锁解锁要快

    • 例如

    • type atomicCounter struct {
          i int32
      }
      func AtomicAddOne(c *atomicCounter) {
          atomic.AddInt(&c.i, 1)
      }
      ​
      /*-----------------------------------------*/type mutexCounter struct {
          i int32
          m sync.Mutex
      }
      func MutexAddOne(c *mutexCounter) {
          c.m.Lock()
          c.i++
          c.m.Unlock()
      }
      

      在上述代码中,上面的的方式就要比下面的快

    • 这是因为锁的实现是通过操作系统来实现,属于系统调用,atomic操作时通过硬件实现,效率比锁高,sync.Mutex应该用来保护一段逻辑,而不仅仅是一个变量