1.8.7 中的to_proc
实现如下所示(请参阅 参考资料object.c
):
static VALUE
sym_to_proc(VALUE sym)
{
return rb_proc_new(sym_call, (VALUE)SYM2ID(sym));
}
而 1.9.2 实现(请参阅 参考资料string.c
)如下所示:
static VALUE
sym_to_proc(VALUE sym)
{
static VALUE sym_proc_cache = Qfalse;
enum {SYM_PROC_CACHE_SIZE = 67};
VALUE proc;
long id, index;
VALUE *aryp;
if (!sym_proc_cache) {
sym_proc_cache = rb_ary_tmp_new(SYM_PROC_CACHE_SIZE * 2);
rb_gc_register_mark_object(sym_proc_cache);
rb_ary_store(sym_proc_cache, SYM_PROC_CACHE_SIZE*2 - 1, Qnil);
}
id = SYM2ID(sym);
index = (id % SYM_PROC_CACHE_SIZE) << 1;
aryp = RARRAY_PTR(sym_proc_cache);
if (aryp[index] == sym) {
return aryp[index + 1];
}
else {
proc = rb_proc_new(sym_call, (VALUE)id);
aryp[index] = sym;
aryp[index + 1] = proc;
return proc;
}
}
如果您剥离了所有繁忙的初始化工作sym_proc_cache
,那么您(或多或少)剩下的是:
aryp = RARRAY_PTR(sym_proc_cache);
if (aryp[index] == sym) {
return aryp[index + 1];
}
else {
proc = rb_proc_new(sym_call, (VALUE)id);
aryp[index] = sym;
aryp[index + 1] = proc;
return proc;
}
所以真正的区别是 1.9.2to_proc
缓存了生成的 Procs,而 1.8.7 每次调用时都会生成一个全新的 Procs to_proc
。除非每次迭代都在单独的过程中完成,否则您所做的任何基准测试都会放大这两者之间的性能差异;但是,每个进程一次迭代会掩盖您尝试用启动成本进行基准测试的内容。
rb_proc_new
看起来几乎相同(参见1.8.7eval.c
或proc.c
1.9.2),但 1.9.2 可能会从rb_iterate
. 缓存可能是最大的性能差异。
值得注意的是,符号到哈希缓存的大小是固定的(67 个条目,但我不确定 67 来自哪里,可能与运算符的数量有关,并且通常用于符号到过程的转换):
id = SYM2ID(sym);
index = (id % SYM_PROC_CACHE_SIZE) << 1;
/* ... */
if (aryp[index] == sym) {
如果您使用超过 67 个符号作为 proc,或者您的符号 ID 重叠(mod 67),那么您将无法获得缓存的全部好处。
Rails 和 1.9 编程风格涉及很多简写,例如:
id = SYM2ID(sym);
index = (id % SYM_PROC_CACHE_SIZE) << 1;
而不是更长的显式块形式:
ints = strings.collect { |s| s.to_i }
sum = ints.inject(0) { |s,i| s += i }
鉴于这种(流行的)编程风格,通过缓存查找来以内存换取速度是有意义的。
您不太可能从 gem 中获得更快的实现,因为 gem 必须替换一大块核心 Ruby 功能。不过,您可以将 1.9.2 缓存修补到 1.8.7 源中。