From b14609032cf328dea48b0803f3e585e223283b3d Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Fri, 3 May 2019 10:14:25 -0300 Subject: [PATCH] Avoid the creation of too many strings in 'package' Both when setting a path and searching for a file ('searchpath'), this commit reduces the number of intermediate strings created in Lua. (For setting a path the change is not relevant, because this is done only twice when loading the module. Anyway, it is a nice example of how to use auxlib buffers to manipulate strings in the C API.) --- lgc.h | 2 +- llimits.h | 2 +- loadlib.c | 89 ++++++++++++++++++++++++++++++++----------------- testes/main.lua | 27 ++++++++++----- 4 files changed, 79 insertions(+), 41 deletions(-) diff --git a/lgc.h b/lgc.h index 9ba7ecb0..b972472f 100644 --- a/lgc.h +++ b/lgc.h @@ -127,7 +127,7 @@ /* ** some gc parameters are stored divided by 4 to allow a maximum value -** larger than 1000 in a 'lu_byte'. +** up to 1023 in a 'lu_byte'. */ #define getgcparam(p) ((p) * 4) #define setgcparam(p,v) ((p) = (v) / 4) diff --git a/llimits.h b/llimits.h index cc983972..febf7555 100644 --- a/llimits.h +++ b/llimits.h @@ -39,7 +39,7 @@ typedef signed char ls_byte; /* maximum value for size_t */ #define MAX_SIZET ((size_t)(~(size_t)0)) -/* maximum size visible for Lua (must be representable in a lua_Integer */ +/* maximum size visible for Lua (must be representable in a lua_Integer) */ #define MAX_SIZE (sizeof(size_t) < sizeof(lua_Integer) ? MAX_SIZET \ : (size_t)(LUA_MAXINTEGER)) diff --git a/loadlib.c b/loadlib.c index 4cf9aec3..ff73a459 100644 --- a/loadlib.c +++ b/loadlib.c @@ -290,22 +290,33 @@ static int noenv (lua_State *L) { static void setpath (lua_State *L, const char *fieldname, const char *envname, const char *dft) { + const char *dftmark; const char *nver = lua_pushfstring(L, "%s%s", envname, LUA_VERSUFFIX); - const char *path = getenv(nver); /* use versioned name */ - if (path == NULL) /* no environment variable? */ + const char *path = getenv(nver); /* try versioned name */ + if (path == NULL) /* no versioned environment variable? */ path = getenv(envname); /* try unversioned name */ if (path == NULL || noenv(L)) /* no environment variable? */ lua_pushstring(L, dft); /* use default */ - else { - /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */ - path = luaL_gsub(L, path, LUA_PATH_SEP LUA_PATH_SEP, - LUA_PATH_SEP AUXMARK LUA_PATH_SEP); - luaL_gsub(L, path, AUXMARK, dft); - lua_remove(L, -2); /* remove result from 1st 'gsub' */ + else if ((dftmark = strstr(path, LUA_PATH_SEP LUA_PATH_SEP)) == NULL) + lua_pushstring(L, path); /* nothing to change */ + else { /* path contains a ";;": insert default path in its place */ + size_t len = strlen(path); + luaL_Buffer b; + luaL_buffinit(L, &b); + if (path < dftmark) { /* is there a prefix before ';;'? */ + luaL_addlstring(&b, path, dftmark - path); /* add it */ + luaL_addchar(&b, *LUA_PATH_SEP); + } + luaL_addstring(&b, dft); /* add default */ + if (dftmark < path + len - 2) { /* is there a sufix after ';;'? */ + luaL_addchar(&b, *LUA_PATH_SEP); + luaL_addlstring(&b, dftmark + 2, (path + len - 2) - dftmark); + } + luaL_pushresult(&b); } setprogdir(L); lua_setfield(L, -3, fieldname); /* package[fieldname] = path value */ - lua_pop(L, 1); /* pop versioned variable name */ + lua_pop(L, 1); /* pop versioned variable name ('nver') */ } /* }================================================================== */ @@ -421,17 +432,26 @@ static int readable (const char *filename) { } -static const char *pushnextfilename (lua_State *L, const char *path) { - const char *l; - if (*path == *LUA_PATH_SEP) - path++; /* skip separator */ - if (*path == '\0') +/* +** Get the next name in '*path' = 'name1;name2;name3;...', changing +** the ending ';' to '\0' to create a zero-terminated string. Return +** NULL when list ends. +*/ +static const char *getnextfilename (char **path, char *end) { + char *sep; + char *name = *path; + if (name == end) return NULL; /* no more names */ - l = strchr(path, *LUA_PATH_SEP); /* find next separator */ - if (l == NULL) /* no more separators? */ - l = path + strlen(path); /* go until the end */ - lua_pushlstring(L, path, l - path); /* file name */ - return l; /* rest of the path */ + else if (*name == '\0') { /* from previous iteration? */ + *name = *LUA_PATH_SEP; /* restore separator */ + name++; /* skip it */ + } + sep = strchr(name, *LUA_PATH_SEP); /* find next separator */ + if (sep == NULL) /* separator not found? */ + sep = end; /* name goes until the end */ + *sep = '\0'; /* finish file name */ + *path = sep; /* will start next search from here */ + return name; } @@ -442,12 +462,12 @@ static const char *pushnextfilename (lua_State *L, const char *path) { ** no file 'blublu.so' */ static void pusherrornotfound (lua_State *L, const char *path) { - if (*path == *LUA_PATH_SEP) - path++; /* skip separator */ - lua_pushstring(L, "\n\tno file '"); - luaL_gsub(L, path, LUA_PATH_SEP, "'\n\tno file '"); - lua_pushstring(L, "'"); - lua_concat(L, 3); + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addstring(&b, "\n\tno file '"); + luaL_addgsub(&b, path, LUA_PATH_SEP, "'\n\tno file '"); + luaL_addstring(&b, "'"); + luaL_pushresult(&b); } @@ -455,17 +475,24 @@ static const char *searchpath (lua_State *L, const char *name, const char *path, const char *sep, const char *dirsep) { + luaL_Buffer buff; + char *pathname; /* path with name inserted */ + char *endpathname; /* its end */ + const char *filename; /* separator is non-empty and appears in 'name'? */ if (*sep != '\0' && strchr(name, *sep) != NULL) name = luaL_gsub(L, name, sep, dirsep); /* replace it by 'dirsep' */ - /* replace marks ('?') in 'path' by the file name */ - path = luaL_gsub(L, path, LUA_PATH_MARK, name); - while ((path = pushnextfilename(L, path)) != NULL) { - const char *filename = lua_tostring(L, -1); + luaL_buffinit(L, &buff); + /* add path to the buffer, replacing marks ('?') with the file name */ + luaL_addgsub(&buff, path, LUA_PATH_MARK, name); + luaL_addchar(&buff, '\0'); + pathname = luaL_buffaddr(&buff); /* writable list of file names */ + endpathname = pathname + luaL_bufflen(&buff) - 1; + while ((filename = getnextfilename(&pathname, endpathname)) != NULL) { if (readable(filename)) /* does file exist and is readable? */ - return filename; /* return that file name */ - lua_pop(L, 1); /* else remove file name */ + return lua_pushstring(L, filename); /* save and return name */ } + luaL_pushresult(&buff); /* push path to create error message */ pusherrornotfound(L, lua_tostring(L, -1)); /* create error message */ return NULL; /* not found */ } diff --git a/testes/main.lua b/testes/main.lua index b9dcab1c..aab490c8 100644 --- a/testes/main.lua +++ b/testes/main.lua @@ -142,12 +142,18 @@ do prepfile("print(package.path, package.cpath)") RUN('env LUA_INIT="error(10)" LUA_PATH=xxx LUA_CPATH=xxx lua -E %s > %s', prog, out) + local output = getoutput() + defaultpath = string.match(output, "^(.-)\t") + defaultCpath = string.match(output, "\t(.-)$") + + -- running with an empty environment + RUN('env -i lua %s > %s', prog, out) local out = getoutput() - defaultpath = string.match(out, "^(.-)\t") - defaultCpath = string.match(out, "\t(.-)$") + assert(defaultpath == string.match(output, "^(.-)\t")) + assert(defaultCpath == string.match(output, "\t(.-)$")) end --- paths did not changed +-- paths did not change assert(not string.find(defaultpath, "xxx") and string.find(defaultpath, "lua") and not string.find(defaultCpath, "xxx") and @@ -160,15 +166,20 @@ local function convert (p) RUN('env LUA_PATH="%s" lua %s > %s', p, prog, out) local expected = getoutput() expected = string.sub(expected, 1, -2) -- cut final end of line - assert(string.gsub(p, ";;", ";"..defaultpath..";") == expected) + if string.find(p, ";;") then + p = string.gsub(p, ";;", ";"..defaultpath..";") + p = string.gsub(p, "^;", "") -- remove ';' at the beginning + p = string.gsub(p, ";$", "") -- remove ';' at the end + end + assert(p == expected) end convert(";") convert(";;") -convert(";;;") -convert(";;;;") -convert(";;;;;") -convert(";;a;;;bc") +convert("a;;b") +convert(";;b") +convert("a;;") +convert("a;b;;c") -- test -l over multiple libraries