Go调用Lua性能压测与调优

原创 吴就业 252 0 2022-12-29

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/c1b3d030fd764a48bd5f457bdddd109d

作者:吴就业
链接:https://wujiuye.com/article/c1b3d030fd764a48bd5f457bdddd109d
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

目的

  1. 调优使用gopher-lua库调用lua脚本性能:是否使用池化,使用什么策略实现虚拟机池。
  2. 输出性能测试报告给用户(开发者)。

测试说明

基于go提供的基准测试能力编写并发测试用例,为排除脚本本身的性能影响,脚本只实现简单的逻辑,并实现预编译。通过调整虚拟机池策略、cpu数、并行度等,输出调用lua脚本的平均耗时、占用的内存。

测试脚本

function helloLua(n)
    goSayHello("hello","my name is lua") -- 调用一下go方法
    return n,100000
end

基准测试代码

var luaMng = NewLuaPreCompileManager(NewLStatePool)

func init() {
   err := luaMng.CompileLua("test.lua", script)
   if err != nil {
      panic(err)
   }
}

func invokeLua() {
   result, err := luaMng.InvokeScriptFunc("test.lua", "helloLua", 30*time.Second, 2, 1)
   if err != nil {
      panic(err)
   }
   fmt.Println(result[0], result[1])
}

// go test -bench='Parallel$' -cpu=2 -benchtime=5s -count=3 -benchmem
func BenchmarkLuaPreCompileManager_InvokeScriptFunc_Parallel(b *testing.B) {
   b.ReportAllocs()
   b.ResetTimer()
   b.SetParallelism(2000)
   b.RunParallel(func(pb *testing.PB) {
      for pb.Next() {
         invokeLua()
      }
   })
}

虚拟机配置:

return NewLState(lua.Options{
   CallStackSize:       32,   // 最大调用栈大小,调用栈的深度,即最多可以有32个方法深度
   MinimizeStackMemory: true, // 调用堆栈将根据需要自动增长和收缩,最大为 `CallStackSize`
})

报表数据

几个概念:

虚拟机池 基准测试指定CPU数 基准测试时长 基准测试次数 并行度(goroutine数) 毫秒/op 内存消耗/op cpu占用(峰值) 内存占用(峰值)
不使用池 2 10s 5 1000 0.18455 159.5KB 190% 476.9M
不使用池 2 10s 5 2000 0.168622 159.5KB 191% 935.8M
不使用池 2 10s 5 4000 0.175112 159.6KB 190% 1.82G
使用池,不固定大小 2 10s 5 1000 0.065165 6.53KB 44% 291M
使用池,不固定大小 2 10s 5 2000 0.073247 6.50KB 50% 560M
使用池,不固定大小 2 10s 5 4000 0.077863 6.47KB 52% 1.08G
固定核心数1000+不限非核心数 2 10s 5 4000 0.046725 7.4KB 90% 883M
固定核心数2000+不限非核心数 2 10s 5 4000 0.045968 6.8KB 66% 962M
固定核心数1000+阻塞等待 2 10s 5 4000 0.048416 6.52KB 70% 326M
固定核心数2000+阻塞等待 2 10s 5 4000 0.04729 6.52KB 72% 652M
固定核心数1000+阻塞等待 4 10s 5 4000 0.046915 6.52KB 100% 348M
固定核心数2000+阻塞等待 4 10s 5 4000 0.047518 6.52KB 102% 649M
固定核心1000+非核心2000+阻塞等待 2 10s 5 4000 0.048806 7.2KB 84% 682M

【报表分析】

脚本一次调用耗时小于0.2ms。

池化与非池化对比:

  1. cpu占用相差大,创建虚拟机非常消耗cpu资源。
  2. 每调用一次脚本平均耗时相差0.1ms,池化可以提升性能。
  3. 相同并行度,总的内存占用相差大,池化可以减少内存占用。

池化不固定池大小:

  1. 支持1000并行,需要291M内存,每增加一倍并行数,内存增加一倍。
  2. 并行度越高,平均耗时越高。

池化固定核心数+不限制非核心数:相同并行度,核心数越低,cpu占用越高,平均耗时越高。

池化固定核心数+阻塞等待:相同并行度,核心数越低,平均耗时越高,但总的内存占用越低。

池化固定核心数+不限制非核心数 vs 池化固定核心数+阻塞等待:同2000核心数,4000并发,阻塞等待比非阻塞等待,内存占用低,但平均耗时高。

优化方案

  1. 选择池化方向,可减少cpu消耗、内存占用,降低平均耗时。
  2. 池最大大小要限制,避免突增流量导致OOM。
  3. 可以考虑池固定核心数+最大非核心数+阻塞等待策略优化池性能(根据单进程平均并发量、内存情况,配置固定核心数+最大非核心数):
    1. 当并发度<核心数时,性能最佳;
    2. 当并发度在最大非核心数范围内,可减少阻塞等待;
    3. 当并发度超过最大核心数,可控制最大内存占用。
  4. 为避免脚本规则已卸载等情况下,池还继续占用内存,应实现空闲检查释放机制。
#中间件

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

vSpear 2024-11-01

能否分享一下您的 lua 池相关代码呢

文章推荐

Go写的文件上传中间件内存泄露问题排查

用go开发的一个文件上传中间件,由于依赖了ceph这个c库,早期通过pprof排查,怀疑内存泄露在c层,而项目依赖ceph,于是就怀疑是ceph的问题。但通过使用jemalloc排查后,并未发现ceph有什么异常。最后使用最笨的方法,定位到github.com/chai2010/webp库存在内存泄露bug。

组件和框架初始化顺序背后隐藏的线上故障

一个微服务可能引入非常多的SDK,例如消息中间件kafka的组件、RPC框架dubbo、定时任务调度平台xxl-job的组件,以及提供web服务的jetty/tomcat等。这些组件的初始化是不确定的,那么假如启动初始化过程中,其中某个组件初始化失败了,会发生什么?

S3文件上传403问题排查

业务反馈有个iOS设备上传出现403问题,打点日记只能看到403,没有详细的错误信息。开了s3的日记后,也只是看到403 AccessDenied。

被开源组件坑惨了,文件上传到MFS后MD5不一致

moosefs没有关于底层自定义二进制通信协议的文档,且各大版本api上差异很大。我们在github上找到一个go语言实现api操作moosefs的开源库,但不幸的是,这个组件实现的api版本很低,在测试阶段,我们发现上传文件到moosefs后,moosefs上存储的文件的md5与本地原文件md5不一致,如果是图片,能很明显的看出少了一块像素。

基于dubbo-go二次开发荔枝RPC框架

本文介绍如何基于dubbo-go的扩展点,二次开发支持公司内部rpc协议,支持java项目和go项目的互相调用。

如何开发一个Java微服务项目脚手架

如果没有脚手架,每当需要创建一个新的project,我们通常会选择基于现有的project复制一份,然后修改修改。