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

发布于 2024-10-05 11:15:49 字数 1815 浏览 2 评论 0原文

如何代理 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"

这工作得很好,除了我用 1 个方法调用换成了 2 个方法调用(1 个访问器;返回记录器)。我仍然需要调用 LoggerProxy.:debug, :info, ...,这又调用原始记录器!因此,有 1 个方法调用时有 2 个方法调用。

我不想搞乱 Logger 类,或者重载它,因为我想在将来使用其他记录器、syslog、推出我自己的记录器或类似的记录器。

有没有办法减少方法调用次数以提高性能?

-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)

How do I proxy the ruby logger and keep performance?

So, we have an requirement at work, quite reasonable. When a program is sent the signal HUP
the log is flushed and restarted.

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

The problem, is that if context.logger is reset, then @logger still points to the old one.

So, I thought I would proxy 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"

This works fine, except I've traded one method call for 2 method calls (1 accessor; which returns the logger). I still have to call LoggerProxy.:debug, :info, ..., which in turn calls the original logger! Ergo, 2 methods calls, where there was one.

I don't want to monkey with Logger class, or overload it, because I want to use other loggers in the future, syslog, roll my own, or some such.

Is there a way reduce the number of method calls for performance?

-daniel

Update: in response to a question about performance, here is the sample test.

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

献世佛 2024-10-12 11:15:49

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

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

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

Instead of resetting the logger itself, flush and reopen its output:

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

Signal.trap('HUP') {
  logfile.flush
  logfile.reopen 'my_file.log', 'w+'
}
つ低調成傷 2024-10-12 11:15:49

首先:你的问题听起来像是你过早地优化。仅当您知道您的代码太慢时才应该进行优化。 (并且您的基准测试仅显示出微小的差异)

也就是说,如果记录器已更新,您可以使上下文通知每个代理:

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

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

(相关:这是一个非常好的演示,展示了 @tenderlove 优化ARel 宝石

First: your question smells like you're optimizing prematurely. You should only optimize if you know your code is too slow. (And your benchmark show only a tiny difference)

That said, you could make the Context notify every proxy if logger is ever updated:

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

but again, this does not really seem worth the additional complexity.

(related: this is a very nice presentation showing @tenderlove optimizing the ARel gem)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文