lua-users home
lua-l archive

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


It was thus said that the Great Mason Bogue once stated:
> Hi,
> 
> Currently, as I understand it, the metatable of a Lua value is a Lua table
> with string keys "__index", "__add", "__tostring", etc. These string keys
> are stored in a hash table and incur a hash-table lookup whenever accessing
> a metatable, i.e., if I write something like
> 
> t = {}
> setmetatable(t, {__index = {a = function() return "b" end}})
> 
> then upon calling t:a(), Lua will perform *three* hash-table lookups: one
> check for 'a' in 't', one check for __index in t's metatable, and finally a
> check for 'a' in the method table.
> 
> But accessing __index by a string lookup in a hash table seems
> unnecessarily complicated, when for metatables we are always interested in
> the same 20 or so keys. Instead, Lua could simply define global values:
> 
> __index = 1
> __newindex = 2
> __tostring = 3
> -- and so on
> 
> and then we write the metatables like this:
> 
> t = {}
> setmetatable(t, {[__index] = {a = function() return "b" end}})
> 
> Now we have exchanged a hash-table lookup for an array lookup. 

  The issue here is when Lua uses the array portion of a table, which is for
a sequence, which is defined as a series of values where the keys start with
1, and each subsequent value isn't nil.  So if I have a metatable with
(using your example) __index, __newindex and __tostring, then yes, it can
probably use the array.  But if I only use __index and __tostring, that is
no longer a Lua sequence (since index 2 is missing) and thus, goes into the
hash portion.

  The other issue is C modules which use metatables.  Right now, to declare
the metatable you use the type luaL_Reg, which has strings for keys, like:

	static luaL_Reg const m_addr_meta[] =
	{
	  { "__index"           , addrlua___index       } ,
	  { "__tostring"        , addrlua___tostring    } ,
	  { "__eq"              , addrlua___eq          } ,
	  { "__lt"              , addrlua___lt          } ,
	  { "__le"              , addrlua___le          } ,
	  { "__len"             , addrlua___len         } ,
	  { NULL                , NULL                  }
	};

	luaL_newlib(L,m_addr_meta);

(pulling this from a C module I wrote).  You would now have to write code
like:

	lua_createtable(L,0,0);
	lua_pushcfunction(L,addrlua___index);
	lua_seti(L,-2,META_INDEX);
	lua_pushcfunction(L,addrlua___tostring);
	lua_seti(L,-2,META_TOSTRING);

or a new structure would have to be created to handle integer keys.  Maybe:

	static luaL_iReg const m_addr_meta[] =
	{
	  addrlua___index,
	  NULL,
	  addrlua___tostring,
	  addrlua___eq,
	  addrlua___lt,
	  addrlua___le,
	  addrlua___len,
	  NULL, /* um ... how to mark the end? */
	};

	luaL_newlib(L,m_addr_meta); /* maybe? */

  But then that assumes one knows the index.  I suppose one could do:

	static luaL_iReg const m_addr_meta[] =
	{
	  { META_INDEX , addrlua___index },
	  { META_TOSTRING, addrlua___tostring },
	  /* ... */
	};

  But out of order setting of the table won't result in the use of the array
portion.  This also assumes that other functions won't be part of the
metatable.  Another metatable from my code:

	static luaL_Reg const m_sock_meta[] =
	{
	  { "__tostring"        , socklua___tostring    } ,
	  { "__gc"              , socklua_close         } ,
	#if LUA_VERSION_NUM >= 504
	  { "__close"           , socklua_close         } ,
	#endif
	  { "__index"           , socklua___index       } ,
	  { "__newindex"        , socklua___newindex    } ,
	  { "peer"              , socklua_peer          } ,
	  { "addr"              , socklua_addr          } ,
	  { "bind"              , socklua_bind          } ,
	  { "connect"           , socklua_connect       } ,
	  { "listen"            , socklua_listen        } ,
	  { "accept"            , socklua_accept        } ,
	  { "recv"              , socklua_recv          } ,
	  { "send"              , socklua_send          } ,
	  { "shutdown"          , socklua_shutdown      } ,
	  { "close"             , socklua_close         } ,
	  { "_tofd"             , socklua__tofd         } ,
	  { "_dup"              , socklua__dup          } ,
	  { NULL                , NULL                  }
	};

  This case needs to be handled as well.

> The code is
> not really more complicated; you have to type only two more characters.
> Plus, many people will use an OOP library that abstracts away the whole
> metatable creation, so they wouldn't notice any difference. 

  Not all of us are enamored with OOP.

> Practically
> everything using metatables immediately gets faster. Other than backwards
> compatibility, I'm having trouble thinking of a downside.

  Have you actually implemented this and measured?  

  -spc