首先,我知道怎么extend
和include
他们的工作,什么通常用于等等.无论是一个好主意与否是不是我的问题的一部分.
我的问题是:有多贵extend
?这是扩展实例和单例对象的常用Javascript技术.人们可以在Ruby中做类似的事情,但是如果在很多对象上使用它会很慢吗?
Nik*_* B. 24
让我们看看如果你调用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);
}
Run Code Online (Sandbox Code Playgroud)
因此模块被混合到对象的单例类中.获取单例类的成本有多高?那么,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;
}
Run Code Online (Sandbox Code Playgroud)
这就是全部O(1)
.之后,调用rb_include_module
(class.c:660),这是O(n)
关于单例类已经包含的模块数量,因为它需要检查模块是否已经存在(单例类中通常没有很多包含的模块,所以这没关系).
结论: extend
这不是一个非常昂贵的操作,所以如果你愿意,你可以经常使用它.我能想象到的唯一的事情就是该方法的决议要求,以实例后的extend
可能是更复杂一点,因为需要检查模块的一个附加层.如果你知道单例类已经存在,那么两者都不是问题.在这种情况下,extend
几乎不会引入额外的复杂性.但是,如果应用过于广泛,动态扩展实例可能会导致代码非常难以理解,因此请注意.
这个小基准测试表明了有关性能的情况:
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
Run Code Online (Sandbox Code Playgroud)
结果
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)
Run Code Online (Sandbox Code Playgroud)
有趣的是,Module#include
在元类上实际上比Object#extend
它慢,尽管它完全相同(因为我们需要特殊的Ruby语法来访问元类).Object#extend
如果单例类已经存在,则快两倍多.Object#define_singleton_method
是最慢的(虽然如果你只想动态添加一个方法,它可以更清晰).
最有趣的结果是底部的两个,但是:创建一个对象然后扩展它只是创建对象的速度的近4倍!因此,如果您在循环中创建了大量的一次性对象,例如,如果扩展其中的每一个,它可能会对性能产生重大影响.在这里创建一个包含mixin的临时类是非常有效的.
需要注意的一点是,extend(和include)都会重置ruby使用的缓存来从名称中查找方法实现.
我记得在几年前的railsconf会议上提到这是一个潜在的性能问题.我不知道实际的性能影响是什么,并且让我觉得难以独立进行基准测试.适应Niklas的基准,我做到了
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
Run Code Online (Sandbox Code Playgroud)
在第一种情况下,我做所有扩展,然后循环遍历所有人并调用.name
方法.缓存失效显然仍然会发生,但是一旦我在第一个人身上调用了名字,缓存就会变暖并且永远不会变冷
在第二种情况下,我交替调用extend和call .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)
Run Code Online (Sandbox Code Playgroud)
因此交错调用较慢.我无法确定唯一的原因是方法查找缓存被清除了.