5

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

4

1 に答える 1

4

sourceを掘り下げた後、最初にオブジェクトNameError#messageを呼び出そうとすることがわかりました。inspectその文字列が長すぎる場合は、to_s代わりに呼び出します。inspectすべてのインスタンス変数を再帰的に検査するため、時間がかかることが予想されます。( inspectのドキュメントを参照してください。)

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
于 2013-05-04T02:18:08.257 に答える