在我最近的一次代码审查中,出现了一个关于一段 lua 代码的问题。有问题的代码正在刷新缓存并使用一些数据重新初始化它:
for filename,_ in pairs(fileTable) do
fileTable[filename] = nil
end
-- reinitialize here
有什么理由不应该用这个代替上面的循环吗?
fileTable = { }
-- reinitialize here
在我最近的一次代码审查中,出现了一个关于一段 lua 代码的问题。有问题的代码正在刷新缓存并使用一些数据重新初始化它:
for filename,_ in pairs(fileTable) do
fileTable[filename] = nil
end
-- reinitialize here
有什么理由不应该用这个代替上面的循环吗?
fileTable = { }
-- reinitialize here
除非您有其他证据,否则最好相信 Lua 的垃圾收集:只需在需要时创建一个新的空表。
这是由于表调整大小/重新散列开销。创建表时,它是空的。当您插入一个元素时,会发生重新散列,并且表大小会增加到 1。当您插入另一个元素时,也会发生同样的情况。规则是,只要没有足够的空间(在数组或哈希部分)来容纳另一个元素,就会增长一个表。新尺寸是 2 的最小幂,可以容纳所需的元素数量。例如,当您插入一个元素时,如果一个表包含 0、1、2、4、8 等元素,则会发生重新散列。
现在,您所描述的技术保存了这些重新散列,因为 Lua 不会缩小表。因此,当您有频繁的填充/刷新表操作时,最好(在性能方面)像在您的示例中那样做而不是创建一个空表。
更新:
我做了一个小测试:
local function rehash1(el, loops)
local table = {}
for i = 1, loops do
for j = 1, el do
table[j] = j
end
for k in ipairs(table) do table[k] = nil end
end
end
local function rehash2(el, loops)
for i = 1, loops do
local table = {}
for j = 1, el do
table[j] = j
end
end
end
local function test(elements, loops)
local time = os.time();
rehash1(elements, loops);
local time1 = os.time();
rehash2(elements, loops);
local time2 = os.time();
print("Time nils: ", tostring(time1 - time), "\n");
print("Time empty: ", tostring(time2 - time1), "\n");
end
结果很有趣。在 Lua 5.1 上运行test(4, 10000000)
nils 需要 7 秒,empties 需要 10 秒。对于大于 32 个元素的表格,空版本更快(表格越大,差异越大)。test(128, 400000)
给 nils 9 秒,给空 5 秒。
现在在 LuaJIT 上,alloc 和 gc 操作相对较慢,运行test(1024, 1000000)
nils 需要 3 秒,empties 需要 7 秒。
PS 请注意普通 Lua 和 LuaJIT 之间的纯粹性能差异。对于 1024 个元素表,普通 Lua 在大约 20 秒内完成了 100,000 次测试迭代,LuaJIT 在 10 秒内完成了 1,000,000 次迭代!
在 Lua 中分配新表是一项代价高昂的操作(对于几乎任何动态语言中的任何对象分配都是如此)。此外,不断地“丢失”新创建的 GC 会给性能和内存带来额外的压力,因为在 GC 真正声称它之前,每个创建的表仍将在内存中。
您示例中的技术以这些缺点换取显式删除表中所有元素所需的时间。这将始终节省内存,并且根据元素的数量,通常也可能会提高性能。