# HG changeset patch # User jbe # Date 1407755929 -7200 # Node ID 0014a7c22013b215d8fdffa4cdf8710012eae3d3 # Parent 33c8f7029cfa22bf3c6801d719620db141ad03d0 Improved performance of JSON library by storing shadow tables directly via a lightuserdata key instead of using ephemeron tables diff -r 33c8f7029cfa -r 0014a7c22013 libraries/json/json.c --- a/libraries/json/json.c Sun Aug 10 20:18:57 2014 +0200 +++ b/libraries/json/json.c Mon Aug 11 13:18:49 2014 +0200 @@ -12,27 +12,36 @@ // levels for JSON documents <= 2 GiB. #define JSON_MAXDEPTH (1024*1024*1024) -// generate dummy memory addresses that represents null values: -char json_nullmark; -#define json_isnullmark(L, i) (lua_touserdata((L), (i)) == &json_nullmark) -#define json_pushnullmark(L) lua_pushlightuserdata((L), &json_nullmark) +// define type JSON_LIGHTUSERDATA and +// generate dummy memory addresses for lightuserdata values: +#define JSON_LIGHTUSERDATA char +static struct { + JSON_LIGHTUSERDATA nullmark; // lightuserdata value represents a NULL value + JSON_LIGHTUSERDATA shadowtbl; // lightuserdata key for shadow table +} json_lightuserdata; + +// macros for special nullmark value: +#define json_isnullmark(L, i) (lua_touserdata((L), (i)) == &json_lightuserdata.nullmark) +#define json_pushnullmark(L) lua_pushlightuserdata((L), &json_lightuserdata.nullmark) + +// macros for getting and setting shadow tables +#define json_setshadow(L, i) lua_rawsetp((L), (i), &json_lightuserdata.shadowtbl) +#define json_getshadow(L, i) lua_rawgetp((L), (i), &json_lightuserdata.shadowtbl) +#define json_createproxy(L) lua_createtable((L), 0, 1) + +// generate additional dummy memory addresses that represent Lua objects +// via lightuserdata keys and LUA_REGISTRYINDEX: +static struct { + JSON_LIGHTUSERDATA objectmt; // metatable for JSON objects + JSON_LIGHTUSERDATA arraymt; // metatable for JSON arrays +} json_registry; // macros for usage of Lua registry: -#define JSON_REGENT char -#define JSON_REGPOINTER void * #define json_regpointer(x) (&json_registry.x) #define json_regfetchpointer(L, x) lua_rawgetp((L), LUA_REGISTRYINDEX, (x)) #define json_regfetch(L, x) json_regfetchpointer(L, json_regpointer(x)) #define json_regstore(L, x) lua_rawsetp(L, LUA_REGISTRYINDEX, json_regpointer(x)) -// generate dummy memory addresses that represent Lua objects -// via lightuserdata keys and LUA_REGISTRYINDEX: -static struct { - JSON_REGENT shadowtbl; // ephemeron table that maps tables to their corresponding shadow table - JSON_REGENT objectmt; // metatable for JSON objects - JSON_REGENT arraymt; // metatable for JSON arrays -} json_registry; - // returns the string "": static int json_nullmark_tostring(lua_State *L) { lua_pushliteral(L, ""); @@ -53,35 +62,33 @@ // determine is argument is given: if (lua_isnoneornil(L, json_convert_source_idx)) { // if no argument is given (or if argument is nil), - // create table with shadow table, and leave first table on top of stack: - json_regfetch(L, shadowtbl); + // create proxy table with shadow table, and leave proxy table on top of stack: + json_createproxy(L); lua_newtable(L); - lua_pushvalue(L, -1); - lua_newtable(L); - lua_rawset(L, -4); + json_setshadow(L, -2); } else { // if an argument was given, - // push its iterator function on stack position 2 if existent, - // else push null for normal tables: + // stack shall contain only one function argument: lua_settop(L, 1); - if (lua_getmetatable(L, json_convert_source_idx)) { - lua_getfield(L, -1, array ? "__ipairs" : "__pairs"); - if (lua_isnil(L, -1)) luaL_checktype(L, 1, LUA_TTABLE); - else if (lua_type(L, -1) != LUA_TFUNCTION) + // check if there is an iterator function in its metatable: + if (luaL_getmetafield(L, json_convert_source_idx, array ? "__ipairs" : "__pairs")) { + // if there is an iterator function, + // leave it on stack position 2 and verify its type: + if (lua_type(L, json_convert_iterator_idx) != LUA_TFUNCTION) return luaL_error(L, "%s metamethod is not a function", array ? "__ipairs" : "__pairs"); - lua_replace(L, -2); } else { + // if there is no iterator function, + // verify the type of the argument itself: + luaL_checktype(L, json_convert_source_idx, LUA_TTABLE); + // push nil onto stack position 2: lua_pushnil(L); } // create result table on stack position 3: - lua_newtable(L); + json_createproxy(L); // create shadow table on stack position 4: - json_regfetch(L, shadowtbl); lua_newtable(L); - lua_pushvalue(L, json_convert_output_idx); - lua_pushvalue(L, -2); - lua_rawset(L, -4); - lua_replace(L, -2); + lua_pushvalue(L, -1); + json_setshadow(L, -3); // check if iterator function exists: if (lua_isnil(L, json_convert_iterator_idx)) { // if there is no iterator function, @@ -185,8 +192,7 @@ // special Lua stack indicies for json_import function: #define json_import_objectmt_idx 2 #define json_import_arraymt_idx 3 -#define json_import_shadowtbl_idx 4 -#define json_import_stackswap_idx 5 +#define json_import_stackswap_idx 4 // macros for hex decoding: #define json_utf16_surrogate(x) ((x) >= 0xD800 && (x) <= 0xDFFF) @@ -232,8 +238,6 @@ json_regfetch(L, objectmt); // push arraymt onto stack position 3: json_regfetch(L, arraymt); - // push shadowtbl onto stack position 4: - json_regfetch(L, shadowtbl); // push table for stack swapping onto stack position 5: // (needed to avoid Lua stack overflows) lua_newtable(L); @@ -307,9 +311,8 @@ // create internal shadow table on stack: lua_newtable(L); // register internal shadow table: - lua_pushvalue(L, -2); - lua_pushvalue(L, -2); - lua_rawset(L, json_import_shadowtbl_idx); + lua_pushvalue(L, -1); + json_setshadow(L, -3); // distinguish between JSON objects and JSON arrays: if (c == '{') { // if JSON object, @@ -610,38 +613,28 @@ return 2; } -// special Lua stack indicies for json_path function: -#define json_path_shadowtbl_idx 1 - -// stack offset of arguments to json_path function: -#define json_path_idxshift 1 - // gets a value or its type from a JSON document (passed as first argument) // using a path (passed as variable number of keys after the first argument): static int json_path(lua_State *L, int type_mode) { - int stacktop; // stack index of top of stack (after shifting) - int idx = 2 + json_path_idxshift; // stack index of current argument to process + int stacktop; // number of arguments + int idx = 2; // stack index of current argument to process // require at least one argument: luaL_checkany(L, 1); - // insert shadowtbl into stack at position 1 (shifting the arguments): - json_regfetch(L, shadowtbl); - lua_insert(L, 1); - // store stack index of top of stack: + // store stack index of top of stack (number of arguments): stacktop = lua_gettop(L); // use first argument as "current value" (stored on top of stack): - lua_pushvalue(L, 1 + json_path_idxshift); + lua_pushvalue(L, 1); // process each "path key" (2nd argument and following arguments): while (idx <= stacktop) { // if "current value" (on top of stack) is nil, then the path cannot be walked and nil is returned: if (lua_isnil(L, -1)) return 1; // try to get shadow table of "current value": - lua_pushvalue(L, -1); - lua_rawget(L, json_path_shadowtbl_idx); + json_getshadow(L, -1); if (lua_isnil(L, -1)) { // if no shadow table is found, - if (lua_type(L, -1) == LUA_TTABLE) { + if (lua_type(L, -2) == LUA_TTABLE) { // and if "current value" is a table, - // drop nil from stack: + // pop nil from stack: lua_pop(L, 1); // get "next value" using the "path key": lua_pushvalue(L, idx++); @@ -714,30 +707,26 @@ } // special Lua stack indicies for json_set function: -#define json_set_shadowtbl_idx 1 -#define json_set_objectmt_idx 2 -#define json_set_arraymt_idx 3 +#define json_set_objectmt_idx 1 +#define json_set_arraymt_idx 2 // stack offset of arguments to json_set function: -#define json_set_idxshift 3 +#define json_set_idxshift 2 // sets a value (passed as second argument) in a JSON document (passed as first argument) // using a path (passed as variable number of keys starting at third argument): static int json_set(lua_State *L) { - int stacktop; // stack index of top of stack (after shifting) - int idx = 3; // stack index of current argument to process + int stacktop; // stack index of top of stack (after shifting) + int idx; // stack index of current argument to process // require at least two arguments: luaL_checkany(L, 1); luaL_checkany(L, 2); - // insert shadowtbl into stack at position 1 (shifting the arguments): - json_regfetch(L, shadowtbl); - lua_insert(L, 1); - // insert objectmt into stack at position 2 (shifting the arguments): + // insert objectmt into stack at position 1 (shifting the arguments): json_regfetch(L, objectmt); + lua_insert(L, 1); + // insert arraymt into stack at position 2 (shifting the arguments): + json_regfetch(L, arraymt); lua_insert(L, 2); - // insert arraymt into stack at position 3 (shifting the arguments): - json_regfetch(L, arraymt); - lua_insert(L, 3); // store stack index of top of stack: stacktop = lua_gettop(L); // use nil as initial "parent value": @@ -767,11 +756,10 @@ // throw error if parent element does not exist: if (lua_isnil(L, -1)) return luaL_error(L, "Root element is not a JSON object"); // push new JSON object as "current value" onto stack: - lua_newtable(L); + json_createproxy(L); // create and register shadow table: - lua_pushvalue(L, -1); lua_newtable(L); - lua_rawset(L, json_set_shadowtbl_idx); + json_setshadow(L, -2); // set metatable of JSON object: lua_pushvalue(L, json_set_objectmt_idx); lua_setmetatable(L, -2); @@ -798,11 +786,10 @@ // throw error if parent element does not exist: if (lua_isnil(L, -1)) return luaL_error(L, "Root element is not a JSON array"); // push new JSON array as "current value" onto stack: - lua_newtable(L); + json_createproxy(L); // create and register shadow table: - lua_pushvalue(L, -1); lua_newtable(L); - lua_rawset(L, json_set_shadowtbl_idx); + json_setshadow(L, -2); // set metatable of JSON array: lua_pushvalue(L, json_set_arraymt_idx); lua_setmetatable(L, -2); @@ -840,12 +827,12 @@ static int json_len(lua_State *L) { // stack shall contain one function argument: lua_settop(L, 1); - // try to get corresponding shadow table for first argument: - json_regfetch(L, shadowtbl); - lua_pushvalue(L, 1); - lua_rawget(L, -2); - // if shadow table does not exist, return length of argument, else length of shadow table: - lua_pushnumber(L, lua_rawlen(L, lua_isnil(L, -1) ? 1 : -1)); + // push shadow table or nil onto stack: + json_getshadow(L, 1); + // pop nil from stack if no shadow table has been found: + if (lua_isnil(L, -1)) lua_pop(L, 1); + // return length of argument or shadow table: + lua_pushnumber(L, lua_rawlen(L, -1)); return 1; } @@ -853,17 +840,15 @@ static int json_index(lua_State *L) { // stack shall contain two function arguments: lua_settop(L, 2); - // get corresponding shadow table for first argument: - json_regfetch(L, shadowtbl); - lua_pushvalue(L, 1); - lua_rawget(L, -2); - // throw error if no shadow table was found: + // replace first argument with its shadow table + // or throw error if no shadow table is found: + json_getshadow(L, 1); if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); + lua_replace(L, 1); // use key passed as second argument to lookup value in shadow table: - lua_pushvalue(L, 2); - lua_rawget(L, -2); + lua_rawget(L, 1); // if value is null-marker, then push nil onto stack: - if (json_isnullmark(L, -1)) lua_pushnil(L); + if (json_isnullmark(L, 2)) lua_pushnil(L); // return either looked up value, or nil return 1; } @@ -872,16 +857,12 @@ static int json_newindex(lua_State *L) { // stack shall contain three function arguments: lua_settop(L, 3); - // get corresponding shadow table for first argument: - json_regfetch(L, shadowtbl); - lua_pushvalue(L, 1); - lua_rawget(L, -2); - // throw error if no shadow table was found: + // replace first argument with its shadow table + // or throw error if no shadow table is found: + json_getshadow(L, 1); if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); - // replace first argument with shadow table: lua_replace(L, 1); - // reset stack and use second and third argument to write to shadow table: - lua_settop(L, 3); + // second and third argument to write to shadow table: lua_rawset(L, 1); // return nothing: return 0; @@ -891,16 +872,14 @@ static int json_pairs_iterfunc(lua_State *L) { // stack shall contain two function arguments: lua_settop(L, 2); - // get corresponding shadow table for first argument: - json_regfetch(L, shadowtbl); - lua_pushvalue(L, 1); - lua_rawget(L, -2); - // throw error if no shadow table was found: + // replace first argument with its shadow table + // or throw error if no shadow table is found: + json_getshadow(L, 1); if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); + lua_replace(L, 1); // get next key value pair from shadow table (using previous key from argument 2) // and return nothing if there is no next pair: - lua_pushvalue(L, 2); - if (!lua_next(L, -2)) return 0; + if (!lua_next(L, 1)) return 0; // replace null-marker with nil: if (json_isnullmark(L, -1)) { lua_pop(L, 1); @@ -929,22 +908,20 @@ lua_settop(L, 2); // calculate new index by incrementing second argument: idx = lua_tointeger(L, 2) + 1; - // get corresponding shadow table for first argument: - json_regfetch(L, shadowtbl); - lua_pushvalue(L, 1); - lua_rawget(L, -2); - // throw error if no shadow table was found: + // push shadow table onto stack position 3 + // or throw error if no shadow table is found: + json_getshadow(L, 1); if (lua_isnil(L, -1)) return luaL_error(L, "Shadow table not found"); - // do integer lookup in shadow table: - lua_rawgeti(L, -1, idx); + // do integer lookup in shadow table and store result on stack position 4: + lua_rawgeti(L, 3, idx); // return nothing if there was no value: - if (lua_isnil(L, -1)) return 0; + if (lua_isnil(L, 4)) return 0; // return new index and // either the looked up value if it is not equal to the null-marker // or nil instead of null-marker: lua_pushinteger(L, idx); - if (json_isnullmark(L, -2)) lua_pushnil(L); - else lua_pushvalue(L, -2); + if (json_isnullmark(L, 4)) lua_pushnil(L); + else lua_pushvalue(L, 4); return 2; } @@ -1009,11 +986,10 @@ #define json_export_indentstring_idx 2 #define json_export_objectmt_idx 3 #define json_export_arraymt_idx 4 -#define json_export_shadowtbl_idx 5 -#define json_export_stackswap_idx 6 -#define json_export_luacontainer_idx 7 -#define json_export_ccontainer_idx 8 -#define json_export_buffer_idx 9 +#define json_export_stackswap_idx 5 +#define json_export_luacontainer_idx 6 +#define json_export_ccontainer_idx 7 +#define json_export_buffer_idx 8 // encodes a JSON document (passed as first argument) // optionally using indentation (indentation string or true passed as second argument) @@ -1063,11 +1039,9 @@ json_regfetch(L, objectmt); // push arraymt onto stack position 4: json_regfetch(L, arraymt); - // push shadowtbl onto stack position 5: - json_regfetch(L, shadowtbl); - // push table for stack swapping onto stack position 6: + // push table for stack swapping onto stack position 5: lua_newtable(L); - // create placeholders on stack positions 7 through 8: + // create placeholders on stack positions 6 through 7: lua_settop(L, json_export_buffer_idx); // create Lua string buffer: luaL_buffinit(L, &buf); @@ -1146,11 +1120,10 @@ // reset stack (pop metatable from stack): lua_pop(L, 1); } - // replace table with its shadow table if existent, and reset stack: - lua_pushvalue(L, json_export_value_idx); - lua_rawget(L, json_export_shadowtbl_idx); + // replace table with its shadow table if existent: + json_getshadow(L, json_export_value_idx); if (lua_isnil(L, -1)) lua_pop(L, 1); - else lua_replace(L, json_export_value_idx); + else lua_replace(L, json_export_value_idx); // check if type of table is still undetermined // and optionally calculate number of string keys (keycount) // or set keycount to zero: @@ -1395,15 +1368,6 @@ lua_newtable(L); luaL_setfuncs(L, json_metatable_functions, 0); json_regstore(L, arraymt); - // create and store ephemeron table to store shadow tables for each JSON object/array - // to allow NULL values returned as nil - lua_newtable(L); - lua_newtable(L); // metatable for ephemeron table - lua_pushliteral(L, "__mode"); - lua_pushliteral(L, "k"); - lua_rawset(L, -3); - lua_setmetatable(L, -2); - json_regstore(L, shadowtbl); // set metatable of null marker and make it available through library module: json_pushnullmark(L); lua_newtable(L);