本文內容基於版本:Lua 5.3.0算法
Lua容許用戶自定義內存管理器,並在建立Lua虛擬機(lua_State實例)時傳入。固然自定義內存管理器必須遵循Lua已定義的一些行爲規則。建立一個Lua虛擬機須要使用luaL_newstate函數:api
lua_State *L = luaL_newstate();數組
luaL_newstate函數的實現主要是調用lua_newstate函數,lua_newstate函數將接受一個內存分配器函數做爲參數,進而在內部分配內存:cookie
// lauxlib.h LUALIB_API lua_State *(luaL_newstate) (void); // lauxlib.c LUALIB_API lua_State *luaL_newstate (void) { lua_State *L = lua_newstate(l_alloc, NULL);
if (L) lua_atpanic(L, &panic); return L; } // lua.h LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); // lstate.c LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); if (l == NULL) return NULL; ......
return L; }
能夠看到,Lua內存分配器必須是一個lua_Alloc類型的函數,若是想自定義內存分配器,那麼用戶必須定義一個lua_Alloc類型的函數:數據結構
// lua.h typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
luaL_newstate函數調用lua_newstate函數時,傳入了一個lua_Alloc類型的函數:l_alloc。該函數就是Lua提供的默認內存管理函數,它主要使用C標準庫中的realloc函數進行內存管理:函數
// lauxlib.c static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; /* not used */ if (nsize == 0) { free(ptr); return NULL; } else return realloc(ptr, nsize); }
ud :Lua默認內存管理器並未使用該參數。不過在用戶自定義內存管理器中,可讓內存管理在不一樣的堆上進行。優化
ptr :非NULL表示指向一個已分配的內存塊指針,NULL表示將分配一塊nsize大小的新內存塊。this
osize:原始內存塊大小,默認內存管理器並未使用該參數。Lua的設計強制在調用內存管理器函數時候須要給出原始內存塊的大小信息,若是用戶須要自定義一個高效的內存管理器,那麼這個參數信息將十分重要。這是由於大多數的內存管理算法都須要爲所管理的內存塊加上一個cookie,裏面存儲了內存塊尺寸的信息,以便在釋放內存的時候可以獲取到尺寸信息(譬如多級內存池回收內存操做)。而Lua內存管理器刻意在調用內存管理器時提供了這個信息,這樣就沒必要額外存儲這些cookie信息,這樣在大量使用小內存塊的環境中將能夠節省很多的內存。另外在ptr傳入NULL時,osize表示Lua對象類型(LUA_TNIL、LUA_TBOOLEAN、LUA_TTHREAD等等),這樣內存管理器就能夠知道當前在分配的對象的類型,從而能夠針對它作一些統計或優化的工做。lua
nsize:新的內存塊大小,特別地,在nsize爲0時須要提供內存釋放的功能。spa
咱們已經知道在建立Lua虛擬機時將傳入一個內存管理器,而後使用該內存管理器分配相關的數據結構。Lua設計了一個global_State結構(全局狀態機)來存儲各類全局數據,其中就包含了內存管理器。也就是說咱們使用內存管理器建立了一個Lua虛擬機,Lua虛擬機的全局狀態機中保存了該內存管理器,以便Lua後續內部的工做能使用該內存管理器進行內存管理工做。
// lstate.c LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); if (l == NULL) return NULL; L = &l->l.l; g = &l->g; L->next = NULL; L->tt = LUA_TTHREAD; g->currentwhite = bitmask(WHITE0BIT); L->marked = luaC_white(g); preinit_thread(L, g); g->frealloc = f; ...... return L; }
Lua設計了一組宏來管理不一樣類別的內存:單個對象、數組、可變長數組等等。這一系列的宏使用了兩個核心API:luaM_realloc_和luaM_growaux_,下面咱們先就這兩個核心API進行分析。
luaM_realloc_函數並不會被直接調用,它將調用保存在global_State.frealloc中的內存分配器進行內存管理工做。luaM_realloc_會根據傳入的osize和nsize調整內部感知的內存大小(設置GCdebt),並在內存不夠用的時候會主動嘗試作GC操做。
// lmem.h
/* not to be called directly */
LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize,
size_t size);
// lmem.c /* ** generic allocation routine. */ void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) { void *newblock; global_State *g = G(L); size_t realosize = (block) ? osize : 0; lua_assert((realosize == 0) == (block == NULL)); #if defined(HARDMEMTESTS) if (nsize > realosize && g->gcrunning) luaC_fullgc(L, 1); /* force a GC whenever possible */ #endif newblock = (*g->frealloc)(g->ud, block, osize, nsize); if (newblock == NULL && nsize > 0) { api_check( nsize > realosize, "realloc cannot fail when shrinking a block"); luaC_fullgc(L, 1); /* try to free some memory... */ newblock = (*g->frealloc)(g->ud, block, osize, nsize); /* try again */ if (newblock == NULL) luaD_throw(L, LUA_ERRMEM); } lua_assert((nsize == 0) == (newblock == NULL)); g->GCdebt = (g->GCdebt + nsize) - realosize; return newblock; }
• luaM_growaux_
luaM_growaux_函數是用來管理可變長數組的,其主要策略是:當數組空間不夠時,擴大爲原來空間的兩倍。其中管理內存部分使用了基於luaM_realloc_函數的宏luaM_reallocv,該宏針對數組操做,根據新的數組元素個數從新分配內存。
// lmem.h /* not to be called directly */ LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elem, int limit, const char *what); // lmem.c #define MINSIZEARRAY 4 void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems, int limit, const char *what) { void *newblock; int newsize; if (*size >= limit/2) { /* cannot double it? */ if (*size >= limit) /* cannot grow even a little? */ luaG_runerror(L, "too many %s (limit is %d)", what, limit); newsize = limit; /* still have at least one free place */ } else { newsize = (*size)*2; if (newsize < MINSIZEARRAY) newsize = MINSIZEARRAY; /* minimum size */ } newblock = luaM_reallocv(L, block, *size, newsize, size_elems); *size = newsize; /* update only when everything else is OK */ return newblock; }
size :數組長度(最大容納元素個數),傳入表示原始數組長度,傳出表示從新分配後數組長度;
size_elems:單個數組元素大小;
limit :數組元素最大容納個數限制;
what :提示信息字符串;
/*
** This macro reallocs a vector 'b' from 'on' to 'n' elements, where
** each element has size 'e'. In case of arithmetic overflow of the
** product 'n'*'e', it raises an error (calling 'luaM_toobig'). Because
** 'e' is always constant, it avoids the runtime division MAX_SIZET/(e).
**
** (The macro is somewhat complex to avoid warnings: The 'sizeof'
** comparison avoids a runtime comparison when overflow cannot occur.
** The compiler should be able to optimize the real test by itself, but
** when it does it, it may give a warning about "comparison is always
** false due to limited range of data type"; the +1 tricks the compiler,
** avoiding this warning but also this optimization.)
*/
#define luaM_reallocv(L,b,on,n,e) \ (((sizeof(n) >= sizeof(size_t) && cast(size_t, (n)) + 1 > MAX_SIZET/(e)) \ ? luaM_toobig(L) : cast_void(0)) , \ luaM_realloc_(L, (b), (on)*(e), (n)*(e)))
luaM_reallocv將使數組b的長度(最大容納元素個數)從on從新分配爲n,其中每一個數組元素大小爲e。
b :數組指針;
on :數組從新分配前的長度(最大容納元素個數);
n :數組從新分配後的長度(最大容納元素個數);
e :數組元素大小;
/* ** Arrays of chars do not need any test */ #define luaM_reallocvchar(L,b,on,n) \ cast(char *, luaM_realloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char)))
luaM_reallocvchar將使字符數組b的長度(最大容納元素個數)從on從新分配爲n,其中每一個數組元素大小爲sizeof(char)。
b :數組指針;
on :數組從新分配前的長度(最大容納元素個數);
n :數組從新分配後的長度(最大容納元素個數);
#define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0)
luaM_freemem將釋放b指向的內存塊空間。
b :內存塊指針;
s :內存塊大小;
#define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0)
luaM_free將釋放b指向的內存塊空間(b表示某種對象類型指針)。
b :內存指針,同時表示某種對象類型指針;
#define luaM_freearray(L, b, n) luaM_realloc_(L, (b), (n)*sizeof(*(b)), 0)
luaM_freearray將釋放b指向的內存塊空間(b表示某種類型對象的數組指針)。
b :內存指針,同時表示某種類型對象的數組指針;
n :數組長度(最大容納元素個數);
#define luaM_malloc(L,s) luaM_realloc_(L, NULL, 0, (s))
luaM_malloc將分配一塊大小爲s的內存塊空間。
s :將要分配的內存塊空間大小;
#define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t)))
luaM_new將分配一塊內存塊空間,空間大小爲sizeof(t)。
t :某種數據類型;
#define luaM_newvector(L,n,t) \ cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t)))
luaM_newvector將分配一個長度爲n的數組空間,數組元素爲類型t。
n :數組長度(最大容納元素個數);
t :數組元素類型;
#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s))
luaM_newobject將分配一塊大小爲s的內存塊空間,其將要容納的Lua數據類型爲tag表示的類型。
tag :Lua數據類型;
s :分配的內存塊大小;
#define luaM_growvector(L,v,nelems,size,t,limit,e) \ if ((nelems)+1 > (size)) \ ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e)))
luaM_growvector將在數組空間不足以容納下一個元素的狀況下增加空間大小(原空間大小 * 2)。
v :數組指針;
nelems :正在使用的元素個數;
size :數組元素個數,傳入表示原始數組大小,傳出表示從新分配後數組大小;
t :(數組元素的)數據類型;
limit :數組元素最大個數限制;
e :提示信息字符串;
#define luaM_reallocvector(L, v,oldn,n,t) \ ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t))))
luaM_reallocvector將從新分配數組空間大小。
v :數組指針;
oldn :從新分配前數組大小;
n :從新分配後數組大小;