From: eregontp@... Date: 2019-10-07T08:59:00+00:00 Subject: [ruby-core:95258] [Ruby master Bug#16243] case/when is slower than if on MRI Issue #16243 has been updated by Eregon (Benoit Daloze). File case_vs_if_sum.rb added The original benchmark allocates quite a bit, if we change it to just sum elements, the difference is around 20%: ``` Warming up -------------------------------------- deep_dup_case 44.804k i/100ms deep_dup_if 53.648k i/100ms Calculating ------------------------------------- deep_dup_case 487.684k (� 0.6%) i/s - 2.464M in 5.053100s deep_dup_if 584.710k (� 0.6%) i/s - 2.951M in 5.046530s Comparison: deep_dup_if: 584709.9 i/s deep_dup_case: 487684.5 i/s - 1.20x slower ``` @nobu thanks for the experiments, do you have an idea why `case` is slower than `if`? ---------------------------------------- Bug #16243: case/when is slower than if on MRI https://bugs.ruby-lang.org/issues/16243#change-81936 * Author: Eregon (Benoit Daloze) * Status: Open * Priority: Normal * Assignee: * Target version: * ruby -v: ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux] * Backport: 2.5: UNKNOWN, 2.6: UNKNOWN ---------------------------------------- This comes from a discussion on a PR on Sidekiq: https://github.com/mperham/sidekiq/pull/4303 This benchmark: ```ruby # frozen_string_literal: true require "benchmark/ips" def deep_dup_case(obj) case obj when Integer, Float, TrueClass, FalseClass, NilClass obj when String obj.dup when Array obj.map { |e| deep_dup_case(e) } when Hash duped = obj.dup duped.each_pair do |key, value| duped[key] = deep_dup_case(value) end else obj.dup end end def deep_dup_if(obj) if Integer === obj || Float === obj || TrueClass === obj || FalseClass === obj || NilClass === obj obj elsif String === obj obj.dup elsif Array === obj obj.map { |e| deep_dup_if(e) } elsif Hash === obj duped = obj.dup duped.each_pair do |key, value| duped[key] = deep_dup_if(value) end duped else obj.dup end end obj = { "class" => "FooWorker", "args" => [1, 2, 3, "foobar"], "jid" => "123987123" } Benchmark.ips do |x| x.report("deep_dup_case") do deep_dup_case(obj) end x.report("deep_dup_if") do deep_dup_if(obj) end x.compare! end ``` gives in MRI 2.6.5: ``` Warming up -------------------------------------- deep_dup_case 37.767k i/100ms deep_dup_if 41.802k i/100ms Calculating ------------------------------------- deep_dup_case 408.046k (� 0.9%) i/s - 2.077M in 5.090997s deep_dup_if 456.657k (� 0.9%) i/s - 2.299M in 5.035040s Comparison: deep_dup_if: 456657.4 i/s deep_dup_case: 408046.1 i/s - 1.12x slower ``` It seems @tenderlovemaking already noticed this a while ago due to missing inline caches for `case`, but the performance bug seems to still remain: https://youtu.be/b77V0rkr5rk?t=1442 Do you think this could be fixed in MRI? AFAIK both JRuby and TruffleRuby support inline cached `===` calls in `case/when`. I think the code with `case` is more idiomatic and readable and should be preferred to `if`, and this performance issue makes that less clear. ---Files-------------------------------- case_vs_if.rb (1003 Bytes) case_vs_if_sum.rb (1.02 KB) -- https://bugs.ruby-lang.org/ Unsubscribe: