An easy way to cache method calls with Ruby's native extensions

Overview

I tried to use Rust's HashMap from Ruby ... is a continuation.

Last time, I wrote as follows.

To get the hash value, a Ruby method is dynamically called and Rust's hash function is also executed. Furthermore, Ruby methods are dynamically called during Eq. When calling a method from Ruby, the method is usually cached so that the next call will be faster, but when it is called from C or Rust, it will have to be searched from the method table every time, which is very slow.

This time, I was able to confirm that find, insert, and delete are faster by caching the method.

result

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 is 37% faster than the standard Hash class. I did it

What i did

So far, the hash function has been implemented as follows.

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

Every time I call it from Rust, I have to pull the table, but when I call it from Ruby, it is cached just like that. In other words, you can call it via Ruby. so

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);

Create eval to define a Ruby function.

    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);
    }

The hash function makes this cached Method call instead of calling the instance method.

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

Be careful not to retrieve this Method instance by GC (this sometimes caused SEGV and was hard to debug)

Recommended Posts

An easy way to cache method calls with Ruby's native extensions
Easy BDD with (Java) Spectrum?
Easy microservices with Spark Framework!
CSV output with Apache Commons CSV
Use Spring JDBC with Spring Boot
Easy web scraping with Jsoup
Easy library introduction with Maven!
Use SpatiaLite with Java / JDBC
An easy way to cache method calls with Ruby's native extensions
Easy way to create an original logo for your application (easy with your smartphone)
Easy way to create an implementation of java.util.stream.Stream
Super easy way to use enum with JSP
Easy way to check method / field list in Java REPL
Easy to use array.map (&: method)
Introduction to algorithms with java-Shakutori method