Lua脚本的内存使用

Lua API 提供丰富的可能使用户实现自己的功能, 但是它也受到诸多限制, 其中最大的限制应该是RAM使用量。 应该有很多开发者知道, 与可以使用大量RAM的PC上编程不同,在微处理器上编程将收到微处理器极为苛刻的内存限制。这个项目的微处理器的RAM总量为128KBytes,而能供用户lua脚本使用的RAM目前大概为80KBytes。如果人们像在PC上一样使用RAM, RAM将会很快变得不够用。 但是,通过遵守一些规则,仍然能写出实现复杂功能的lua脚本。

内存使用

这个项目的内存大概分为以下几个部分:

  • 静态分配的BSS段,其中包括中断堆栈,USB缓冲区,屏幕缓冲区和其他频繁访问的缓冲区,这一部分目前大约占用26KBytes。

  • 表头UI的堆栈,后台服务(数据采集,lua解释器,快充触发),PC端App的服务,它们由RTOS创建,这一部分占用的大小大概为20KBytes。

  • 大约80Kbytes的空闲内存可以由您的lua脚本使用

监控内存使用

您可以在上位机的Lua脚本面板中查看内存的使用情况。 您也可以在您的脚本中通过: sys.gFreeHeap() 来获取当前的空闲堆大小 sys.gFreeHeapEver() 来获取历史上最低的空闲堆大小 sys.gTotalHeap() 来获取堆总大小

Lua语言的考虑

乍听之下,您似乎可以轻松地使用80KBytes RAM实现很多功能,毕竟表头本身只用了46KB RAM实现所有功能,更别提您可以直接调用它们提供的API。

但是我们需要考虑另一个因素,表头的固件是完全由C实现的,而您的脚本是由lua写的,lua是一款动态的高级的编程语言。它在提供极其方便简单的编程的同时,其内存消耗也是实现同样功能的C代码的几倍。例如一个number型(这在固件中被设置为float型)将消耗22字节RAM,是在C中的五倍。

重要的是内存的峰值使用量

在强调稳定性的嵌入式系统中,所有内存分配的失败都是不可接受的。表头被设计为当内存分配失败发生时,停止所有功能,并弹出内存失败的对话框,除非重新上下电,没有任何机会(也不应该有)从这种错误中恢复。如上所述,当内存分配失败时,您没有任何机会尝试释放您的资源,或者是弹出对话框。这意味着您必须保证您的峰值内存使用量跨过红线。

建议1: 将您的程序模块化

当您执行一个脚本时,lua解释器先将整个文件读进内存并编译为字节码,添加变量到堆上。这意味着在某个时间点,您的脚本和脚本的字节码将同时存在于内存中。回忆上面所述的内容,重要的是内存的峰值使用量。尽管在转换为字节码后,您的脚本已经不必要在内存中,并且也会被解释器释放掉,但是如果脚本和脚本的字节码同时存在时内存不足,您的程序也将失效。

现在考虑您一次性加载一个40K字节的脚本,并且让我们假设它将产生40K字节的字节码。内存使用的峰值将为80K字节,这已经是我们能够使用的全部RAM。解决这个问题的一个方法是,您将您的程序分为多个模块,在需要使用时使用lua的require函数加载它们。

现在假设您将40K字节的脚本分为4个10K字节的脚本,并假设他们各产生10K字节的字节码。即便加载所有模块,内存使用的峰值仅为10K+40K=50K字节。模块化代码是编写代码的好实践,这么做也对内存使用有所帮助。

建议2:将您的模块预编译

尽管建议1在多数情况下已经能将模块编译的损耗减小到可接受范围,但当您仍需要更多RAM时,您可以完全去掉由编译行为造成的内存峰值。执行sys.gByteCode({path},[option]),您可以将您的源文件转化为字节码。一个以*.lc为后缀的文件将被输出在输入文件同目录内。使用sys.RESERVE_DEBUG_INFO选项,您可以在预编译时保留调试信息。

下面列出一组数据,使您对这么做能节省的RAM有一个大致了解: 测试源文件UI_Demo,大小5.23KB: | 情形 | 峰值内存使用量 | | —— | —— | | 直接执行*.lua | 29.64KB| | *.lc RESERVE_DEBUG_INFO | 19.52KB | | *.lc STRIP_DEBUG_INFO | 16.00KB |