Actions
Feature #22131
closedAvoid array allocation in `Array#include?` on literal arrays
Feature #22131:
Avoid array allocation in `Array#include?` on literal arrays
Status:
Closed
Assignee:
-
Target version:
-
Description
I've noticed that Array#include? on a literal array of frozen-shareable elements (Integers, Symbols, true/false/nil, frozen String literals) compiles the receiver to duparray, which allocates a fresh Array copy on every call — even though #include? only reads it.
Before
# frozen_string_literal: true
[1, 2].include?(3)
== disasm: #<ISeq:<main>@-e:2 (2,0)-(2,18)>
0000 duparray [1, 2] ( 2)[Li]
0002 putobject 3
0004 opt_send_without_block <calldata!mid:include?, argc:1, ARGS_SIMPLE>
0006 leave
After
# frozen_string_literal: true
[1, 2].include?(3)
== disasm: #<ISeq:<main>@-e:2 (2,0)-(2,18)>
0000 putobject 3 ( 2)[Li]
0002 opt_duparray_send [1, 2], :include?, 1
0006 leave
Benchmarks
require 'benchmark/ips'
GC.disable
ARR = [1, 2, 3]
Benchmark.ips do |x|
x.report "literal [1,2,3].include?(2)" do |loop|
loop.times { [1, 2, 3].include?(2) }
end
x.report "constant ARR.include?(2)" do |loop|
loop.times { ARR.include?(2) }
end
x.compare!
end
| case | master | candidate | speedup |
|---|---|---|---|
[1,2,3].include?(2) |
17.83M i/s (56.09 ns) | 30.00M i/s (33.33 ns) | 1.68x |
ARR.include?(2) (const) |
23.92M i/s (41.80 ns) | 23.92M i/s (41.81 ns) | ~1.00x |
Github PR: https://github.com/ruby/ruby/pull/17496
Actions