まず第一に、私はextend
とinclude
がどのように機能しているのかを知っています。彼らは通常、どのように使用されていますか。それは良いアイデアかどうかは私の質問の一部ではありません。ルビーではどれくらいのコストがかかりますか?
私の質問は:どれくらい高価ですかextend
?これは、インスタンスとシングルトンオブジェクトを拡張する一般的なJavascriptテクニックです。 1つはRubyでも同様のことができますが、多くのオブジェクトで使用すると遅くなるでしょうか?
まず第一に、私はextend
とinclude
がどのように機能しているのかを知っています。彼らは通常、どのように使用されていますか。それは良いアイデアかどうかは私の質問の一部ではありません。ルビーではどれくらいのコストがかかりますか?
私の質問は:どれくらい高価ですかextend
?これは、インスタンスとシングルトンオブジェクトを拡張する一般的なJavascriptテクニックです。 1つはRubyでも同様のことができますが、多くのオブジェクトで使用すると遅くなるでしょうか?
は、あなたがオブジェクトの上にextend
を呼び出す場合のRuby 1.9.3-p0とで何が起こるか見てみましょう:
/* eval.c, line 879 */
void
rb_extend_object(VALUE obj, VALUE module)
{
rb_include_module(rb_singleton_class(obj), module);
}
ので、モジュールは、オブジェクトのシングルトンクラスに混入されます。シングルトンクラスをフェッチするのはどれくらいの費用がかかりますか?まあ、rb_singleton_class_of(obj)
はsingleton_class_of(obj)
(class.c:1253)です。シングルトンクラスが以前にアクセスされた(したがって既に存在している)場合、すぐにその値が返されます。ない場合は、新しいクラスが同様にあまりにも高価ではありませんどのmake_singleton_class
によって作成されます。
/* class.c, line 341 */
static inline VALUE
make_singleton_class(VALUE obj)
{
VALUE orig_class = RBASIC(obj)->klass;
VALUE klass = rb_class_boot(orig_class);
FL_SET(klass, FL_SINGLETON);
RBASIC(obj)->klass = klass;
rb_singleton_class_attached(klass, obj);
METACLASS_OF(klass) = METACLASS_OF(rb_class_real(orig_class));
return klass;
}
これは、すべてのO(1)
です。 rb_include_module
(class.c:660)が呼び出されます。これは、シングルトンクラスがすでに含まれているモジュールの数に関して、O(n)
です。モジュールが既に存在するかどうかをチェックする必要があるためですシングルトンクラスでは、これは問題ありません)。
結論: は非常に高価な操作ではありませんので、したい場合は頻繁に使用できます。私が想像できる唯一のことは、extend
の後にインスタンスへのメソッド呼び出しの解決がモジュールの追加のレイヤーをチェックする必要があるため、もう少し複雑になる可能性があるということです。シングルトンクラスがすでに存在することが分かっている場合は、どちらも問題になりません。その場合、extend
はほとんど複雑さをもたらさない。 しかし、インスタンスを動的に拡張すると、あまりに広範囲に適用するとコードが非常に読みにくくなる可能性がありますので注意してください。 (それはまったく同じことをしたが、興味深いことに、メタクラスのModule#include
がObject#extend
よりも、実際に遅い
user system total real
Object#extend 0.200000 0.060000 0.260000 ( 0.272779)
Object#extend (existing singleton class) 0.130000 0.000000 0.130000 ( 0.130711)
Module#include 0.280000 0.040000 0.320000 ( 0.332719)
Object#define_singleton_method 0.350000 0.040000 0.390000 ( 0.396296)
create object without extending 0.060000 0.010000 0.070000 ( 0.071103)
create object with extending 0.340000 0.000000 0.340000 ( 0.341622)
create object with temp class 0.080000 0.000000 0.080000 ( 0.076526)
require 'benchmark'
module DynamicMixin
def debug_me
puts "Hi, I'm %s" % name
end
end
Person = Struct.new(:name)
def create_people
100000.times.map { |i| Person.new(i.to_s) }
end
if $0 == __FILE__
debug_me = Proc.new { puts "Hi, I'm %s" % name }
Benchmark.bm do |x|
people = create_people
case ARGV[0]
when "extend1"
x.report "Object#extend" do
people.each { |person|
person.extend DynamicMixin
}
end
when "extend2"
# force creation of singleton class
people.map { |x| class << x; self; end }
x.report "Object#extend (existing singleton class)" do
people.each { |person|
person.extend DynamicMixin
}
end
when "include"
x.report "Module#include" do
people.each { |person|
class << person
include DynamicMixin
end
}
end
when "method"
x.report "Object#define_singleton_method" do
people.each { |person|
person.define_singleton_method("debug_me", &debug_me)
}
end
when "object1"
x.report "create object without extending" do
100000.times { |i|
person = Person.new(i.to_s)
}
end
when "object2"
x.report "create object with extending" do
100000.times { |i|
person = Person.new(i.to_s)
person.extend DynamicMixin
}
end
when "object3"
class TmpPerson < Person
include DynamicMixin
end
x.report "create object with temp class" do
100000.times { |i|
person = TmpPerson.new(i.to_s)
}
end
end
end
end
結果:
は、この小さなベンチマークは、パフォーマンスに関する状況を示していますメタクラスにアクセスするために特別なRuby構文が必要なため)。 Object#extend
は、シングルトンクラスがすでに存在する場合には2倍以上の速さです。 Object#define_singleton_method
は最も遅いですが(ただし、1つのメソッドだけを動的に追加したい場合は、よりクリーンにできます)
もっとも興味深い結果は、下の2つですが、次のとおりです。オブジェクトを作成してからそれを拡張するのは、オブジェクトを作成する場合の約4倍です!たとえば、ループ内に多くのオブジェクトを作成すると、そのオブジェクトを1つずつ拡張すると、パフォーマンスに大きな影響を与える可能性があります。明示的にmixinを含む一時クラスを作成する方がはるかに効率的です。
注意しなければならないことは、rubyがメソッド実装を名前から検索するために使用するキャッシュを再設定することです。
これは、数年前のrailsconfのセッションでパフォーマンス上の問題が発生する可能性があることを覚えています。実際のパフォーマンスへの影響は何か分かりません。私を孤立してベンチマークするのは難しいものです。ニクラスベンチマークの適合、私はすべてのすべての人々を通って延び、その後のループでください最初のケースで
require 'benchmark'
module DynamicMixin
def debug_me
puts "Hi, I'm %s" % name
end
end
Person = Struct.new(:name)
def create_people
100000.times.map { |i| Person.new(i.to_s) }
end
if $0 == __FILE__
debug_me = Proc.new { puts "Hi, I'm %s" % name }
Benchmark.bm do |x|
people = create_people
x.report "separate loop" do
people.each { |person|
person.extend DynamicMixin
}
people.each {|p| p.name}
end
people = create_people
x.report "interleaved calls to name" do
people.each { |person|
person.extend DynamicMixin
person.name
}
end
end
end
を行なったし、.name
メソッドを呼び出します。キャッシュの無効化はまだ起こっていますが、一度名前を呼び出すとキャッシュが暖かくなり、寒いことはありません。
2番目のケースでは、キャッシュを延長して.name
いつも冷たい私は.nameの
を呼び出すときに私が得る数字だから、インターリーブ呼び出しが遅い
user system total real
separate loop 0.210000 0.030000 0.240000 ( 0.230208)
interleaved calls to name 0.260000 0.030000 0.290000 ( 0.290910)
です。唯一の理由は、メソッドルックアップキャッシュがクリアされていることは確かではありません。
キャッシュの無効化がオブジェクトレベルまたはグローバルレベルで行われるかどうか知りたいですか?私は、これがこれと無関係の他のすべてのクラスを無効にするのであれば心配しています。 – lulalala
ルビーのバージョンと実装によって異なります。現在のバージョンのMRIでは、それはクラスごとのキャッシュだと思います –
extend
を呼び出すと、Rubyのすべてのメソッドキャッシュがグローバルとインラインの両方で無効になります。これは、どのクラス/オブジェクトをも拡張するたびにすべてのメソッドキャッシュがフラッシュされ、どのメソッド呼び出しもコールドキャッシュにヒットします。
なぜこれが悪く、メソッドキャッシュは何のために使用されていますか?
メソッドキャッシュは、Rubyプログラムを実行するときに時間を節約するために使用されます。たとえば、value.foo
を呼び出すと、ランタイムはvalue
の最新のクラスに関する情報とクラス階層foo
があるインラインキャッシュを追加します。これは、同じコールサイトからの将来のコールを高速化するのに役立ちます。
プログラムの実行中に頻繁にクラス/オブジェクトを拡張すると、処理速度が大幅に遅くなります。拡張するクラス/オブジェクトをプログラムの先頭に限定するのが最善です。
メソッドの定義やメソッドの解決に影響するその他の変更についても同様です。問題の詳細についてはinfomration
これは完全な話ではない、悲しいかな故ジェームズGolick、http://jamesgolick.com/2013/4/14/mris-method-caches.html
によって、この記事を参照してください。 extendを呼び出すと、グローバルとインラインのRubyのすべてのメソッドキャッシュが無効になります。プログラムの実行中に頻繁にクラスやオブジェクトを拡張すると、かなり遅くなります。 – akuhn