在golang写服务的过程,以及到一些代码上的优化以及使用技巧。 例如,大批量对象使用以及字符串拼写的问题。
背景
在工作的过程中,公司的服务从python2.7 迁移到 golang 的版本。从语言层面上去迁移对应的服务。
在对应的逻辑代码迁移完毕后,上线总是失败,出现大量超时。但golang 在同个机器上 处理速度以及QPS是远远超过Python版本的服务。
于是,在找不到原因的情况下,只能努力的对golang 进行优化。
那么涉及的优化有:
1:打印log部分
2:redis pipeline 优化
3:对象复用优化,减少gc时间
4:减少string直接操作拼接
5:减少defer 的使用
6:json解析问题
1:打印log优化
在使用golang 自带的log 的时候,你会发现,每个调用都会有时间戳,然后我也会统计主要处理函数的时间,能不能在 打印log的时候,把时间Time 从外部传入。那么就可以减少 time.Now() 的调用。
2:redis pipeline 优化
在项目中,如果存在 多个 redis getbit 处理 或者其他同一时间同一函数内 批量操作的时候,可以采用 redis pipeline 优化。一次性传输等待处理,减少传输数据的时间。
在如果是getbit 的话,当数据量大起来 ,需要多次操作gitbit 的时候,速度会慢好多,因为这个耗时主要是redis 的cpu处理,有多余的处理操作。
其实redis getbit 和setbit 的处理相当于 是 在一个string类型上位操作 。可以把 总体的 getbit 看成一个 get 操作。之后用代码处理 把bit 的数据还原成 数组的形式。服务器这样处理的代价远远少于redis处理的代价。redis是共用的。
3:对象复用优化,减少gc时间
在代码的函数中,有时候会经常使用到 make()的方法申请数组或者map 使用,当函数次数调用多起来的时候,申请的内存总量也多。容易触发gc。
之后使用 sync 包下面 的Pool作为对象池使用。
该对象池的特点是,缓存对象。有Get 和Put的操作。每个触发gc的时候会清空对象池,所以不适合当长连接的对象池。
每次对 pool 的操作 减少了内存的使用总量,提升了对象复用的概率。减少gc触发时间。gc 也是需要时间的。
4:减少string直接操作拼接
golang 对于string 的处理是不可变的,每个拼接string 的时候都是创建新的字符串。
为了避免这样的操作,如果处理大批量的字符串拼接处理。
就引入到 bytes.Buffer 这个类 可尝试基准测试
5:减少defer 的使用
defer 看起来很方便简洁的处理关闭资源的事情。但在内存的操作中,是类似于堆栈的形式,先入后出的调用。
func BenchmarkDefer1(t *testing.B) {
l := 0
for i := 0; i < t.N; i++ {
func() {
l++
}()
}
fmt.Println("l=", l)
} //2.26 ns/op
func BenchmarkDefer2(t *testing.B) {
l := 0
for i := 0; i < t.N; i++ {
func() {
defer func(item *int) {
*item++
}(&l)
}()
}
fmt.Println("l=", l)
}//48.8 ns/op
差不多一个defer 操作 需要 40+的 ns/op ,量大起来就比较可观了。
网络上有些关于测试性能的资料可能比较老旧,或者版本比较旧。
新的版本优化总会比旧的好。
6:json解析问题
在golang 自带的 encoding/json 包中,json 的解析是采用了 反射的机制来反射出对应的数值。
为了优化json 的解析以及生成,采用的是easyjson 来解析。
github.com/mailru/easyjson
效率上提升几倍。 在easyjson 使用写struct的时候,能写完整就写完整的类型。例如 interface{}是万能的,但具体是 map[string]interface{} 的时候,写 map[string]interface{} 效率会更优。
end
优化的一些小问题就暂时说到这里了,这是根据项目进行的过程中开展的优化。
或许还有其他好的优化,欢迎指教。