Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mandatory only builtin function #5112

Merged
merged 5 commits into from
Nov 15, 2021
Merged

Conversation

ko1
Copy link
Contributor

@ko1 ko1 commented Nov 12, 2021

Primitive.mandatory_only? for fast path

Compare with the C methods, A built-in methods written in Ruby is
slower if only mandatory parameters are given because it needs to
check the argumens and fill default values for optional and keyword
parameters (C methods can check the number of parameters with argc,
so there are no overhead). Passing mandatory arguments are common
(optional arguments are exceptional, in many cases) so it is important
to provide the fast path for such common cases.

Primitive.mandatory_only? is a special builtin function used with
if expression like that:

  def self.at(time, subsec = false, unit = :microsecond, in: nil)
    if Primitive.mandatory_only?
      Primitive.time_s_at1(time)
    else
      Primitive.time_s_at(time, subsec, unit, Primitive.arg!(:in))
    end
  end

and it makes two ISeq,

  def self.at(time, subsec = false, unit = :microsecond, in: nil)
    Primitive.time_s_at(time, subsec, unit, Primitive.arg!(:in))
  end

  def self.at(time)
    Primitive.time_s_at1(time)
  end

and (2) is pointed by (1). Note that Primitive.mandatory_only?
should be used only in a condition of an if statement and the
if statement should be equal to the methdo body (you can not
put any expression before and after the if statement).

A method entry with mandatory_only? (Time.at on the above case)
is marked as iseq_overload. When the method will be dispatch only
with mandatory arguments (Time.at(0) for example), make another
method entry with ISeq (2) as mandatory only method entry and it
will be cached in an inline method cache.

The idea is similar discussed in https://bugs.ruby-lang.org/issues/16254
but it only checks mandatory parameters or more, because many cases
only mandatory parameters are given. If we find other cases (optional
or keyword parameters are used frequently and it hurts performance),
we can extend the feature.


add benchmark/array_sample.yml

                      ruby_2_6    ruby_2_7    ruby_3_0      master    modified
         ary.sample    32.113M     30.146M     11.162M     10.539M     26.620M i/s -     64.882M times in 2.020428s 2.152296s 5.812981s 6.156398s 2.437325s
      ary.sample(2)     9.420M      8.987M      7.500M      6.973M      7.191M i/s -     25.170M times in 2.672085s 2.800616s 3.355896s 3.609534s 3.500108s

add benchmark/time_at.yml

                                ruby_2_6    ruby_2_7    ruby_3_0      master    modified
                   Time.at(0)    12.362M     11.015M      9.499M      6.615M      9.000M i/s -     32.115M times in 2.597946s 2.915517s 3.380725s 4.854651s 3.568234s
              Time.at(0, 500)     7.542M      7.136M      8.252M      5.707M      5.646M i/s -     20.713M times in 2.746279s 2.902556s 2.510166s 3.629644s 3.668854s
     Time.at(0, in: "+09:00")     1.426M      1.346M      1.565M      1.674M      1.667M i/s -      4.240M times in 2.974049s 3.149753s 2.709416s 2.533043s 2.542853s
ruby_2_6: ruby 2.6.7p150 (2020-12-09 revision 67888) [x86_64-linux]
ruby_2_7: ruby 2.7.3p140 (2020-12-09 revision 9b884df6dd) [x86_64-linux]
ruby_3_0: ruby 3.0.3p150 (2021-11-06 revision 6d540c1b98) [x86_64-linux]
master: ruby 3.1.0dev (2021-11-13T20:48:57Z master fc456adc6a) [x86_64-linux]
modified: ruby 3.1.0dev (2021-11-15T01:12:51Z mandatory_only_bui.. b0228446db) [x86_64-linux]


Compare with the C methods, A built-in methods written in Ruby is
slower if only mandatory parameters are given because it needs to
check the argumens and fill default values for optional and keyword
parameters (C methods can check the number of parameters with `argc`,
so there are no overhead). Passing mandatory arguments are common
(optional arguments are exceptional, in many cases) so it is important
to provide the fast path for such common cases.

