Feature #12484

Updated by nobu (Nobuyoshi Nakada) over 2 years ago

## Abstract

I optimized built-in library Rational. It is more than 3.7 times faster now in some cases.

## Background

Rational is increasing its importance year by year. Ruby 1.9.2 uses Rational as internal representation of Time. Ruby 2.1 introduced Rational literal ("r" suffix).

But it's performance is not good enough because its implementation uses Ruby-level method calling with `rb_funcall()`, in spite of it's implemented in C since Ruby 1.9.1.

## Proposal

I tried to improve its performance with decreasing `rb_funcall()`. rb_funcall(). They have been replaced with more direct representation as C.

## Implementation

2 patches attached. 0001 is exporting some numeric.c functions (Is this OK?) that needed to 0002. 0002 is the main.

This is an example of typical modification:

~~~diff ~~~
- return f_mul(f_to_f(self), other);
+ return DBL2NUM(RFLOAT_VALUE(nurat_to_f(self)) * RFLOAT_VALUE(other));

In this code, `f_mul()` and `f_to_f()` calls `rb_funcall()`. I replaced those with `*` (native multiply) and `nurat_to_f()` (the body of Rational#to_f).

## Evaluation

Performance and testing evaluation is done with r55389 of trunk.

Performance is improved in most of methods. Attached ratios.png shows times of improvement (bigger is better).
The benchmark is done with attached benchmark.rb script. result-trunk.tsv and result-optimized.tsv are raw scores.

Notably, `Rational + Bignum` became 3.7 times or more faster.

These patches also pass `make test`, `make test-all` and `make test-rubyspec`.

## Discussion

I have some concerns about compatibilities but I believe it's not a real problem.

Current implementation uses Ruby-level method calling as written above. If some user redefined Integer method in Ruby level, it effects to Rational calculation.

~~~ruby ~~~
class Integer
alias mul *
def *(o)
p "I'm *"

r = Rational(1<<64)
r * r

Current implementation prints "I'm *" but nothing printed with my patch. This is compatibility breakage in a strict sense, but I don't think it is used as valuable behavior from library users.

Other concern is my prerequisites. I assumed that Rational have a pair of `::Integer` (internally bignum or fixnum) only because I couldn't define subclass of Integer to work with Rational.
If user can pass a subclass of Integer to the constructor of Rational, it may cause some weird behavior.

If I should care about above or other things, please let me know.

## Summary

Most of Rational methods are optimized and those speed are up with more direct C representation.
I believe there is no real compatibility problem with my implementation.

Note that my codes are supported by a grant of Ruby Association. I thank to Ruby Association and grant committee members.