性能优化
1.是什么
在保持功能 不变 满足需求的前提下,优化程序的性能。
2.常见目标
减少执行时间,减少内存使用,提高系统吞吐量,提高系统可伸缩性;
-不同的目标之间往往有冲突
-满足一定约束条件下的多目标优化
3.优化思路
算法和数据结构;并发&并行;网络和磁盘IO;内存分配&垃圾回收;profiling;编译器优化;……
优化实践
本函数在一个api网关中,负责在需要调用client时为服务创建client。
在原有的程序中,我们发现每次需要调用客户端时都创建一个新的client对象,会导致大量的重复创建和销毁操作,降低了程序的效率。为了解决这个问题,我们决定引入一个缓冲池(Clients)来存储已经创建的client对象,以便在需要时直接从缓冲池中获取,而不是每次都创建新的对象。
func GetCli(serviceName string)(genericclient.Client, error) {
...//获取必要参数
cli, err := genericclient.NewClient(serviceName, g, client.WithResolver(r), client.WithLoadBalancer(loadbalance.NewWeightedRandomBalancer()))
return cli,nil
}
在优化后的函数中,我们首先检查缓冲池中是否已经存在所需的client对象。如果存在,我们直接返回该对象。如果不存在,我们调用UpdateCli函数来创建新的client对象,并将其存储到缓冲池中。这样,在后续的请求中,我们就可以直接从缓冲池中获取已经创建好的client对象,而无需再次创建。
func GetCli(serviceName string, idlVersion string) (genericclient.Client, error) {
value, exist := Clients[serviceName]
if exist {
return value, nil
} else {
err := UpdateCli(serviceName)
if err != nil {
return nil, err
}
return Clients[serviceName], nil
}
}
通过引入缓冲池,我们可以显著减少client对象的创建次数,从而提高程序的性能和效率。这种优化方法适用于存在大量重复请求的场景,通过复用已有对象,避免了不必要的资源消耗和性能损失。
同时,为了保证缓冲池中的client对象为最新,需要在参数变更时对缓冲池进行更新,在优化后的函数中,我们通过调用UpdateCli函数来更新缓冲池中的client对象。考虑到RPC调用的服务可能会更新,缓冲池中的client对象也会在服务端进行更新时同步进行更新并重新放入缓冲池。
优化工具
通过Go语言自带的 profiling 工具 pprof,可以分析程序运行时的 CPU、内存、阻塞等资源使用情况。
开启pprof
// your code here
}import (
"net/http"
_ "net/http/pprof" // injecting routing into http
)
func main() {
go http.ListenAndServe("localhost:8080", nil)
for {}
}
优化方式
1.调用链图、火焰图
2.cpu 优化
3.heap 泄露
4.goroutine 泄露
例如
func copySlice(s []int) (r []int) {
for i := 0; i < len(s); i++ {
r = append(r, s[i])
}
return r
}
该函数使用了简单的迭代循环来遍历源切片 s 的元素,并通过 append 函数将每个元素追加到结果切片 r 中。最后,它返回结果切片 r。
通过pprof的图表显示我们可以发现在通过 append 函数将每个元素追加到结果切片 r 中时消耗了较长时间
func copySlice(s []int) []int {
r := make([]int, 0, len(s))
for i := 0; i < len(s); i++ {
r = append(r, s[i])
}
return r
}
新函数与原函数的主要区别在于它使用了 make 函数来创建一个容量为 len(s) 的切片 r,而不是直接声明一个空切片。这样做的目的是为了提前分配足够的内存空间,以避免在追加元素时进行多次重新分配内存的操作,从而提高性能。
处理大型切片时,第二个函数的性能优势会更加显著。由于它提前分配了足够的内存空间,避免了多次重新分配的开销,因此能够更高效地复制大型切片。