2009-08-10 5 views
3

J'écris une extension C pour Ruby qui a vraiment besoin de fusionner deux hachages, cependant la fonction rb_hash_merge() est STATIC dans Ruby 1.8.6. J'ai essayé à la place d'utiliser:Comment fusionner efficacement deux hachages dans Ruby C API?

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

mais c'est beaucoup trop lent, et les performances sont très critiques dans cette application.

Est-ce que quelqu'un sait comment effectuer cette fusion avec efficacité et rapidité?

(Note J'ai essayé simplement de lire la source pour rb_hash_merge() et de la répliquer mais elle est RIDDLED avec d'autres fonctions statiques, elles-mêmes criblées de fonctions encore plus statiques, il semble presque impossible de démêler ... besoin d'une autre manière)

+2

"la performance est très critique dans cette application." == pas Ruby, à mon humble avis. Ce que vous mentionnez ressemble à la seule approche sans construire dans le code fragile. –

+0

Cela ne me dérange pas si le code est fragile, juste tant qu'il fonctionne sur ruby ​​1.8.6 et ruby ​​1.9.1 (même avec l'aide de préprocesseur defs) – horseyguy

+1

J'ai fait quelques tests, et on dirait que vous ne serez pas être en mesure d'obtenir beaucoup en utilisant l'API standard ... Je vais poster le code dans une réponse juste pour le souci de mise en forme. –

Répondre

8

Ok, il semble qu'il ne soit pas possible d'optimiser au sein de l'API publiée.

Code d'essai:

#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" 

Maintenant, les résultats des tests:

$ 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 

Il semble donc que nous pourrions raser au maximum de ~ 0.004s sur une opération 0,2s. Etant donné qu'il n'y a probablement pas grand-chose d'autre à part définir les valeurs, il n'y a peut-être pas beaucoup d'espace pour d'autres optimisations. Peut-être essayer de pirater la source de rubis elle-même - mais à ce stade, vous ne développez plus vraiment "extension" mais plutôt changer le langage, donc ça ne marchera probablement pas. Si la jointure des hachages est quelque chose que vous devez faire plusieurs fois dans la partie C - alors probablement utiliser les structures de données internes et les exporter seulement dans le hachage Ruby dans le dernier passage serait le seul moyen d'optimiser les choses.

p.s. Le squelette initial pour le code emprunté de this excellent tutorial

+0

ouah! merci pour votre analyse très approfondie de la situation! :) oui, en fonction de vos résultats, il semble que je doive trouver une autre façon d'y arriver ... :) cheers! – horseyguy

+0

vous êtes les bienvenus :-) Si vous trouvez le temps d'expérimenter et qu'il est pertinent pour votre situation - peut-être pourriez-vous vérifier si le code similaire dans Lua fonctionne, utiliser des tables et afficher la comparaison ici. Je pense que la solution utilisant les infrastructures de données de Lua devrait être un peu plus rapide. –

Questions connexes