Ruby:代理模式,减少方法调用

时间:2010-11-30 23:52:53

标签: ruby logging proxy-pattern

如何代理ruby记录器并保持性能?

所以,我们在工作中有一个要求,非常合理。当程序发送信号HUP时 刷新并重新启动日志。

class LocalObject

  attr_accessor :logger

  def initialize context
    # one less method call! Yea! performance++
    @logger = context.logger
  end

  def something
    @logger.info "Hello world"
  end

end

问题是,如果重置了context.logger,那么@logger仍然指向旧的。

所以,我以为我会代理记录器:

class LoggerProxy
  attr_accessor :logger

  def debug *args
    @logger.send :debug, args
  end

  def info *args
    @logger.send :info, args
  end
end

context.logger = LoggerProxy.new
context.logger.logger = Logger.new 'my_file.log'

Signal.trap('HUP') { 
  context.logger.logger = Logger.new 'my_file.log'
}
...
@logger = context.logger
@logger.info "Hello world"

这很好用,除了我交换了一个方法调用2个方法调用(1个访问器;它返回记录器)。我仍然需要调用LoggerProxy.:debug,:info,...,然后调用原始记录器! Ergo,2个方法调用,其中有一个。

我不想使用Logger类,或者重载它,因为我希望将来使用其他记录器,系统日志,自己动手或其他类似记录器。

有没有办法减少方法调用的性能?

-daniel

更新:在回答关于性能的问题时,这是样本测试。

require 'logger'
require 'benchmark';

class MyLogger

  attr_accessor :logger

  def info msg
    @logger.info msg
  end

end

myLogger = Logger.new '/dev/null' # dev null to avoid IO issues
myLoggerProxy = MyLogger.new
myLoggerProxy.logger = myLogger

n = 100000
Benchmark.bm do | benchmarker |
  # plain logger
  benchmarker.report { n.times { myLogger.info 'opps' } }

  # via accessor
  benchmarker.report { n.times { myLoggerProxy.logger.info 'opps' } }

  # via proxy
  benchmarker.report { n.times { myLoggerProxy.info 'opps' } }
end


      user     system      total        real
  1.580000   0.150000   1.730000 (  1.734956)
  1.600000   0.150000   1.750000 (  1.747969)
  1.610000   0.160000   1.770000 (  1.767886)

2 个答案:

答案 0 :(得分:4)

不是重置记录器本身,而是刷新并重新打开其输出:

logfile = File.open 'my_file.log', 'w+'
context.logger = Logger.new logfile

Signal.trap('HUP') {
  logfile.flush
  logfile.reopen 'my_file.log', 'w+'
}

答案 1 :(得分:2)

首先:你的问题有点像你过早地进行优化。只有在知道您的代码太慢时才应优化。 (而你的基准显示只有很小的差异)

也就是说,如果记录器被更新,你可以让Context通知每个代理:

class ProxyLogger
  attr_accessor :logger

  def initialize(context)
    context.register(self)
  end
end

class Context
  attr_accessor :logger

  def initialize
    @proxies = []
  end

  def logger=(val)
    @logger = val
    @proxies.each { |p| p.logger = val }
  end

  def register(proxy)
    @proxies << proxy
  end
end

但同样,这似乎并不值得额外的复杂性。

(相关:this is a very nice presentation showing @tenderlove optimizing the ARel gem