代码之家  ›  专栏  ›  技术社区  ›  horseyguy

如何有效地合并RubyC API中的两个哈希?

  •  3
  • horseyguy  · 技术社区  · 16 年前

    我正在为Ruby编写一个C扩展,它确实需要合并两个散列,但是Ruby1.8.6中的rb_Hash_Merge()函数是静态的。我尝试使用:

    rb_funcall(hash1, rb_intern("merge"), 1, hash2);
    

    但这太慢了,性能在这个应用程序中非常关键。

    有人知道如何进行这种融合,同时考虑到效率和速度吗?

    (注意,我试过简单地查看rb_hash_merge()的源代码并复制它,但是它被其他静态函数所困扰,这些静态函数本身也被更多的静态函数所困扰,因此几乎不可能解开……我需要另一种方法)

    1 回复  |  直到 13 年前
        1
  •  8
  •   Andrew Y    16 年前

    好的,看起来在已发布的API中可能无法进行优化。

    测试代码:

    #extconf.rb
    require 'mkmf'
    dir_config("hello")
    create_makefile("hello")
    
    
    // hello.c
    #include "ruby.h"
    
    static VALUE rb_mHello;
    static VALUE rb_cMyCalc;
    
    static void calc_mark(void *f) { }
    static void calc_free(void *f) { }
    static VALUE calc_alloc(VALUE klass) { return Data_Wrap_Struct(klass, calc_mark, calc_free, NULL); }
    
    static VALUE calc_init(VALUE obj) { return Qnil; }
    
    static VALUE calc_merge(VALUE obj, VALUE h1, VALUE h2) {
      return rb_funcall(h1, rb_intern("merge"), 1, h2);
    }
    
    static VALUE
    calc_merge2(VALUE obj, VALUE h1, VALUE h2)
    {
      VALUE h3 = rb_hash_new();
      VALUE keys;
      VALUE akey;
      keys = rb_funcall(h1, rb_intern("keys"), 0);
      while (akey = rb_each(keys)) {
        rb_hash_aset(h3, akey, rb_hash_aref(h1, akey));
      }
      keys = rb_funcall(h2, rb_intern("keys"), 0);
      while (akey = rb_each(keys)) {
        rb_hash_aset(h3, akey, rb_hash_aref(h2, akey));
      }
      return h3;
    }
    
    static VALUE
    calc_merge3(VALUE obj, VALUE h1, VALUE h2)
    {
      VALUE keys;
      VALUE akey;
      keys = rb_funcall(h1, rb_intern("keys"), 0);
      while (akey = rb_each(keys)) {
        rb_hash_aset(h2, akey, rb_hash_aref(h1, akey));
      }
      return h2;
    }
    
    void
    Init_hello()
    {
      rb_mHello = rb_define_module("Hello");
      rb_cMyCalc = rb_define_class_under(rb_mHello, "Calculator", rb_cObject);
      rb_define_alloc_func(rb_cMyCalc, calc_alloc);
      rb_define_method(rb_cMyCalc, "initialize", calc_init, 0);
      rb_define_method(rb_cMyCalc, "merge", calc_merge, 2);
      rb_define_method(rb_cMyCalc, "merge2", calc_merge, 2);
      rb_define_method(rb_cMyCalc, "merge3", calc_merge, 2);
    }
    
    
    # test.rb
    require "hello"
    
    h1 = Hash.new()
    h2 = Hash.new()
    
    1.upto(100000) { |x| h1[x] = x+1; }
    1.upto(100000) { |x| h2["#{x}-12"] = x+1; }
    
    c = Hello::Calculator.new()
    
    puts c.merge(h1, h2).keys.length if ARGV[0] == "1"
    puts c.merge2(h1, h2).keys.length if ARGV[0] == "2"
    puts c.merge3(h1, h2).keys.length if ARGV[0] == "3"
    

    现在测试结果:

    $ time ruby test.rb
    
    real    0m1.021s
    user    0m0.940s
    sys     0m0.080s
    $ time ruby test.rb 1
    200000
    
    real    0m1.224s
    user    0m1.148s
    sys     0m0.076s
    $ time ruby test.rb 2
    200000
    
    real    0m1.219s
    user    0m1.132s
    sys     0m0.084s
    $ time ruby test.rb 3
    200000
    
    real    0m1.220s
    user    0m1.128s
    sys     0m0.092s
    

    因此,在0.2s的操作中,我们可能会以最大~0.004s的速度剃掉。

    考虑到除了设置值之外,可能没有太多空间用于进一步优化。也许尝试着破解Ruby源代码本身——但是在那一点上,你不再真正开发“扩展”,而是改变语言,所以它可能不会工作。

    如果散列的连接是您在C部分中需要多次执行的操作,那么可能使用内部数据结构,并且在最后一个过程中只将它们导出到Ruby散列中,这将是优化操作的唯一方法。

    P.S.代码的初始框架 this excellent tutorial