`Primitive.mandatory_only?` is a special builtin function used with
`if` expression like that:

```ruby
  def self.at(time, subsec = false, unit = :microsecond, in: nil)
    if Primitive.mandatory_only?
      Primitive.time_s_at1(time)
    else
      Primitive.time_s_at(time, subsec, unit, Primitive.arg!(:in))
    end
  end
```

and it makes two ISeq,

```
  def self.at(time, subsec = false, unit = :microsecond, in: nil)
    Primitive.time_s_at(time, subsec, unit, Primitive.arg!(:in))
  end

  def self.at(time)
    Primitive.time_s_at1(time)
  end
```

and (2) is pointed by (1). Note that `Primitive.mandatory_only?`
should be used only in a condition of an `if` statement and the
`if` statement should be equal to the methdo body (you can not
put any expression before and after the `if` statement).

A method entry with `mandatory_only?` (`Time.at` on the above case)
is marked as `iseq_overload`. When the method will be dispatch only
with mandatory arguments (`Time.at(0)` for example), make another
method entry with ISeq (2) as mandatory only method entry and it
will be cached in an inline method cache.

The idea is similar discussed in https://bugs.ruby-lang.org/issues/16254
but it only checks mandatory parameters or more, because many cases
only mandatory parameters are given. If we find other cases (optional
or keyword parameters are used frequently and it hurts performance),
we can extend the feature.
```
                      ruby_2_6    ruby_2_7    ruby_3_0      master    modified
         ary.sample    32.113M     30.146M     11.162M     10.539M     26.620M i/s -     64.882M times in 2.020428s 2.152296s 5.812981s 6.156398s 2.437325s
      ary.sample(2)     9.420M      8.987M      7.500M      6.973M      7.191M i/s -     25.170M times in 2.672085s 2.800616s 3.355896s 3.609534s 3.500108s
```

```
ruby_2_6: ruby 2.6.7p150 (2020-12-09 revision 67888) [x86_64-linux]
ruby_2_7: ruby 2.7.3p140 (2020-12-09 revision 9b884df) [x86_64-linux]
ruby_3_0: ruby 3.0.3p150 (2021-11-06 revision 6d540c1) [x86_64-linux]
master: ruby 3.1.0dev (2021-11-13T20:48:57Z master fc456ad) [x86_64-linux]
modified: ruby 3.1.0dev (2021-11-15T01:12:51Z mandatory_only_bui.. b0228446db) [x86_64-linux]
```
```
                                ruby_2_6    ruby_2_7    ruby_3_0      master    modified
                   Time.at(0)    12.362M     11.015M      9.499M      6.615M      9.000M i/s -     32.115M times in 2.597946s 2.915517s 3.380725s 4.854651s 3.568234s
              Time.at(0, 500)     7.542M      7.136M      8.252M      5.707M      5.646M i/s -     20.713M times in 2.746279s 2.902556s 2.510166s 3.629644s 3.668854s
     Time.at(0, in: "+09:00")     1.426M      1.346M      1.565M      1.674M      1.667M i/s -      4.240M times in 2.974049s 3.149753s 2.709416s 2.533043s 2.542853s
```

```
ruby_2_6: ruby 2.6.7p150 (2020-12-09 revision 67888) [x86_64-linux]
ruby_2_7: ruby 2.7.3p140 (2020-12-09 revision 9b884df) [x86_64-linux]
ruby_3_0: ruby 3.0.3p150 (2021-11-06 revision 6d540c1) [x86_64-linux]
master: ruby 3.1.0dev (2021-11-13T20:48:57Z master fc456ad) [x86_64-linux]
modified: ruby 3.1.0dev (2021-11-15T01:12:51Z mandatory_only_bui.. b0228446db) [x86_64-linux]
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

1 participant