技术说明 2
正如 “关于”页面 中明确指出的,我们 Lua 实现的目标之一是低嵌入成本。这意味着两件事:首先,将 Lua 嵌入到应用程序中应该很容易;其次,Lua 的附加代码不应该太大。
第一个要求通过 Lua 的 C API 的简单性得到满足。第二个要求通过如下演示得到满足。
以下是一些 Lua 4.0 的数字,在运行 Linux 的 Intel 机器中使用默认选项编译(其他平台的数字将不同,但相对而言可能大致相同)
% size liblua.a
text data bss dec hex filename
3328 0 0 3328 d00 lapi.o (ex liblua.a)
4054 0 0 4054 fd6 lcode.o (ex liblua.a)
3031 0 0 3031 bd7 ldebug.o (ex liblua.a)
2372 0 0 2372 944 ldo.o (ex liblua.a)
574 0 0 574 23e lfunc.o (ex liblua.a)
1874 0 0 1874 752 lgc.o (ex liblua.a)
4909 0 0 4909 132d llex.o (ex liblua.a)
225 0 0 225 e1 lmem.o (ex liblua.a)
734 0 0 734 2de lobject.o (ex liblua.a)
7634 0 0 7634 1dd2 lparser.o (ex liblua.a)
598 0 0 598 256 lstate.o (ex liblua.a)
953 0 0 953 3b9 lstring.o (ex liblua.a)
1651 0 0 1651 673 ltable.o (ex liblua.a)
0 0 0 0 0 ltests.o (ex liblua.a)
1495 0 0 1495 5d7 ltm.o (ex liblua.a)
2491 0 0 2491 9bb lundump.o (ex liblua.a)
5487 0 0 5487 156f lvm.o (ex liblua.a)
336 0 0 336 150 lzio.o (ex liblua.a)
% size liblualib.a
text data bss dec hex filename
1437 0 0 1437 59d lauxlib.o (ex liblualib.a)
5619 0 0 5619 15f3 lbaselib.o (ex liblualib.a)
1674 0 2 1676 68c ldblib.o (ex liblualib.a)
5288 0 0 5288 14a8 liolib.o (ex liblualib.a)
2301 0 0 2301 8fd lmathlib.o (ex liblualib.a)
6209 0 0 6209 1841 lstrlib.o (ex liblualib.a)
在此列表中,text 实际上是以字节为单位的代码大小。我们得出结论,Lua 核心 (liblua.a) 占用 41746 字节,Lua 标准库 (liblualib.a) 占用 22528 字节。因此,整个 Lua 代码占用 64274 字节,或不到 63K。换句话说,Lua 在应用程序中的影响是 63K 的附加代码,这非常小。(当然,Lua 在运行时将使用内存——但具体使用多少取决于应用程序。)
63K 在如今机器拥有数兆字节主内存的时代似乎非常少,但对于尝试在微波炉或机器人中使用 Lua 的人来说,它们可能会有所不同。因此,让我们看看如何将这 63K 减少到更少。(即使您不在嵌入式系统中使用 Lua,您也可能从以下描述中学到一些东西。)
首先要删除任何不需要的标准库。例如,大多数应用程序可能不需要 ldblib.o,而 liolib.o 对于微波炉来说可能没有意义。但是,删除标准库并不能让您走得太远,因为它们本来就小。因此,让我们再次查看核心的数字,但现在按大小排序
text %core %whole filename 0 0% 0% ltests.o 225 1% 0% lmem.o 336 1% 1% lzio.o 574 1% 1% lfunc.o 598 1% 1% lstate.o 734 2% 1% lobject.o 953 2% 1% lstring.o 1495 4% 2% ltm.o 1651 4% 3% ltable.o 1874 4% 3% lgc.o 2372 6% 4% ldo.o 2491 6% 4% lundump.o 3031 7% 5% ldebug.o 3328 8% 5% lapi.o 4054 10% 6% lcode.o 4909 12% 8% llex.o 5487 13% 9% lvm.o 7634 18% 12% lparser.o此列表告诉我们,解析模块——词法分析器
llex.o、解析器 lparser.o 和代码生成器 lcode.o——占核心(和整体)的 40%。因此,它们是删除的主要候选对象。不需要在运行时编译 Lua 代码的应用程序不需要解析模块。
我们设计代码时,考虑到了轻松移除这三个模块。只有一个模块 (ldo.o) 调用解析器,该解析器只有一个公共函数 (luaY_parser)。除了在 lua_open 中使用的初始化函数 (luaX_init) 外,调用词法分析器的唯一模块是解析器。除了 ldebug.o 从 lcode.o 使用数组 luaK_opproperties 外,调用代码生成器的唯一模块是解析器。因此,要移除解析模块,只需将以下代码添加到应用程序即可(可以从 lua/src/luac/stubs.c 中提取该代码,其中默认情况下禁用该代码)
#include "llex.h"
#include "lparser.h"
void luaX_init(lua_State *L) {
UNUSED(L);
}
Proto *luaY_parser(lua_State *L, ZIO *z) {
UNUSED(z);
lua_error(L,"parser not loaded");
return NULL;
}
要移除代码生成器,还需要添加 #include "lcode.h",并将 luaK_opproperties 从 lcode.c 复制到此代码中。
包含上述代码的应用程序不会链接解析模块,并且尝试加载 Lua 源代码会生成错误。但是,您可能会问,应用程序如何加载 Lua 代码?答案是:通过加载预编译块,而不是源代码。预编译块使用 将包含解析模块的 luac 创建,但它是一个外部应用程序。加载预编译块的模块是 lundump.o,它足够小。
尽管 lua_dofile 和 dofile 会自动检测预编译块,但一种方便的方法是将 lua_dobuffer 与静态链接到应用程序的预编译块一起使用(您会发现 lua/etc/bin2c.c 对此很有用),因为嵌入式系统甚至没有文件系统。(这是一个快速解决方案,但会增加应用程序的大小,并且对您来说可能过于不灵活。)
移除解析模块后,我们只剩下一个只有 25296 字节的核心,略大于 24K。对于像 Lua 这样的强大语言来说,这确实非常小!还要注意,这种缩减是在不牺牲任何语言特性和不触及源代码的情况下完成的;我们只需要链接器的少量帮助。
此说明重点介绍了减少 Lua 库添加到应用程序的代码量。需要此功能的应用程序可能还更愿意对 Lua 中的数字使用整数,而不是浮点数。(微波炉需要浮点数吗?)这应该很容易做到,如 lua/config 中所述,但详细信息可能会在另一份 LTN 中讨论。