我已经在Lua(C API)之上实现了一个解决量子力学问题的代码。它为脚本语言增加了量子力学算子和波函数。到现在为止还挺好。挑战在于wavefunction userdata可能很大(userdata包含指向1Mb和1Gb之间数组的指针)我已经添加了标准的垃圾收集方法,对于简单的情况,它们可以工作。
static int LuaWavefunctionDestroy(lua_State * L)
{
WaveFunctionType *psi = (WaveFunctionType*) luaL_checkudata(L, 1, "Wavefunction_Type");
WaveFunctionFree(psi);
return 0;
}
使用_gc 进行和metamethod调用
static const struct luaL_Reg Wavefunction_meta[] = {
{"__add", LuaWavefunctionAdd},
{"__sub", LuaWavefunctionSub},
{"__mul", LuaWavefunctionMul},
{"__div", LuaWavefunctionDiv},
{"__unm", LuaWavefunctionUnm},
{"__index", LuaWavefunctionIndex},
{"__newindex", LuaWavefunctionNewIndex},
{"__tostring", LuaWavefunctionToString},
{"__gc", LuaWavefunctionDestroy},
{NULL, NULL}
};
如果我现在在Lua中运行以下代码
for j=1,N do
for i=j,N do
psi[j] = psi[j] - ( psi[i] * psi[j] ) * psi[j]
end
end
psi是几个(10-100)波函数的表(数组),因为垃圾收集器无法跟上,所以我的内存耗尽很快。
更糟糕的是,我已经注册了数千个字符串和常量(数字),因此完整的垃圾收集需要经历许多变量
有没有办法在特定对象或用户数据上运行垃圾收集?
答案 0 :(得分:2)
不,目前不是(< = 5.3.x)。
但是你可以采取一些措施来改善这种状况:
...由于垃圾收集器无法跟上,我的内存很快耗尽。
GC跟踪完整用户数据的大小(并相应地增加GC债务),但是对于轻用户数据并不知道它们。如果你
lua_pushlightuserdata
( L, ptr );
在其他位置(malloc
,mmap
等)分配的值,GC"看到"大小为零。如果您使用lua_newuserdata
( L, size )
进行分配,则GC会知道完整大小。
你可能会使用lua_newuserdata
作为一个瘦的包装器结构(以获得__gc
)" fat"从该结构引用的数据(不可见地为Lua),因此当您占用千兆字节时,GC会看到几千字节的已用内存。如果可能,尝试将内部事物的分配器从malloc
切换到lua_newuserdata
。 (除非您需要跨Lua状态共享数据和/或需要特殊对齐或有其他约束......)这应该解决大多数问题。 (它仍将不断收集东西,但至少它不再是OOM。)
如果失败,请在每次分配之前插入显式GC步骤调用(使用$((以千字节为单位的不可见分配的大小)步骤,模拟您使用完整用户数据的大部分内容),并使用GC步骤乘数和/或暂停。
如果这还不够,你可以尝试更多涉及丑化代码的事情。
运算符元方法只知道这两个参数,因此它们总是必须创建一个新的目标值。如果使用函数而不是运算符,则可以将现有的废弃值作为目标传递,例如
local psi_ij, psi_ijj
for j=1,N do
for i=j,N do
psi_ij = qmul( psi[i],psi[j], psi_ij )
psi_ijj = qmul( psi_ij,psi[j], psi_ijj )
psi[j] = qsub( psi[j],psi_ijj, psi[j] )
end
end
其中qadd
,qsub
,qmul
等需要(a,b[, target])
并重新使用target
如果给定或以其他方式分配新存储空间。 (这会将N ^ 2分配的循环减少到两个分配。)如果你这样定义它们的一个很好的技巧是你也可以使用与operator metamethods相同的函数。 (target
只会一直缺席,所以如果你不关心分配,你可以使用运营商。)
(如果psi[k]
并非全部具有相同的大小,以便中间值的大小发生变化,则可以在数学函数中将target
明确标记为免费,如果它不兼容,然后可以与查找存储结合如下:)
另一个选择是存储一个废弃的值并明确地将值标记为废弃,然后让分配器优先重用现有废弃的值。粗略的未经测试的代码:
-- hidden in implementation somewhere
-- MT to mark per-size stores as weak so GC can collect values
local weak = { __mode = "k" }
-- storage of freed values, auto-create per-size storage table
local freed = setmetatable( { }, {
__index = function( t, k )
local v = setmetatable( { }, weak )
t[k] = v
return v
end
} )
-- interface to that storage
-- replace size( v ) by some way to get a size/layout descriptor
-- (e.g. number or string giving size in bytes or dimensions or ...)
function free( v, ... )
freed[size( v )][v] = true -- mark as free
if ... then return free( ... ) end
end
-- return re-usable value of size/layout vsize, or nil if allocation needed
function reuse( vsize )
local v = next( freed[vsize] )
if not v then return nil end
freed[vsize][v] = nil -- un-mark
return v
end
这样,您的分配器应首先检查reuse
,并且只有在返回nil时才实际分配新值。你的代码必须改变,例如:
for j=1,N do
for i=j,N do
local psi_j = psi[j]
local psi_ij = psi[i] * psi_j
local psi_ijj = psi_ij * psi_j
psi[j] = psi[j] - psi_ijj
free( psi_j, psi_ij, psi_ijj )
end
end
改变了免费的定义,比如
local temp = setmetatable( { }, { __mode = "v" } )
function free( v )
table.insert( temp, v )
if #temp > 2 then
local w = table.remove( temp, 1 )
freed[size( w )][w] = true
end
return v
end
你添加足够的延迟,你可以内联编写表达式(当值实际标记为空闲时,如果之间没有其他任何事情发生,则会评估二进制操作):
local _ = free
for j=1,N do
for i=j,N do
local psi_j = psi[j]
psi[j] = psi_j - _(_(psi[i] * psi_j) * psi_j)
free( psi_j )
end
end
......看起来更好并且不需要跟踪中间值,但是很容易因为意外地过早释放而中断(例如,而不是在最后释放psi[j]
,执行psi[j] = _(psi[j]) - _(_(psi[i] * psi[j]) * psi[j])
会打破。)
虽然这最后一个变体(差不多)恢复了看似正常的表情,但它真的不是一个好主意。之前的那个并不比使用函数而不是运算符好,但需要更多的脆弱簿记而且速度更慢。因此,如果您决定对分配进行微观管理,我建议您使用第一个版本的变体。 (您也可以使用函数作为方法,可能使用像火炬一样切换的参数(target:add(a,b)
)(但是您需要在代码中明确分配target
并且需要先知道大小操作...)所有这些变化都以不同的方式吮吸。尽量避免走这么远,并且失败建立你最不喜欢的那个。)