【问题标题】:How to efficiently merge two hashes in Ruby C API?如何在 Ruby C API 中有效地合并两个哈希?
【发布时间】:2009-08-10 20:17:36
【问题描述】:

我正在为 Ruby 编写一个 C 扩展,它确实需要合并两个哈希,但是 rb_hash_merge() 函数在 Ruby 1.8.6 中是 STATIC。我尝试改用:

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

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

有谁知道如何在兼顾效率和速度的情况下执行此合并?

(请注意,我尝试简单地查看 rb_hash_merge() 的源并复制它,但它与其他静态函数相去甚远,这些静态函数本身充满了更多静态函数,因此似乎几乎不可能解开......我需要另一种方式)

【问题讨论】:

  • "性能在这个应用程序中非常关键。" == 不是 Ruby,恕我直言。您提到的似乎是唯一无需构建脆弱代码的方法。
  • 我不介意代码是否脆弱,只要它适用于 ruby​​ 1.8.6 和 ruby​​ 1.9.1(即使在预处理器 defs 的帮助下)
  • 我做了一些测试,看起来你无法使用标准 API 获得很多东西......我将在答案中发布代码只是为了格式化。

标签: c ruby ruby-c-extension


【解决方案1】:

好的,看起来可能无法在已发布的 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.2 秒的操作中最多剃掉约 0.004 秒。

鉴于除了设置值之外可能没有那么多,可能没有那么多空间进行进一步优化。也许尝试破解 ruby​​ 源代码本身 - 但此时您不再真正开发“扩展”,而是更改语言,因此它可能行不通。

如果哈希连接是您需要在 C 部分中多次执行的操作 - 那么可能使用内部数据结构并仅在最后一遍将它们导出到 Ruby 哈希中将是优化事物的唯一方法。

附言从this excellent tutorial借来的代码的初始骨架

【讨论】:

  • 哇!感谢您对情况的非常透彻的分析! :) 所以,是的,根据您的结果,看来我必须找到另一种解决方法... :) 干杯!
  • 不客气 :-) 如果您有时间进行实验并且它与您的情况相关 - 也许您可以使用表格检查 Lua 中类似代码的执行情况,然后发布比较这里。我认为使用 Lua 数据结构的解决方案应该更快一些。
猜你喜欢
  • 2017-12-18
  • 1970-01-01
  • 1970-01-01
  • 2016-01-06
  • 2012-01-14
  • 2021-07-25
  • 2016-09-02
  • 2017-07-16
  • 2014-03-02
相关资源
最近更新 更多