Commit ca46576
committed
Add new
There's a well-known pattern of defining methods conditionally, especially in gems:
```ruby
def foo
if RUBY_VERSION < '4.0.'
...
else
...
end
end
```
If `foo` is a hotspot, its performance can be improved (see benchmarks below)
by moving the conditional logic outside the method body definition:
```ruby
if RUBY_VERSION < '4.0.'
def foo
...
end
else
def foo
...
end
end
```
I'd like to propose a new `Performance/ConditionalDefinition` cop
that detects conditional method definitions, as described above.
We can start by matching constant expressions such as
`{RUBY_VERSION,RUBY_RELEASE_DATE,...} <op> ...`, and generalize the detection logic later.
```ruby
require 'benchmark/ips'
class ConditionInBody
def foo
if RUBY_VERSION >= '3.2'
1
else
2
end
end
end
class ConditionalDefinition
if RUBY_VERSION >= '3.2'
def foo
1
end
else
def foo
2
end
end
end
obj1 = ConditionInBody.new
obj2 = ConditionalDefinition.new
Benchmark.ips do |x|
x.report('condition in body') do
obj1.foo
end
x.report('conditional method definition') do
obj2.foo
end
x.compare!
end
```
```bash
$ ruby bm.rb
ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +PRISM [x86_64-linux]
Warming up --------------------------------------
condition in body 1.170M i/100ms
conditional method definition
2.228M i/100ms
Calculating -------------------------------------
condition in body 11.683M (± 2.6%) i/s (85.60 ns/i) - 58.485M in 5.009850s
conditional method definition
22.148M (± 1.0%) i/s (45.15 ns/i) - 111.418M in 5.031059s
Comparison:
conditional method definition: 22148111.6 i/s
condition in body: 11682500.0 i/s - 1.90x slower
```
I'm not an expert in YJIT (or any other MRI JIT compiler), but AFAIK
even when YJIT determines that `RUBY_VERSION <op> ...` is constant, it still needs to check
a guard clause for every method call. So there's still a performance improvement:
```bash
$ ruby --yjit bm.rb
ruby 3.4.3 (2025-04-14 revision d0b7e5b6a0) +YJIT +PRISM [x86_64-linux]
Warming up --------------------------------------
condition in body 1.853M i/100ms
conditional method definition
3.356M i/100ms
Calculating -------------------------------------
condition in body 22.752M (± 0.4%) i/s (43.95 ns/i) - 114.907M in 5.050444s
conditional method definition
51.223M (± 0.4%) i/s (19.52 ns/i) - 258.439M in 5.045467s
Comparison:
conditional method definition: 51223091.5 i/s
condition in body: 22752233.2 i/s - 2.25x slower
```
**NOTE**: This is a WIP, and the cop is not yet ready -- at this point
I just want to get feedback on the idea so I can continue working on it.Performance/ConditionalDefinition cop1 parent d080ec3 commit ca46576
File tree
4 files changed
+129
-0
lines changed- config
- lib/rubocop/cop
- performance
- spec/rubocop/cop/performance
4 files changed
+129
-0
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
92 | 99 | | |
93 | 100 | | |
94 | 101 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
Lines changed: 53 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
0 commit comments