概要

RustのHashMapをRubyから使えるようにしてみたが…… の続きです。

前回、以下のように書いた。

ハッシュ値を取得するのに、Rubyのメソッドが動的に呼び出され、Rustのhash関数も実行されている。
さらに Eqの時もRubyのメソッドが動的に呼び出されている。
Rubyからメソッド呼び出しする場合は普通はメソッドをキャッシュしておくので次の呼び出しは速くなるが、CやRustから呼ばれる場合は毎回メソッドテーブルから探しに行く必要があって、とても遅い。

今回、メソッドをキャッシュさせることで find, insert, delete が速くなることを確認できた。

結果

before:

$ ruby benchmark.rb --rust --seed 1
Hash: Rust
Seed: 1
       user     system      total        real
values  1.896433   0.152249   2.048682 (  2.049503)
keys    1.589012   0.118299   1.707311 (  1.708012)
find   11.612382   0.002426  11.614808 ( 11.617854)
insert 15.397090   0.012138  15.409228 ( 15.413532)
delete  7.581418   0.001750   7.583168 (  7.585473)

after:

$ ruby benchmark.rb --rust --seed 1
Hash: Rust
Seed: 1
       user     system      total        real
values  1.924479   0.166750   2.091229 (  2.092445)
keys    1.514441   0.112787   1.627228 (  1.628187)
find    8.677083   0.001990   8.679073 (  8.681669)
insert 12.661791   0.010463  12.672254 ( 12.678383)
delete  5.908580   0.001670   5.910250 (  5.912530)

insertは標準のHashクラスより37%も速くなっている。やったね

やったこと

これまで、hash関数は以下のように実装していた。

    fn hash<H: Hasher>(&self, state: &mut H) {
        let val = ruby::fun_call(self.0, "hash", &[]);
        val.to_raw().hash(state);
    }

Rustから呼び出すと毎回テーブルを引く必要があるが、Rubyから呼び出せばいい感じにキャッシュされる。
つまりRubyを経由して呼び出せばよい。というわけで

pub static mut M_HASH: Value = NIL;
// ...
    let sym_hash = module_eval(klass, "def self.value_hash(val); val.hash; end");
    M_HASH = obj_method_by_symbol(klass, sym_hash);

eval を作ってRubyの関数を定義する。

    fn hash<H: Hasher>(&self, state: &mut H) {
        let method = unsafe { crate::hashmap::M_HASH };
        let val = ruby::method_call(method, &[self.0]);
        val.to_raw().hash(state);
    }

hash関数では、インスタンスメソッドを呼ぶ代わりにこのキャッシュされた Method 呼び出しを行う。

extern "C" fn mark(ptr: *const ffi::c_void) {
   gc_mark(unsafe { M_HASH });

この Method のインスタンスがGCで回収されないように注意(これが原因で時々SEGVが出てデバッグが辛かった)

广告
将在 10 秒后关闭
bannerAds