2013-05-03 106 views
4

我看到很有趣的和灾难性的行为与红宝石,见下文红宝石exception.message花费过多时间

class ExceptionTest 

    def test 
    @result = [0]*500000 

    begin 
     no_such_method 
    rescue Exception => ex 
     puts "before #{ex.class}" 
     st = Time.now 
     ex.message 
     puts "after #{Time.now-st} #{ex.message}" 
    end 

    end 
end 

ExceptionTest.new.test 

代码理想ex.message不应该采取任何时间来执行,因此所用时间应在毫秒,但这里是输出

before NameError 
after 0.462443 undefined local variable or method `no_such_method' for #<ExceptionTest:0x007fc74a84e4f0> 

如果我给你[0]*500000到一个局部变量,而不是实例变量如result = [0]*500000它运行正常

before NameError 
after 2.8e-05 undefined local variable or method `no_such_method' for #<ExceptionTest:0x007ff59204e518> 

看起来莫名其妙ex.message是循环直通实例变量,为什么会这样做,请赐教!

我试过它在红宝石ruby-1.9.2-p290,ruby-1.9.1-p376,ruby 2.0.0以及在codepad.org上的任何版本的ruby。

编辑:文件中的错误http://bugs.ruby-lang.org/issues/8366

+0

你真的引用了局部变量,所以它的创建没有完全优化吗? – 2013-05-03 23:02:21

+0

@JoachimIsaksson我尝试打印测试方法的开始和结束的第一个和最后一个项目,但仍然与本地var它是快速的,即使它不是,问题仍然如何来ex.message是受所有这些影响 – 2013-05-03 23:04:52

+1

http:///www.mikeperham.com/2012/03/03/the-perils-of-rescue-exception/ – 2013-05-04 01:48:07

回答

2

挖掘到the source后,我发现NameError#message首先尝试打电话给你的对象inspect,如果该字符串是太长,它调用to_s代替。预计inspect会花费很长时间,因为它会递归检查每个实例变量。 (见documentation的检查。)

从error.c:

d = rb_protect(rb_inspect, obj, &state); 
if (state) 
    rb_set_errinfo(Qnil); 
if (NIL_P(d) || RSTRING_LEN(d) > 65) { 
    d = rb_any_to_s(obj); 
} 
desc = RSTRING_PTR(d); 

可以归结这个测试,看看它有什么毕竟跟例外:

class InspectTest 
    def initialize 
    @result = [0]*500000 
    end 

    def test 
    puts "before" 
    st = Time.now 
    self.inspect 
    puts "after #{Time.now-st}" 
    end 
end 

InspectTest.new.test 
#before 
#after 0.162566 

InspectTest.new.foo 
# NoMethodError: undefined method `foo' for #<InspectTest:0x007fd7e317bf20> 

e=InspectTest.new.tap {|e| e.instance_variable_set(:@result, 0) } 
e.foo 
# NoMethodError: undefined method `foo' for #<InspectTest:0x007fd7e3184580 @result=0> 
e.test 
#before 
#after 1.5e-05 

如果您知道您的班级将持有大量数据并可能抛出许多例外情况,那么您理论上可以覆盖#inspect

class InspectTest 
    def inspect 
    to_s 
    end 
end 

InspectTest.new.test 
#before 
#after 1.0e-05 
+0

不理解使用'InspectTest.new.foo'和'e.foo'的技巧。你能解释一下这些吗? – 2013-05-04 04:14:18

+0

我调用'foo'的唯一原因是触发'NoMethodError'并显示输出结果。这表明当'inspect'太长时,类名会被截断。 – davogones 2013-05-04 05:04:41

+0

非常感谢,我仍然认为这是一个bug,因为检查可能很大的对象会总是很慢,ruby在想什么,如果这些是第三方插件,实施检查是不现实的,更好的办法是覆盖NameError#to_s – 2013-05-04 06:49:34