【CVM】【2018.3.16】CVM 的内存占用优化(一)

2018/03/16 cvm 优化

优化前后内存占用的对比

commit

优化前 优化后
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 。

下一篇优化记录,将会在重构后进行。

附注

在优化期间还发现了文件占用时间太长的问题。顺手解决了。

Search

    Table of Contents