Lua 中具有大量用户数据的有效垃圾回收
Effective garbage collection in Lua with large userdata
我已经在Lua(C API)之上实现了一个代码来解决量子力学中的问题。它将量子力学算子和波函数添加到脚本语言中。目前为止,一切都好。挑战在于wavefunction用户数据可能很大(用户数据包含指向1Mb和1Gb之间的数组的指针) 我添加了标准的垃圾收集方法,对于简单的情况,它们有效。
static int LuaWavefunctionDestroy(lua_State * L)
{
WaveFunctionType *psi = (WaveFunctionType*) luaL_checkudata(L, 1, "Wavefunction_Type");
WaveFunctionFree(psi);
return 0;
}
以及使用 _gc 的元方法调用
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) 个波函数的表(数组),由于垃圾收集器无法跟上,我很快就会耗尽内存。
更糟糕的是,我已经注册了数千个字符串和常量(数字),因此完整的垃圾收集需要经过许多变量
有没有办法对特定对象或仅对用户数据运行垃圾回收?
不,目前没有(<=5.3.x)。
但是你可以做很多事情来改善这种情况:
GC。我很快就用完了内存,因为垃圾收集器跟不上。
跟踪完整用户数据的大小(并相应地增加 GC 债务),但不知道轻用户数据的大小。 如果你 lua_pushlightuserdata
( L, ptr );
在其他地方分配的值(通过malloc
、mmap
等),GC "看到"大小为零。 如果使用 lua_newuserdata
( L, size )
进行分配,GC 将知道完整大小。
您可能会使用 lua_newuserdata
作为瘦包装结构(获取__gc
)围绕从该结构引用(不可见于 Lua)的"胖"数据,因此 GC 在您占用千兆字节时会看到几千字节的已用内存。 如果可能,请尝试将内部事物的分配器从 malloc
切换到 lua_newuserdata
。 (除非你需要跨Lua状态共享数据和/或需要特殊对齐或有其他限制...... 这应该可以解决大部分问题。 (它仍然会不断收集东西,但至少它不会再 OOM 了。
如果做不到这一点,请在每次分配之前插入显式 GC 步骤调用(使用 $((以 KB 为单位的不可见分配大小))步骤,以模拟使用完整用户数据的大部分内容),并使用 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 个分配减少到两个分配。 如果以这种方式定义它们,一个不错的技巧是,您还可以使用与运算符元方法相同的函数。(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
您添加了足够的延迟,以便可以内联编写表达式(当值实际标记为 free 时,如果两者之间没有其他发生任何操作,则将计算二进制操作):
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])
会中断。
虽然最后一个变体(几乎)恢复了正常的表情,但这真的不是一个好主意。 而前面的那个并不比使用函数而不是运算符好多少,但需要更脆弱的簿记并且速度更慢。 因此,如果您决定微观管理分配,我建议您在第一个版本上使用变体。 (你也可以将函数作为方法,可能像Torch一样切换参数(target:add(a,b)
)(但是你需要在代码中显式分配target
,并且在你进行操作之前需要知道大小...... 所有这些变化都以不同的方式吸吮。尽量避免走这么远,如果做不到,就要构建你最不喜欢的那个。
- 有哪些有效的方法可以消除一组 100 万个字符串>重复数据?
- 一种有效的数据结构,用于按 ID 访问和查找加权随机项
- 在C++事务之间存储大量字符数据的有效方法
- 有效地将数据加载到 std::vector 中<char>
- 用于随机数据访问的最有效文件类型
- 错误 - 自定义数据类型作为有效负载,带有提升::几何
- 一种将 Dart 中的字节数据转换为 C++ 中的无符号字符*的有效方法?
- C++,在对象内分配多个数据时,堆栈分配是否更有效? 在下面的程序中,类A_Heap的效率会更低吗?
- 如何有效地实现将向量的数据分配给多个变量?
- 将一种数据类型的向量复制到同一数据类型的结构向量中的有效方法是什么
- 有没有办法有效地更新QML中的CAN数据?
- 张量(多维数组)是树前瞻数据的有效存储类型吗?
- 缓存和访问数据库数据的有效方法?
- 如何有效地读取和存储 1GB 文本文件中的数据
- 在使用 0MQ 异步接收时异步发送数据的最有效方法是什么?
- 存储大量会话的最有效数据结构是什么?
- 在英特尔 CPU 上将最近的 8 个顶点插值到随机采样点时,对称 3D 网格的最有效数据布局
- 基数树(Patricia Trie)是手机通讯录的有效数据结构吗?
- 用于查找包含数字的非重叠范围的有效数据结构
- c++检查cin是否有效数据不工作