技术开发 频道

如何使用表而不引发额外的垃圾回收

  【IT168 技术文档】在Lua中创建数字和字符串是很高效的, 数字无需任何额外的数据结构关联, 当不再使用时释放也是立即的. 相同的字符串只有一个实例, 因此创建10000个"Hello, world!"字符串无论在创建或释放时都不会产生垃圾回收. 然而当表不再使用释放时将会引起垃圾回收. 如果你不想引发不必要的垃圾回收, 要么尽量避免使用表, 要么重用以前使用过的表.

  举个例子, 当你需要对20个值进行排序, 显示结果然后再排序另30个值同样打印出结果, 多数人会这样写:

  local temporaryTable   temporaryTable={}   for idx=1, 20 do   temporaryTable[idx]=GetSomeDataByIndex(idx)   end   DisplayResult(table.sort(temporaryTable))   temporaryTable={} -- inefficient   for idx=1, 30 do   temporaryTable[idx]=GetSomeOtherDataByIndex(idx)   end   DisplayResults(table.sort(temporaryTable))

  这个例子创建了一个表, 废弃之后又创建了另一个多余的表. 去掉标识了"-- inefficient"的一行会节省内存. 这样的优化对于大量的循环或经常被调用的函数效果尤其好.

  Old:   local function OftenCalledSortingFunction()   local temporaryTable={}   for idx=1, 20 do   temporaryTable[idx]=GetSomeDataByIndex(idx)   end   return table.sort(temporaryTable)   end

  将表的初始化放入频繁被调用的函数内, 如经常发生的事件, OnUpdate handler内, 内存很快将会被填满而引发垃圾回收.

  New:   local workingTableForOftenCalledSortingFunction={}
-- we can create it once and reuse it because we always sort exactly 20 values   local function OftenCalledSortingFunction()   for idx=1, 20 do   workingTableForOftenCalledSortingFunction[idx]=GetSomeDataByIndex(idx)   end   return table.sort(workingTableForOftenCalledSortingFunction)   end

  如果其他函数频繁的使用同样大小的表你可以在外部建立一个共享表以供他们使用这样就能更进一步的最大化节省内存.

  Here's example straight from many addons that create UI dropdowns:   local function MyAddon_InitMenu()   local info   info = {}   info.text = "Settings Button1"   info.value = "Some value"   info.checked = PlayerSettings["Setting1"]   UIDropDownMenu_AddButton(info);   info = {};   info.text = "Settings Button2"   info.value = "Some other value"   info.checked = PlayerSettings["Setting2"]   UIDropDownMenu_AddButton(info);   -- end of settings buttons   info = {};   info.text = "Action Button1"   info.value = "Value to act on"   info.func = DoSomething   UIDropDownMenu_AddButton(info);   info = {};   info.text = "Action Button2"   info.value = "Another value to act on"   info.func = DoSomethingElse   UIDropDownMenu_AddButton(info);   end

  如果你曾经这样写代码, 那么在每次的菜单展现时你的插件会创建4个表(在这个例子中). 如果你使用循环来动态创建多个菜单(如, 列出所有角色信息)你将损失更多内存. 有效的内存使用方式是只定义一次info表, 去掉所有的其他的 info = {} 行并且加入 info.checked = nil 行在settings buttons后来清除状态不影响复用. 这样会减少内存损失, 每次调用只使用一个表.

  之后可以参照上面例子的样子, 建立一个持久的表供重复使用, 就完全的排除了垃圾回收的引发.

  -- UIDropDownMenu_AddButton only needs table to conveniently pass arguments by name   -- All values are copied to their correct places inside UIDropDownMenu_AddButton   -- and it doesn't need table we passed any longer after that. This allows us to   -- reuse same table for passing parameters, since we know that reference to this   -- table won't be saved anywhere as long as we use same fields and thus overwrite   -- previous values automatically without need to clear working table in some way.   local workingTableForInitMenu={}   local function MyAddon_InitMenu()   workingTableForInitMenu.func=nil -- erase value that could be there from last call   workingTableForInitMenu.text = "Settings Button1"   workingTableForInitMenu.value = "Some value"   workingTableForInitMenu.checked = PlayerSettings["Setting1"]   UIDropDownMenu_AddButton(workingTableForInitMenu)   workingTableForInitMenu.text = "Settings Button2"   workingTableForInitMenu.value = "Some other value"   workingTableForInitMenu.checked = PlayerSettings["Setting2"]   UIDropDownMenu_AddButton(workingTableForInitMenu)   -- end of settings buttons   workingTableForInitMenu.checked=nil
-- erase value that could be there from last settings button   workingTableForInitMenu.text = "Action Button1"   workingTableForInitMenu.value = "Value to act on"   workingTableForInitMenu.func = DoSomething   UIDropDownMenu_AddButton(workingTableForInitMenu)   workingTableForInitMenu.text = "Action Button2"   workingTableForInitMenu.value = "Another value to act on"   workingTableForInitMenu.func = DoSomethingElse   UIDropDownMenu_AddButton(workingTableForInitMenu)   end

  最后再次强调, 这能很好的工作在使用表的结构是相同的情况下. 上面的例子可以看到表的使用结构是不太相同的, 所以你的很小心. 忘记了这点的话会产生很多有趣而难以跟踪的bug. 如果表中字段很多, 在每次执行时清除表中先前的值, 会造成性能的损耗. 另一个要注意的是如果函数中的表存储了引用类型变量并且需要在未来某处检查这个值, 因此希望这是一个私有表.

0
相关文章