正如 “关于”页面 中明确指出的,我们 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 中讨论。