Skip to content

case/when optimizations#7522

Merged
headius merged 2 commits intojruby:masterfrom
headius:case_when_opt
Mar 15, 2023
Merged

case/when optimizations#7522
headius merged 2 commits intojruby:masterfrom
headius:case_when_opt

Conversation

@headius
Copy link
Member

@headius headius commented Dec 13, 2022

Just a gaggle of small optimizations to case/when.

@headius headius added this to the JRuby 9.4.1.0 milestone Dec 13, 2022
@headius headius modified the milestones: JRuby 9.4.1.0, JRuby 9.4.2.0 Feb 6, 2023
@headius headius modified the milestones: JRuby 9.4.2.0, JRuby 9.4.3.0 Feb 28, 2023
This avoids allocating a new String each time and may assist the
JIT or CPU branch optimization. Results on 17 show a reasonable
improvement:

Script
```
10.times do
  t = Time.now
  i = 0; while i < 100_000_000
    case RUBY_ENGINE
    when "foo"
    when "bar"
    when "baz"
    when "quux"
    when "jruby"
    end
    i += 1
  end
  puts Time.now - t
end
```

Before:
```
$ jruby -Xcompile.invokedynamic blah.rb
5.6268959999999995
5.126104
5.089117000000001
5.156553
5.1014420000000005
5.1802779999999995
5.112481
5.135682
5.108746
5.164584
```

After:
```
$ jruby -Xcompile.invokedynamic blah.rb
3.3244930000000004
3.0746480000000003
2.930905
3.020159
2.925828
2.925954
2.926557
2.9447579999999998
2.926937
2.925714
```

This difference is reduced but not eliminated on a JIT with better
escape analysis (the overall reduction is due to the removal of the
loop fixnums):

Before:
```
$ JAVA_HOME=~/graalvm-ce-java17-22.3.0/Contents/Home jruby -Xcompile.invokedynamic -Xfixnum.cache=false blah.rb
4.947942
4.683816
3.772222
3.8641159999999997
3.782633
3.7981920000000002
3.816897
3.786005
3.784264
3.784209
```

After:
```
$ JAVA_HOME=~/graalvm-ce-java17-22.3.0/Contents/Home jruby -Xcompile.invokedynamic -Xfixnum.cache=false blah.rb
3.1830589999999996
2.999025
2.135789
2.051477
1.9829130000000001
2.211411
2.1756149999999996
2.177314
2.177531
2.180635
```

This breaks compatibility with CRuby but in a way that should not
typically be visible to any user. Both `case "foo"` and
`when "foo"` will now use a cached frozen string. The `case` form
is unusual but has a more visible behavior change due to the
`when` elements receiving a now-frozen string via `===`i, but
expecting the `case` value to be mutable here seems unreasonable.
The `when` form will now call `===` against frozen strings, only
visible if `String#===` has been patched in some way.
Like the fixnum optimization, this directly embeds the ID values
of literal symbol whens and emits a fast JVM switch (usually will
be a lookupswitch since symbol IDs are quite variable). The
incoming value is typechecked against RubySymbol and if it
matches, getID is called and used as the input for a switch.

This improves performance of a matching case/when by roughly 5x
on a VM that eliminates the benchmark loop:
Script:
```ruby
100.times do
  t = Time.now
  i = 0; while i < 100_000_000
    case RUBY_ENGINE
    when :"foo"
    when :"bar"
    when :"baz"
    when :"quux"
    when :"jruby"
    end
    i += 1
  end
  puts Time.now - t
end
```

Before:
```
$ JAVA_HOME=~/graalvm-ce-java17-22.3.0/Contents/Home jruby -Xcompile.invokedynamic -Xfixnum.cache=false blah.rb
0.840298
0.458168
0.41852999999999996
0.40532500000000005
0.402366
0.40269200000000005
0.40293
0.406479
0.40441400000000005
0.40388100000000005
0.403687
0.402861
0.439501
...
```

After:
```
$ JAVA_HOME=~/graalvm-ce-java17-22.3.0/Contents/Home jruby -Xcompile.invokedynamic -Xfixnum.cache=false blah.rb
0.125933
0.081487
0.08532999999999999
0.111028
0.085825
0.08541699999999999
0.08525300000000001
0.08525300000000001
0.08675699999999999
0.08553300000000001
0.08535699999999999
0.08566299999999999
0.085715
...
```

This may not match semantics of CRuby if `Symbol#===` is patched
but we can add detection for that case if necessary.
@headius headius marked this pull request as ready for review March 14, 2023 21:35
@headius headius merged commit 7ebb03b into jruby:master Mar 15, 2023
@headius headius deleted the case_when_opt branch March 15, 2023 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant