lua table vs closure

  最近在重构自己写的框架中的定时器模块,需要把回调函数保存起来,大概如下:

function timer_mgr:save_timer( this,callback )
    return { this = this,callback = callback}
end

-- 创建新定时器
-- @after:延迟N秒后启动定时器
-- @repeated:N秒循环
-- @this:回调对象
-- @callbakck:回调函数
function timer_mgr:new_timer( after,repeated,this,callback )
    local timer_id = self:next_id()
    self.timers[timer_id] = save_timer( this,callback )
end

function timer_mgr:do_timer( timer_id )
    local timer = self.timers[timer_id]

    -- 调用之前保存的定时器回调
    return timer.callback( timer.this )
end

正常情况下,用table保存定时器的回调参数,毕竟lua中也没有太多的数据结构可以选择。不过,我们也可以这样用closure来保存:

function timer_mgr:save_timer( this,callback )
    return function()
        return callback( this )
    end
end

-- 创建新定时器
-- @after:延迟N秒后启动定时器
-- @repeated:N秒循环
-- @this:回调对象
-- @callbakck:回调函数
function timer_mgr:new_timer( after,repeated,this,callback )
    local timer_id = self:next_id()
    self.timers[timer_id] = save_timer( this,callback )
end

function timer_mgr:do_timer( timer_id )
    local timer = self.timers[timer_id]

    -- 调用之前保存的定时器回调
    return timer()
end

这样似乎看起来更优雅更方便一些,不过,频繁创建closure也是很消耗内存和cpu的,需要和table对比一下:

function test_table_object( this,cb )
    return { this = this,cb = cb }
end

function test_closure_object( this,cb )
    return function()
        return cb( this )
    end
end

local function cb()
    print("do nothing ...")
end

local max = 1000000

local table_mgr = {}
local closure_mgr = {}

function test_table()
    
    local beg_m = collectgarbage("count")
    local beg_tm = os.clock()

    for idx = 1,max do
        table_mgr[idx] = test_table_object( {},cb )
    end

    local end_m = collectgarbage("count")
    local end_tm = os.clock()

    print("table test",end_m - beg_m,end_tm - beg_tm)
end

function test_closure()
    
    local beg_m = collectgarbage("count")
    local beg_tm = os.clock()

    for idx = 1,max do
        table_mgr[idx] = test_closure_object( {},cb )
    end

    local end_m = collectgarbage("count")
    local end_tm = os.clock()

    print("closure test",end_m - beg_m,end_tm - beg_tm)
end

collectgarbage("stop")
collectgarbage("collect")

test_closure()
test_table()

这段代码,分别在win10 - I3 cpu和debian7(虚拟机) - A8 cpu下测试:

closure test	117946.5	0.489
table test	125000.0	0.446

closure test	180446.5	0.75
table test	171875.0	0.72

可以看到,table和closure的消耗其实都差不多。但closure在这个应用场景下就优雅得多,因为可以很方便地传更多的参数,比如:

function timer_mgr:save_timer( this,callback,... )
    return function()
        return callback( this,... )
    end
end

function timer_mgr:new_timer( after,repeated,this,callback,... )
    local timer_id = self:next_id()
    self.timers[timer_id] = save_timer( this,callback,... )
end

function timer_mgr:do_timer( timer_id )
    local timer = self.timers[timer_id]

    -- 调用之前保存的定时器回调
    return timer()
end

而table要传可变参则不太好处理了,需要另外创建一个table来pack参数,调用时再unpack,或者只用一个table把callback函数和参数存一起,调用时把函数移除再unpack。总而言之,在这种需要保存参数的回调,closure更合适。当然,如果你的回调函数不带参数,那就是另外一码事了。

  查了下,之前也有人做了类似的测试,结果也差不多:http://lua-users.org/wiki/ObjectOrientationClosureApproach

猜你喜欢

转载自www.cnblogs.com/coding-my-life/p/10923988.html