Lua 整数类型

14

我真的需要在Lua中有一个整数类型。

我的意思是定义通常运算符(/ * +等)并且表现得像整数的类型,内部表示并不重要。

用表格实现这样的事情非常简单,但问题在于,我尝试过了,性能非常糟糕(当然)。这是我的部分实现:

function num_op(a, b, calc_func)
    local atype = pytype(a)
    local btype = pytype(b)
    local a_val, b_val

    a_val = (atype == "Integer" or atype == "Double") and a[1] or a
    b_val = (btype == "Integer" or btype == "Double") and b[1] or b
    val = calc_func(a_val, b_val)

    if atype == "Integer" and btype == "Integer" then
        return Integer:create(val)
    else
        return Double:create(val)
    end
end

numeric_mt = { 
    __add = function(a, b)
        return num_op(a, b, function(a,b) return a + b end)
    end,

    __sub = function(a, b)
        return num_op(a, b, function(a,b) return a - b end)
    end,

    __div = function(a, b)
        return num_op(a, b, function(a,b) return a / b end)
    end,

    __mul = function(a, b)
        return num_op(a, b, function(a,b) return a * b end)
    end,

    __tostring = function(a)
        return tostring(a[1])
    end
}

-----------------------------
-- Integer type definition --
-----------------------------

Integer = {}
Integer_mt = table.copy(numeric_mt)
Integer_mt["__index"] = Integer

function Integer:create(value)
    local new_inst = {math.floor(value)}
    setmetatable(new_inst, Integer_mt)
    return new_inst
end

function Integer:className()
    return "Integer"
end
据我了解,主要的性能损失来自于非常频繁的分配内存。LuaJit 能够很好地优化操作函数,但对元表的分配无能为力。
有没有人认为通过自定义 C 实现和用户数据可以做得更好?或者说我的追求是不可能实现的?
注:我知道 Lua 没有整数。我也知道可以使用 math 库获得相同的结果。我想要的是在使用整数时具有完全的透明度,除了创建阶段以外。
编辑:我会在这里添加额外的信息,使所有内容仍然集中在一起。
@Mud:我需要在一定程度上拥有“透明”的混合算术运算,就像 Python/Ruby 等语言中一样,同时尽可能提高性能。我正在将 LuaJIT 作为编译器的目标,对于不支持 LuaJIT 的平台,我将使用普通的 Lua。这对于性能特征非常重要。
这意味着我希望能够这样做:
a = int(5) -- Integer value
b = int(2) -- Another Integer
c = 2      -- Double
d = a / b  -- == 2 , integer arithmetics
e = a / c  -- == 2.5, floating point arithmetics

我可以通过上面展示的实现方法来达到一定程度的目标。问题在于,每个数字都需要装箱,因此我正在减慢操作速度。我可以使用调试库来重载数字的元表,但是:

  • 我不知道这个功能在生产质量软件中可靠性如何。
  • 由于要使用(number):get()才能统一数字接口,因此它仍会降低数字的性能。

昨晚我自己在C中实现了整数。虽然它比我在常规lua中的天真实现和内联调用math.floor的方式有所改善,但在使用LuaJIT时却不太清晰,因为内联调用仍然比C实现快得多。

另一种解决方法是始终使用非装箱数字,并在编译器中使用某种类型传播来跟踪整数,并在需要时使用适当的内联操作。但是,这种解决方案的复杂性更大,而且有点违背使用Lua / LuaJIT作为后端的初衷。

我将尝试您的实现方法,但我怀疑它不会比LuaJIT中的内联调用更好。很可能我追求的目标(既具有double和整数的透明操作,又接近于luaJIT上的内联调用性能)根本不可能实现。非常感谢您的帮助。

@miky:谢谢,看起来很好,但我怀疑我无法将其打补丁到luaJIT中,如果我不能,它对我的目标就失去了所有兴趣。


2
是的,这是真的。试图使用任何东西来修补LuaJIT都是疯狂的行为,最好的方法是在纯Lua中实现整个功能(这就是Lua擅长的,它提供了工具...)。然后LuaJIT将能够对其进行优化(而不是使用C函数)。 - Michal Kottman
5个回答

16

为什么你需要它们?帮助你找到一个高效的解决方案的最佳方法是了解问题。你具体需要整数来做什么?

据我所知,主要的性能损失是(当然)非常多的分配。

嗯,你在每个操作上都创建闭包,而且我不明白为什么你需要一个Double类,因为Lua的数字类型已经是double了。你不能像这样做吗?

Integer = {}
local function val(o) return type(o) == 'number' and o or o[1] end
function Integer.__add(a,b) return Integer:create(val(a) + val(b)) end
function Integer.__sub(a,b) return Integer:create(val(a) - val(b)) end
function Integer.__div(a,b) return Integer:create(val(a) / val(b)) end
function Integer.__mul(a,b) return Integer:create(val(a) * val(b)) end
function Integer:__tostring() return tostring(self[1]) end
function Integer:create(value)
   return setmetatable({math.floor(value)}, Integer)
