lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Fri, Dec 6, 2013 at 10:16 AM, dcharno <dcharno@comcast.net> wrote:


> On Dec 5, 2013, at 8:11 PM, Philipp Janda <siffiejoe@gmx.net> wrote:
>
> Am 05.12.2013 19:59 schröbte Tom N Harris:
>> On Thursday, December 05, 2013 09:45:06 AM Marc Lepage wrote:
>>> I guess I'd still be looping (or if-else-if-ing) over a list of pointers
>>> for which to compare, if I can't statically embed that dispatch as a switch
>>> statement (the pointers vary at runtime). Better than a strcmp, but now I'm
>>> thinking a table of string-to-integer is probably going to be best.
>>
>> The cost of building the table may dominate runtime if the function is called
>> infrequently. So perhaps you'd want to only memoize strings that are being
>> used. And don't forget bsearch if you can promise the C array will be sorted.
>
> Micro benchmark:
> *   10 different options
> *   each option name is 10 bytes long.
> *   2000000 calls for each option name (so 20 million calls total)
>
> luaL_checkoption    1.42    seconds
> bsearch + memcmp    1.24    seconds
> bsearch + strcmp    1.29    seconds
> upvalue table    0.94    seconds

I always used gperf for this, but upvalue beats it. Interestingly, strcmp was faster than memcmp on my machine.



What I did is defined a simple struct (like luaL_reg) to map string name to integer value:

struct LUT
{
    const char* name;
    int value;
};


Then, a simple function that takes a list of those entries, creates two tables in the registry, returning those reference indices. The tables are for getting (mapping integer to string) and setting (mapping string to integer).

void reg_lut(lua_State* L, const LUT* lut, int& get, int& set)
{
    lua_newtable(L); // set
    lua_newtable(L); // get
    for (; lut->name; ++lut)
    {
        lua_pushstring(L, lut->name);
        lua_pushinteger(L, lut->value);
        lua_pushvalue(L, -1); // value
        lua_pushvalue(L, -3); // name
        lua_settable(L, -5); // get
        lua_settable(L, -4); // set
    }
    get = luaL_ref(L, LUA_REGISTRYINDEX);
    set = luaL_ref(L, LUA_REGISTRYINDEX);
}


Then, I can define simple tables of entries like:

static const LUT lut_foo[] =
{
    { "foo", 0 },
    { "bar", 1 },
    { "baz", 2 },
    { NULL, 0 }
};


Register it (once at program start) with a call like:

static int REF_FOO_GET, REF_FOO_SET;
...
reg_lut(L, lut_foo, REF_FOO_GET, REF_FOO_SET);


Using the set table, I can get the integer value for a string using a function like:

// lookup value from referenced LUT using string key at top of stack
// and default integer value d
static int value(lua_State* L, int ref, int d)
{
    int value = d;
    if (!lua_isnil(L, -1))
    {
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        lua_insert(L, -2);
        lua_rawget(L, -2);
        if (lua_isnil(L, -1))
        {
            return luaL_error(L, "invalid value");
        }
        value = lua_tointeger(L, -1);
    }
    return value;
}


So my getters look like:

static int l_foo_get(lua_State* L)
{
    MyType* my = luaXXX_tomytype(L, 1);
    lua_rawgeti(L, LUA_REGISTRYINDEX, REF_FOO_GET);
    lua_pushinteger(L, my->getFoo());
    lua_rawget(L, 2);
    return 1;
}


And my setters look like:

static int l_foo_set(lua_State* L)
{
    MyType* my = luaXXX_tomytype(L, 1);
    my->setFoo(value(L, REF_FOO_SET, 0));
    return 0;
}


That seems to work well enough. Mapping tables stored in the registry. Would it be beneficial to use upvalues instead? (Pros/cons? Performance?) I haven't really done upvalues from C, I will check the docs.

Any suggestions/improvements?

Marc