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,它是在C中实现的并在Lua中公开的函数。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的opcodes限制),其含义仅为C代码所知。当Lua识别CNUMBER时,它会生成新的特殊GETCNUMBER和SETCNUMBER opcodes,并将其ID存储起来。请注意,每个CNUMBER只解析一次(在编译时),因此在运行时访问它更有效。

在处理这两个opcodes时, getcnumber和setcnumber函数会在运行时被调用,分别获取或设置变量。每个函数都会收到前面提到的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


RecentChanges · preferences
编辑 · 历史
最后编辑时间:2006年2月20日,下午6:02 GMT (差异)