end


-- test
a = Integer:create(15.34)
b = Integer:create(775.34433)
print((a*10/2+b-3)/3*a+b) --> 5005
有人认为使用自定义的C实现和用户数据(userdata)可以更好地完成这个任务,你认为这种方式可行吗?
是的,C语言实现应该会更快,因为你不需要为每个整数创建一个表格;你的用户数据可以直接是一个int*。这也将消除对floor函数的需求。
编辑:我编写了一个测试用的C实现,它比本文中介绍的Lua实现快了约5倍。
编辑2:链接到C代码已经失效,所以我在硬盘上找到了一些代码,可能是那个链接里的代码,也可能不是。我知道它可以编译到Lua 5.1上,但除此之外我不确定它是否是一个技术上有效的实现。太久没写过Lua C代码了。
#include <math.h>
#include "lua.h"
#include "lauxlib.h"

/* get value of Integer userdata or Lua number at index, or die */
static int checkintX (lua_State *L, int index) {
   int result = 0;
   if (lua_isuserdata(L, index) && luaL_checkudata(L, index, "integer")) {
      result = *(int*)lua_touserdata(L, index);
   } else if (lua_isnumber(L, index)) {
      double val = lua_tonumber(L, index);
      int x = (int) val;
      result = (int) lua_tonumber(L, index);
   } else {
      lua_pushstring(L, "Invalid operand. Expected 'integer' or 'number'");
      lua_error(L);
   }
   return result;
}

static int checkint(lua_State* L, int index) {
   int result = checkintX(L, index);
   return result;
}

static int create_integer(lua_State* L, int val) {
   int* ud = lua_newuserdata(L, sizeof(int));
   *ud = val;
   luaL_getmetatable(L, "integer");
   lua_setmetatable(L, -2);
   return 1;
}

static int int_new (lua_State* L) { return create_integer( L, checkint(L,1) ); }
static int int_add (lua_State* L) { return create_integer( L, checkint(L,1) + checkint(L,2) ); }
static int int_sub (lua_State* L) { return create_integer( L, checkint(L,1) - checkint(L,2) ); }
static int int_mul (lua_State* L) { return create_integer( L, checkint(L,1) * checkint(L,2) ); }
static int int_div (lua_State* L) { return create_integer( L, checkint(L,1) / checkint(L,2) ); }
static int int_mod (lua_State* L) { return create_integer( L, checkint(L,1) % checkint(L,2) ); }
static int int_pow (lua_State* L) { return create_integer( L, (int) pow( checkint(L,1), checkint(L,2) ) ); }
static int int_unm (lua_State* L) { return create_integer( L, -checkint(L,1) ); }
static int int_eq  (lua_State* L) { lua_pushboolean( L, checkint(L,1) == checkint(L,2) ); return 1; }
static int int_lt  (lua_State* L) { lua_pushboolean( L, checkint(L,1) <  checkint(L,2) ); return 1; }
static int int_le  (lua_State* L) { lua_pushboolean( L, checkint(L,1) <= checkint(L,2) ); return 1; }
static int int_tostring (lua_State* L) {
   lua_pushnumber(L, checkint(L,1));
   lua_tostring(L, -1);
   return 1;
}

int __declspec(dllexport) __cdecl luaopen_integer (lua_State* L) {
   static const struct luaL_reg integermt[] = {
      { "__add", int_add },
      { "__sub", int_sub },
      { "__mul", int_mul },
      { "__div", int_div },
      { "__mod", int_mod },
      { "__pow", int_pow },
      { "__unm", int_unm },
      { "__eq",  int_eq  },
      { "__lt",  int_lt  },
      { "__le",  int_le  },
      { "__tostring", int_tostring},
      NULL, NULL
   };
   luaL_newmetatable(L, "integer");
   luaL_openlib(L, NULL, integermt, 0);

   lua_register(L, "int", int_new);

   return 0;
}

嘿,mud,感谢你的回答,我已经在原问题中回复了。 - raph.amiard
2
我接受了你的答案,因为即使最终它没有解决我的问题,它仍然回答了我的问题。虽然当使用C库时,luaJIT会退回到解释器,所以最终使用C库并不是一个好的解决方案。 - raph.amiard
我写了一个测试C实现,它比这篇文章中介绍的Lua实现快了约5倍。链接似乎已过期@Mud - Rom's
@Rom,我在我的磁盘上找到了一些旧代码,可能是那个链接上的代码(太久远了,我记不清了)。我已经将其添加到帖子中。 - Mud

8

4

如果你只想处理整数,你可以在luaconf.h中定义#define LUA_NUMBER int


2

1
你可以尝试LNUM补丁,它修改了Lua核心以添加整数数字类型,如32位和64位整数以及双精度浮点数。它还支持复数。

从自述文件中可以看出:“使用补丁不会以任何方式改变Lua的外部行为”。所以这绝对不是我想要的。 - raph.amiard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接