0.2.0 GC 与内存使用优化

整体方案

  • 新增内存复用框架,可以简单复用自定义内存
  • 改进BytePool, 更加方便全局复用
  • 改进IoBufferPool,底层使用BytePool,提高内存复用度
  • 改进Read/Write Buffer,减少内存使用,提升性能
  • 优化sofa部分实现,减少临时对象生成
  • 复用protocol, stream, proxy层的常用内存结构

细节与变更

内存复用框架

通过实现如下接口,可方便复用所需内存

  • Name() 返回自定义内存结构的唯一标识。
  • New() 新建内存结构的方法
  • Reset()重置内存结构的方法,用于回收前调用,清空结构。

    1. // BufferPoolCtx is the bufferpool's context
    2. type BufferPoolCtx interface {
    3. // Name returns the bufferpool's name
    4. Name() int
    5. // New returns the buffer
    6. New() interface{}
    7. // Reset resets the buffer
    8. Reset(interface{})
    9. }

    以Sofa为例:

  • SofaProtocolBufferCtx实现了BufferPoolCtx接口
  • SofaProtocolBuffers 为需要复用的内存结构体
  • SofaProtocolBuffersByContent 方式用于获取内存结构体
  • 内存结构体会在请求结束之后统一释放,请求上下文通过 context 关联
    ```go
    type SofaProtocolBufferCtx struct{}

func (ctx SofaProtocolBufferCtx) Name() int {
return buffer.SofaProtocol
}

func (ctx SofaProtocolBufferCtx) New() interface{} {
buffer := new(SofaProtocolBuffers)
return buffer
}

func (ctx SofaProtocolBufferCtx) Reset(i interface{}) {
buf, _ := i.(*SofaProtocolBuffers)
buf.BoltReq = BoltRequestCommand{}
buf.BoltRsp = BoltResponseCommand{}
buf.BoltEncodeReq = BoltRequestCommand{}
buf.BoltEncodeRsp = BoltResponseCommand{}
}

type SofaProtocolBuffers struct {
BoltReq BoltRequestCommand
BoltRsp BoltResponseCommand
BoltEncodeReq BoltRequestCommand
BoltEncodeRsp BoltResponseCommand
}

func SofaProtocolBuffersByContent(context context.Context) SofaProtocolBuffers {
ctx := buffer.PoolContext(context)
return ctx.Find(SofaProtocolBufferCtx{}, nil).(
SofaProtocolBuffers)
}

  1. 使用方法:
  2. ```go
  3. sofabuffers := sofarpc.SofaProtocolBuffersByContent(context)
  4. request := &sofabuffers.BoltReq

BytePool改进

新增GetBytes()PutBytes()函数,用于全局获取和回收 *[]byte, 屏蔽pool实现细节。

  1. // GetBytes returns *[]byte from byteBufferPool
  2. func GetBytes(size int) *[]byte
  3. // PutBytes Put *[]byte to byteBufferPool
  4. func PutBytes(buf *[]byte)

IoBuffer改进

  • 新增GetIoBuffer()PutIoBuffer()函数,用于全局获取和释放 types.IoBuffer,屏蔽pool实现细节。
    ```go
    // GetIoBuffer returns IoBuffer from pool
    func GetIoBuffer(size int) types.IoBuffer

// PutIoBuffer returns IoBuffer to pool
func PutIoBuffer(buf types.IoBuffer)

  1. + Iobuffer封装的 []byte 使用`GetBytes()`获取。
  2. ```go
  3. func NewIoBuffer(capacity int) types.IoBuffer {
  4. buffer := &IoBuffer{
  5. offMark: ResetOffMark,
  6. }
  7. if capacity <= 0 {
  8. capacity = DefaultSize
  9. }
  10. buffer.b = GetBytes(capacity)
  11. buffer.buf = (*buffer.b)[:0]
  12. return buffer
  13. }
  • 新增方法Free()用于释放[]byte, Alloc()用于分配[]byte
  1. func (b *IoBuffer) Free()
  2. func (b *IoBuffer) Alloc(size int)

Read/Write buffer

