Project

General

Profile

Actions

Feature #22131

closed

Avoid array allocation in `Array#include?` on literal arrays

Feature #22131: Avoid array allocation in `Array#include?` on literal arrays

Added by Strech (Sergey Fedorov) about 17 hours ago. Updated about 9 hours ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:125841]

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

Also available in: PDF Atom