Как эффективно объединить два хэша в Ruby C API? - PullRequest
3 голосов
/ 11 августа 2009

Я пишу расширение C для Ruby, которому действительно нужно объединить два хэша, однако функция rb_hash_merge () в Ruby 1.8.6 является STATIC. Вместо этого я попытался использовать:

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

но это слишком медленно, и производительность в этом приложении очень важна.

Кто-нибудь знает, как выполнить это слияние с учетом эффективности и скорости?

(Обратите внимание, что я попытался просто посмотреть на источник для rb_hash_merge () и реплицировать его, но он ЗАБИРАЕТСЯ с другими статическими функциями, которые сами по себе пронизаны еще более статическими функциями, поэтому кажется, что практически невозможно отделить ... по-другому)

1 Ответ

7 голосов
/ 11 августа 2009

Хорошо, похоже, что невозможно оптимизировать в опубликованном 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,004 с при операции 0,2 с.

Учитывая, что, вероятно, не так уж много, кроме установки значений, возможно, не останется так много места для дальнейшей оптимизации. Возможно, попытайтесь взломать сам источник ruby ​​- но на этом этапе вы больше не разрабатываете «расширение», а скорее меняете язык, так что это, вероятно, не будет работать.

Если объединение хэшей является чем-то, что вам нужно делать много раз в части C - тогда, вероятно, использование внутренних структур данных и только экспорт их в хэш Ruby в последнем проходе будет единственным способом оптимизации вещей.

p.s. Первоначальный скелет для кода, заимствованный из этого превосходного учебника

...