【问题标题】:Ruby exception.message taking too much timeRuby exception.message 花费了太多时间
【发布时间】:2013-05-03 22:55:51
【问题描述】:

我看到了 ruby​​ 的非常有趣和灾难性的行为,请参阅下面的代码

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​​ ruby​​-1.9.2-p290、ruby-1.9.1-p376、ruby 2.0.0 以及 codepad.org 上的任何 ruby​​ 版本上尝试过。

编辑:提交错误http://bugs.ruby-lang.org/issues/8366

【问题讨论】:

  • 你是否真的引用了局部变量,所以它的创建没有完全优化?
  • @JoachimIsaksson 我确实尝试在测试方法的开始和结束时打印第一个和最后一个项目,但仍然使用本地变量它很快,即使不是,问题仍然存在 ex.message 是如何受到影响的通过这一切
  • @ScottBartell 这和这有什么关系?

标签: ruby exception


【解决方案1】:

在深入了解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

【讨论】:

  • 不明白使用InspectTest.new.fooe.foo 的技巧。你能解释一下吗?
  • 我调用foo 的唯一原因是触发NoMethodError 并显示输出的样子。这说明inspect太长时类名被截断。
  • 非常感谢,我仍然认为这是一个错误,因为对可能很大的对象进行检查总是很慢,红宝石在想什么,实施更多检查是不切实际的,所以当这些是第三派对插件,更好的是覆盖 NameError#to_s
  • 确实,效率不高。但是,我不希望 NameErrorNoMethodError 在生产中发生很多或根本不会发生。 (这些是唯一受影响的异常类型。)我猜他们试图在提供有用但不过长的错误消息之间取得平衡。覆盖NameError#to_s 会使错误消息对存储“正常”数据量的其他类的用处降低。 inspect 是真正的问题,因为它会输出对象的如此冗长的表示。
  • @davogones 可能是他们应该在 ex.message 中有一个“检查”选项或一个单独的方法 ex.message_slow :) 现在我只看到重写 NameError#to_s 作为唯一的解决方案,尽管我提交了一个错误bugs.ruby-lang.org/issues/8366
猜你喜欢
  • 1970-01-01
  • 2017-02-15
  • 2020-07-23
  • 2013-07-11
  • 2014-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多