Как эффективно объединить два хэша в Ruby C API?

StackOverflow https://stackoverflow.com/questions/1256975

  •  12-09-2019
  •  | 
  •  

Вопрос

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

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

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

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

(Обратите внимание, что я попытался просто посмотреть исходный код для rb_hash_merge() и скопировать его, но он ПРОНИЗАН другими статическими функциями, которые сами по себе пронизаны еще большим количеством статических функций, поэтому кажется, что распутать его почти невозможно ... мне нужен другой способ)

Это было полезно?

Решение

Хорошо, похоже, что оптимизация в рамках опубликованного 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 hash на заключительном этапе.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top