golang技术降本增效的手段

最近一年各大中小厂都在搞”优化”,说到优化,目的还是”降本增效”,降低成本,增加效益(效率)。

技术层面,也有一些降本增效的常规操作。

比如池化、io缓冲区技术

golang C# eg.
池化技术 snnc.Pool ObjectPool 前端切图仔,归入前端资源池 , 随用随取
字节数组缓冲区 bytes.Buffer List
io缓冲区 bufio BufferStream 适度超前,赛道埋伏

池化技术 sync.Pool

sync.Pool位于标准库,该文件提供了对临时对象的重复使用能力, 避免了频繁的gc, 对并发协程是安全的。

该文件只有三个控制点:

  • New: 默认的临时对象
  • Get: 从池中哪一个临时对象
  • Put: 放回池中,以重用

下面使用基准测试进行b.N*1000次运算时的内存消耗。

package main
import (
"sync"
"testing"
)
type Person struct {
Age int
}
var (
personPool = sync.Pool{
New: func() interface{} { // 设置默认值
return &Person{}
},
}
)
func ExampleObjPool() {
var p *Person
for i := 0; i < 1000; i++ {
for j := 0; j < 1000; j++ {
p = personPool.Get().(*Person)
p.Age = i + 1
personPool.Put(p)
}
}
p = personPool.Get().(*Person)
fmt.Println(p.Age)
// output:1000
}
func BenchmarkWithoutPool(b *testing.B) {
var p *Person
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
p = new(Person) // 每次均产生临时对象
p.Age = 23
}
}
}
func BenchmarkWithPool(b *testing.B) {
var p *Person
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
p = personPool.Get().(*Person) // 从池中复用对象
p.Age = 23
personPool.Put(p) // 放回以重用
}
}
}
package main

import (
  "sync"
  "testing"
)

type Person struct {
  Age int
}

var (
  personPool = sync.Pool{
    New: func() interface{} {  // 设置默认值
      return &Person{}
    },
  }
)

func ExampleObjPool() {
  var p *Person
  for i := 0; i < 1000; i++ {
    for j := 0; j < 1000; j++ {
      p = personPool.Get().(*Person)
      p.Age = i + 1
      personPool.Put(p)
    }
  }
  p = personPool.Get().(*Person)
  fmt.Println(p.Age)
  // output:1000
}

func BenchmarkWithoutPool(b *testing.B) {
  var p *Person
  b.ReportAllocs()
  b.ResetTimer()

  for i := 0; i < b.N; i++ {
    for j := 0; j < 1000; j++ {
      p = new(Person)    // 每次均产生临时对象
      p.Age = 23
    }
  }
}

