Ruby:代理模式,减少方法调用
如何代理 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不要重置记录器本身,而是刷新并重新打开其输出:
Instead of resetting the logger itself, flush and reopen its output:
首先:你的问题听起来像是你过早地优化。仅当您知道您的代码太慢时才应该进行优化。 (并且您的基准测试仅显示出微小的差异)
也就是说,如果记录器已更新,您可以使上下文通知每个代理:
但同样,这似乎并不值得额外的复杂性。
(相关:这是一个非常好的演示,展示了 @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:
but again, this does not really seem worth the additional complexity.
(related: this is a very nice presentation showing @tenderlove optimizing the ARel gem)