优化前后内存占用的对比
优化前 | 优化后 |
---|---|
80.9 MB | 23.2 MB |
测试样例
;; test.cms
.program
.entry main
.func Math#add
.stvarb 2, cms#int64
.arg 2
call %res, cms#int64#+, %1 %2
ret
.func main
.stvarb 2, cms#int64
.stvarb 1, cms#int64
load %1, 5, cms#int64
load %2, 6, cms#int64
call %3, Math#add, %1 %2 ;; 40000 个
ret
文件总共 1.14 MB 。
在程序执行前、执行后各插入一个 system("pause");
记录其时的内存占用。
优化前结果
使用 Visual Studio 2017 的 x64 Release 模式编译,在 Windows 10 下运行结果:
Time | 备注 | 占用内存 |
---|---|---|
第一次 pause | parser + compiler | 41.6 MB |
第二次 pause | parser + compiler + runtime | 80.9 MB |
结果分析
- parser + compiler 部分占用了 41.6 MB
- runtime 部分占用了 39.3 MB
理论上,由于全局使用静态寄存器,在 Math#add
函数调用结束后应该能够销毁。而使用性能探查器进行测试时发现,内存占用只增不减。所以应该是内存泄漏无误。
所以先对 runtime 部分进行优化。
第一次优化(对 runtime 进行优化)
首先观察是哪个地方出现的泄漏。
Math#add
函数被调用了 40000 次,而它调用了 cms#int64#+
函数。cms#int64#+
函数是内嵌的,它使用的寄存器都是静态的,理论上不存在分配问题。
因此将 Math#add
换成 cms#int64#+
进行测试。
测试结果如下:
Time | 备注 | 占用内存 |
---|---|---|
第一次 pause | parser + compiler | 41.5 MB |
第二次 pause | parser + compiler + runtime | 41.5 MB |
可见在进行指令函数的调用时,会产生大量的内存泄漏。
理论上,对 cms#int64#+
进行调用不应该占用内存。而实际测试发现,确实没有可见的内存占用。因此对内置函数的调用运行状况良好。
下面对指令函数的调用进行分析。
指令函数调用时会自动创建一个 LocalEnvironment 。
创建使用 new LocalEnvironment
,并加入到 _subenv_set
中,同时执行 Call
。
_subenv_set
存储的是 std::shared_ptr<Environment>
,当该环境不被使用,移除节点,即可销毁环境。
但是并没有在结束时并没有将节点移除,因此存在非常严重的泄漏问题。
在 Call 指令加入节点后、调用子环境后,移除该节点,即可解决这个泄漏问题。
结果如下:
Time | 备注 | 占用内存 |
---|---|---|
第一次 pause | parser + compiler | 41.7 MB |
第二次 pause | parser + compiler + runtime | 43.0 MB |
优化后,runtime 的内存占用从 39.3 MB 降到了 1.3 MB 。成果显著。
第二次优化(对 compiler 的优化)
现在,将 parser 与 compiler 之间插入一个新的 pause 进行测试:
Time | 备注 | 占用内存 |
---|---|---|
第一次 pause | parser | 8.4 MB |
第二次 pause | parser + compiler | 41.7 MB |
第三次 pause | parser + compiler + runtime | 42.8 MB |
占用:
- parser 8.4 MB
- compiler 33.3 MB
- runtime 1.1 MB
因为 compiler 占用了相当大的部分,所以先对此进行优化。
compiler 会编译生成大量的 lambda 。
call 指令包含一个 dst 和一组 src 。
在目前的版本中,会生成一个能够获取 DstData 的 lambda 和一个获取 SrcData 组的 lambda 。在 x64 下,对这两个进行 sizeof ,分别是 64 字节和 24 字节。
将 call 的 lambda 对外部变量的拷贝改成引用后,compiler 占用内存由 33.3 MB 变为了 4.9 MB 。(但是会无法运行。)
将获取参数列表与返回参数的两个保存了 lambda 的 std::function 改为结构体后,内存占用变为了 19.2 MB 。再将获取 call 所调用的函数由 lambda 变为在本体 lambda 内执行,内存占用变为了 13.5 MB 。
可以看出,目前编译为 lambda 的形式会造成内存占用过大的问题。因为修改这种形式需要大幅度修改,目前暂时只修改 call 相关的编译代码。
结果如下:
Time | 备注 | 占用内存 |
---|---|---|
第一次 pause | parser | 8.4 MB |
第二次 pause | parser + compiler | 21.9 MB |
第三次 pause | parser + compiler + runtime | 23.2 MB |
总结
本次优化,内存占用从最初的 80.9 MB 优化到了 23.2 MB 。共优化 57.7 MB 。
- 发现了 runtime 期间调用指令函数时创建 LocalEnvironment 时的内存泄漏问题。修复后优化了 38.0 MB 。
- 发现了直接编译为 lambda 内存过大的问题。由于改动幅度较大,准备重构 compiler 部分。目前的临时优化,优化了 19.6 MB 。
下一篇优化记录,将会在重构后进行。
附注
在优化期间还发现了文件占用时间太长的问题。顺手解决了。