func BenchmarkWithPool(b *testing.B) {
  var p *Person
  b.ReportAllocs()
  b.ResetTimer()

  for i := 0; i < b.N; i++ {
    for j := 0; j < 1000; j++ {
      p = personPool.Get().(*Person)  // 从池中复用对象
      p.Age = 23
      personPool.Put(p)     // 放回以重用
    }
  }
}
package main import ( "sync" "testing" ) type Person struct { Age int } var ( personPool = sync.Pool{ New: func() interface{} { // 设置默认值 return &Person{} }, } ) func ExampleObjPool() { var p *Person for i := 0; i < 1000; i++ { for j := 0; j < 1000; j++ { p = personPool.Get().(*Person) p.Age = i + 1 personPool.Put(p) } } p = personPool.Get().(*Person) fmt.Println(p.Age) // output:1000 } func BenchmarkWithoutPool(b *testing.B) { var p *Person b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < 1000; j++ { p = new(Person) // 每次均产生临时对象 p.Age = 23 } } } func BenchmarkWithPool(b *testing.B) { var p *Person b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < 1000; j++ { p = personPool.Get().(*Person) // 从池中复用对象 p.Age = 23 personPool.Put(p) // 放回以重用 } } }

测试结果如下,sync.Pool[重用临时对象]的性能可见一斑。

bytes.Buffer

golang很多方法内充斥了[]byte, 就连最常规的序列化/反序列化,返回值/参数都是[]byte, 但是slice是一个冷冰冰的数据结构,没有得心趁手的操作行为,还有很多陷阱。

func Marshal(v any) ([]byte, error)
func Unmarshal(data []byte, v any)
  func Marshal(v any) ([]byte, error)
  func Unmarshal(data []byte, v any) 
func Marshal(v any) ([]byte, error) func Unmarshal(data []byte, v any)

A bytes.Buffer is a variable-sized buffer of bytes with Read and Write methods.

坦白讲bytes.Buffer并非底层优化机制, 实际提供了一个友好操作slice的方式。

下面的”abcd”字符串,先读取首字符、后面追加字符”e”:

var b bytes.Buffer
b.Write([]byte("abcd")) // 写入之后,自动扩容
rdbuf := make([]byte, 1)
_, err := b.Read(rdbuf) // 读取一个字节的数据,移动读off指针
if err != nil {
panic(err)
}
fmt.Println(b.String()) // 上面读取了一个字符,读off已经移动,现从读off位置转换为string
b.WriteByte('e') // 在尾部写字符
fmt.Println(b.String())
fmt.Printf("%d, %d \n", b.Len(), b.Cap()) // Len方法返回还能读取的字符数量,Cap返回底层buf的容量
//output:
bcd
bcde
4, 64
  var b bytes.Buffer
  b.Write([]byte("abcd")) // 写入之后,自动扩容
  rdbuf := make([]byte, 1)
  _, err := b.Read(rdbuf) // 读取一个字节的数据,移动读off指针
  if err != nil {
    panic(err)
  }
  fmt.Println(b.String()) // 上面读取了一个字符,读off已经移动,现从读off位置转换为string
  b.WriteByte('e')        // 在尾部写字符
  fmt.Println(b.String())
  fmt.Printf("%d, %d \n", b.Len(), b.Cap()) // Len方法返回还能读取的字符数量,Cap返回底层buf的容量
  
//output:
bcd 
bcde
4, 64
var b bytes.Buffer b.Write([]byte("abcd")) // 写入之后,自动扩容 rdbuf := make([]byte, 1) _, err := b.Read(rdbuf) // 读取一个字节的数据,移动读off指针 if err != nil { panic(err) } fmt.Println(b.String()) // 上面读取了一个字符,读off已经移动,现从读off位置转换为string b.WriteByte('e') // 在尾部写字符 fmt.Println(b.String()) fmt.Printf("%d, %d \n", b.Len(), b.Cap()) // Len方法返回还能读取的字符数量,Cap返回底层buf的容量 //output: bcd bcde 4, 64

bufio

Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer object, creating another object (Reader or Writer) that also implements the interface but provides buffering and some help for textual I/O.

首先我们需要知道当应用程序执行IO操作(从文件、网络和数据库读取或写入数据),它将触发底层的系统调用,从性能角度来看,这很繁重。

缓冲IO是一种技术,用于在传递之前暂时积累IO操作的结果。这种技术可以通过减少系统调用的数量来提高程序的速度。例如,如果您想要逐字节地从磁盘读取数据,与每次直接从磁盘读取每个字节不同,使用缓冲区IO技术,我们可以一次将一个数据块读入缓冲区,然后消费者可以以任何您想要的方式从缓冲区读取数据。通过减少繁重的系统调用,性能将得到提高。

磁盘:1.寻址:ms(毫秒) 2.磁盘带宽:MB/s
内存:1.寻址:ns(纳秒) 2. 内存带宽:GB/s
磁盘比内存在寻址上慢了10W倍,传输带宽上慢了20倍。

开源的带缓冲区的logrus日志写入hook,就利用了bufio技术。

// 利用bufio针对原始io.Writer封装成带缓冲区的io.Writer
`s.writer = bufio.NewWriterSize(s.Writer, size)
......
if len(bs) > s.writer.Available() && s.writer.Buffered() > 0 {
if err := s.writer.Flush(); err != nil {
return err
}
}
_, err = s.writer.Write(bs)`
 // 利用bufio针对原始io.Writer封装成带缓冲区的io.Writer  
 `s.writer = bufio.NewWriterSize(s.Writer, size) 
  ......
  if len(bs) > s.writer.Available() && s.writer.Buffered() > 0 {
    if err := s.writer.Flush(); err != nil {
      return err
    }
  }
  _, err = s.writer.Write(bs)` 
// 利用bufio针对原始io.Writer封装成带缓冲区的io.Writer `s.writer = bufio.NewWriterSize(s.Writer, size) ...... if len(bs) > s.writer.Available() && s.writer.Buffered() > 0 { if err := s.writer.Flush(); err != nil { return err } } _, err = s.writer.Write(bs)`

优化总结

  • sync.Pool 复用临时对象,减少gc次数
  • bufio利用缓冲区,减少频繁的系统调用

研发人员历来都是公司的成本大头,技术优化虽然不能增效,但是能降本,减少核数和内存条,希望大家都能通过技术优化让自己不被优化。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYvSGamy' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片