Project

General

Profile

Actions

Feature #12484

closed

Optimizing Rational

Added by tad (Tadashi Saito) almost 8 years ago. Updated over 7 years ago.

Status:
Closed
Target version:
-
[ruby-core:75960]

Description

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(). 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:

- 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.
https://drone.io/github.com/tadd/ruby/70

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.

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

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.
http://ruby.or.jp/en/news/20160406.html


Files

0001-export-functions.patch (5.27 KB) 0001-export-functions.patch tad (Tadashi Saito), 06/12/2016 12:55 PM
0002-optimize-Rational-methods.patch (25.4 KB) 0002-optimize-Rational-methods.patch tad (Tadashi Saito), 06/12/2016 12:55 PM
ratios.png (80 KB) ratios.png tad (Tadashi Saito), 06/12/2016 12:56 PM
benchmark.rb (2.52 KB) benchmark.rb tad (Tadashi Saito), 06/12/2016 01:04 PM
result-optimized.tsv (1.19 KB) result-optimized.tsv tad (Tadashi Saito), 06/12/2016 01:05 PM
result-trunk.tsv (1.18 KB) result-trunk.tsv tad (Tadashi Saito), 06/12/2016 01:05 PM
test_rational.rb.patch (1.07 KB) test_rational.rb.patch mrkn (Kenta Murata), 06/13/2016 03:36 PM
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0