Cnumber 补丁

lua-users home
wiki

介绍

CNUMBER 补丁(可在 LuaPowerPatches 获取)提供了一种更有效的机制,用于从 Lua 访问数值 C 变量。例如,考虑以下 C 变量

  double foo = 10.0;
您可能希望在 Lua 中将其作为常规变量访问
  foo = foo + 1
在 Lua 5.1 中,这里主要的方法是将您自己的元表附加到全局环境表,然后将 "__index" 和 "__newindex" 函数附加到该元表。这两个函数处理对 "foo" 变量的获取和设置事件,并且必须在 C 中实现,因为数据存储在那里。这工作得相当好,但是如果您非常关心效率(就像我一样),这个过程效率有点低。首先,它使用全局变量,全局变量比局部变量效率低,因为全局变量名是在运行时通过查找环境表来解析的,而局部变量的位置是在编译时计算的。在 Lua 中,即使对于字符串键(例如变量名),表查找也相当高效,因为字符串是“内部化”的,并且具有预先计算的哈希值,但它仍然是一个惩罚。此外,这需要 Lua 元方法调用,这是一个额外的惩罚。另一个问题,更多的是美学上的,是修改整个环境的行为以改变单个变量的行为,这似乎有点扭曲。请注意,此类变量不能存在于其环境之外,因为行为是附加到环境而不是附加到值本身。

另一种方法是公开来自 C 的 getter 和 setter 访问器函数(或方法),并让 Lua 代码直接调用这些函数

  set_foo(get_foo() + 1)
这很丑陋,而且效率也不高。例如,"set_foo" 和 "get_foo" 都是全局变量,必须在运行时解析,并且都涉及 Lua 函数调用。[PIL] 中给出了类似的方法。一个优点是行为附加到值本身,因此不需要更改环境。

另一种建议的方法可能是将数值 C 变量实现为重量级用户数据,并附加一个带有适当事件的元表,以便它们的行为像普通值一样。但是,这也没有提供有效的或语法上干净的解决方案。您可以使用元表事件更改值的行為,但您不能使它们的行为完全像普通值一样。例如,没有“赋值”元表事件可以允许此类值充当左值(与 C++ 不同)。

此补丁通过提供所谓的“CNUMBER”来解决这些问题。CNUMBER 是在 Lua 中公开的变量,它是在 C 中实现的。在某种程度上,它类似于 Lua CFUNCTION,它是在 Lua 中公开并在 C 中实现的函数。对 CNUMBER 的访问几乎与对 Lua 局部变量的访问一样快,这并不奇怪,因为 CNUMBER 的处理方式与局部变量类似(忽略闭包和作用域)。

用法

要将 CNUMBER 注册到 Lua,您必须定义三个函数,如下所示

double foo = 0.0;
double foo2 = 0.0;
double foo3 = 0.0;

    int
iscnumber(lua_State * L, const char * varname, int * id) {
    if(strcmp(varname, "foo") == 0) {
        *id = 1; return 1; /* yes */
    }
    else if(strcmp(varname, "foo2") == 0) {
        *id = 2; return 1; /* yes */
    }
    else if(strcmp(varname, "foo3") == 0) {
        *id = 3; return 1; /* yes */
    }
    else {
        return 0; /* no */
    }
}

    double
getcnumber(lua_State *L, int id) {
    switch(id) {
        case 1: { return foo; }
        case 2: { return foo2; }
        case 3: { return foo3; }
        default: assert(0);
    }
    return 0;  /* should not occur */
}

    void
setcnumber(lua_State *L, int id, double value) {
    switch(id) {
        case 1: { foo  = value;  break; }
        case 2: { foo2 = value;  break; }
        case 3: { foo3 = value;  break; }
        default: assert(0);
    }
}
第一个函数 iscnumber 由 Lua 解析器调用。它允许 Lua 确定给定标识符是否应该被解释为 CNUMBER。如果该函数将名称识别为 CNUMBER,则该函数会为该标识符分配一个 ID 以供日后使用。该 ID 是一个 18 位无符号数(此大小受 Lua 5.1 的操作码限制),其含义仅对 C 代码已知。当 Lua 识别 CNUMBER 时,它会生成新的特殊 GETCNUMBER 和 SETCNUMBER 操作码,并将该 ID 与其一起存储。请注意,每个 CNUMBER 只解析一次(在编译时),因此它在运行时的访问效率更高。

getcnumber 和 setcnumber 函数在运行时处理这两个操作码时被调用,分别用于获取或设置变量。每个函数都传递前面提到的 ID。这些函数可以使用对该 ID 的简单查找,如上所述,或者它可以是更复杂的东西(例如,索引到数组)。C 代码中不一定实际存在 double 变量。

这三个函数必须在解析之前注册,如下所示

  lua_setcnumberhandler(L, iscnumber, getcnumber, setcnumber);

您可以通过将它们全部设置为 NULL 来取消注册它们。

基准测试

基准测试是在此简单示例的 10 次迭代中提供的

  for n=1,10000000 do
    foo = foo + 1
  end
结果比较了 CNUMBER 与两种系统上的各种方法
  Linux 2.4.20 (virtualized) / GCC / Intel(R) Xeon(TM) 3 GHz
  == Run Times (sec)
  LOOP           : 8.100000
  CNUMBER        : 23.490000 *
  LOCAL          : 15.200000
  GLOBAL         : 37.150000
  METATABLELOCAL : 99.980000
  METATABLETABLE : 198.790000
  CFUNCTION      : 72.010000

  WinXP / GCC Ming / Intel(R) P4 3 GHz
  == Run Times (sec)
  LOOP           : 3.361000
  CNUMBER        : 13.702000 *
  LOCAL          : 8.579000
  GLOBAL         : 25.640000
  METATABLELOCAL : 72.858000
  METATABLETABLE : 150.266000
  CFUNCTION      : 52.672000
每种方法的描述

如所示,CNUMBER 几乎与 Lua 局部变量一样快,并且比 Lua 全局变量更快。它们比 METATABLETABLE 和 CFUNCTION 方法快得多,否则需要从 Lua 公开 C 变量。METATABLELOCAL 是 METATABLETABLE 的一个更简单的版本(省略了第二个表查找),但实际上没有用,仅用于比较。此基准程序包含在补丁中(cnumber.c)。

在某个“现实生活”应用程序中,CFUNCTION 将运行时减少了约 40%。

设计说明

CNUMBER 实现具有一些重要特性

此补丁的可能扩展包括以下内容

--DavidManura


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2006 年 2 月 21 日凌晨 12:02 GMT (差异)