Read buffer
  1. 循环读取,直到不可读或者达到预设的最大值, IoBuffer的扩容使用了GetBytes()PutBytes()方法,提高内存复用。
  2. 如果遇到读超时,调用Free()释放[]byte,然后调用Alloc()分配一个1字节的[]byte,用于等待读事件,减少空闲连接的内存使用。

    1. if te, ok := err.(net.Error); ok && te.Timeout() {
    2. if c.readBuffer != nil && c.readBuffer.Len() == 0 {
    3. c.readBuffer.Free()
    4. c.readBuffer.Alloc(DefaultBufferReadCapacity)
    5. }
    6. continue
    7. }
    Write buffer

    优化之前的write调用为writev,减少内存分配和拷贝,减少锁力度。

    1. write将通过chan把iobuffer的切片指针传递给wirteloop协程

      1. func (c *connection) Write(buffers ...types.IoBuffer) error {
      2. c.writeBufferChan <- &buffers
      3. return nil
      4. }
    2. wirteLoop协程合并IoBuffer, 然后调用doWriteIo

      1. func (c *connection) startWriteLoop() {
      2. select {
      3. case buf := <-c.writeBufferChan:
      4. c.appendBuffer(buf)
      5. //todo: dynamic set loop nums
      6. for i := 0; i < 10; i++ {
      7. select {
      8. case buf := <-c.writeBufferChan:
      9. c.appendBuffer(buf)
      10. default:
      11. }
      12. }
      13. _, err = c.doWriteIo()
      14. }
    3. 调用net.Buffers的WriteTo方法,使用wirtev系统调用一次发送多个IoBuffer
    4. 最后调用PutIoBuffer(), 释放已经发送的IoBuffer
      1. func (c *connection) doWriteIo() (bytesSent int64, err error) {
      2. bytesSent, err = c.writeBuffers.WriteTo(c.rawConnection)
      3. if err != nil {
      4. return bytesSent, err
      5. }
      6. for _, buf := range c.ioBuffers {
      7. buffer.PutIoBuffer(buf)
      8. }
      9. c.ioBuffers = c.ioBuffers[:0]
      10. return
      11. }
  • buffersWriter接口
    1. // buffersWriter is the interface implemented by Conns that support a
    2. // "writev"-like batch write optimization.
    3. // writeBuffers should fully consume and write all chunks from the
    4. // provided Buffers, else it should report a non-nil error.
    5. type buffersWriter interface {
    6. writeBuffers(*Buffers) (int64, error)
    7. }

    sofa优化

  • 使用SofaProtocolBuffers复用结构体,复用IoBuffer,map等结构。
  • 减少临时对象
    ConvertPropertyValue返回值为interface{}, 会产生临时对象,该方法在解析解析过程中调用量很大,通过ConvertPropertyValueUint8等系列函数规避临时对象产生。

    1. func ConvertPropertyValue(strValue string, kind reflect.Kind) interface{} {
    2. switch kind {
    3. case reflect.Uint8:
    4. value, _ := strconv.ParseUint(strValue, 10, 8)
    5. return byte(value)
    6. case reflect.Uint16:
    7. value, _ := strconv.ParseUint(strValue, 10, 16)
    8. return uint16(value)
    9. }
    10. func ConvertPropertyValueUint8(strValue string) byte {
    11. value, _ := strconv.ParseUint(strValue, 10, 8)
    12. return byte(value)
    13. }
    14. func ConvertPropertyValueUint16(strValue string) uint16 {
    15. value, _ := strconv.ParseUint(strValue, 10, 16)
    16. return uint16(value)
    17. }

测试

单元测试 unit-test

测试代码:buffer_test.go

  • Bench (场景供参考)
    1. goos: darwin
    2. goarch: amd64
    3. pkg: github.com/alipay/sofa-mosn/pkg/buffer
    4. // 使用 BytePool分配
    5. 1000000 1762 ns/op 26 B/op 0 allocs/op
    6. // 直接分配 []byte
    7. 1000000 2086 ns/op 2048 B/op 1 allocs/op
    8. // 使用 IobufferPool 分配
    9. 1000000 1225 ns/op 31 B/op 0 allocs/op
    10. // 直接分配 Iobuffer
    11. 1000000 1585 ns/op 2133 B/op 3 allocs/op
    12. PASS