diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
new file mode 100644
index 00000000..29e4270b
--- /dev/null
+++ b/.github/workflows/benchmarks.yml
@@ -0,0 +1,81 @@
+name: Benchmarks
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ rake:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ ruby-version: [
+ 'ruby-head', '3.2', '3.1', '3.0', '2.7', '2.6', '2.5', '2.4',
+ 'jruby-head',
+ 'truffleruby-head'
+ ]
+
+ steps:
+ - name: Set Share Env
+ if: github.ref_name == 'main'
+ run: |
+ echo "SHARE=1" >> $GITHUB_ENV
+ - uses: actions/checkout@v3
+ - name: Set up Ruby ${{ matrix.ruby-version }}
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby-version }}
+ - name: Install dependencies
+ run: bundle install
+ - name: Run benchmarks
+ run: bundle exec rake
+
+ rake_old:
+ runs-on: ubuntu-22.04
+
+ strategy:
+ matrix:
+ ruby-version: [
+ 'jruby-9.1',
+ 'truffleruby-22'
+ ]
+
+ steps:
+ - name: Set Share Env
+ if: github.ref_name == 'main'
+ run: |
+ echo "SHARE=1" >> $GITHUB_ENV
+ - uses: actions/checkout@v3
+ - name: Set up Ruby ${{ matrix.ruby-version }}
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby-version }}
+ - name: Install dependencies
+ run: bundle install
+ - name: Run benchmarks
+ run: bundle exec rake
+
+ rake_older:
+ runs-on: ubuntu-20.04
+
+ strategy:
+ matrix:
+ ruby-version: ['2.3', '2.2', '2.1']
+
+ steps:
+ - name: Set Share Env
+ if: github.ref_name == 'main'
+ run: |
+ echo "SHARE=1" >> $GITHUB_ENV
+ - uses: actions/checkout@v3
+ - name: Set up Ruby ${{ matrix.ruby-version }}
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby-version }}
+ - name: Install dependencies
+ run: bundle install
+ - name: Run benchmarks
+ run: bundle exec rake
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..1cd9aadb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/.bundle/
+*.bundle
+/Gemfile.lock
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9761f7f0..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-sudo: false
-cache: bundler
-bundler_args: --retry=3 --jobs=3
-language: ruby
-rvm:
- - 2.0
- - 2.1
- - 2.2.2
- - 2.2.1
- - 2.2.0
- - ruby-head
- - jruby
- - jruby-head
- - rbx-2
-matrix:
- allow_failures:
- - rvm: 2.2.1
- - rvm: 2.2.0
- - rvm: ruby-head
- - rvm: jruby
- - rvm: juby-head
- - rvm: rbx-2
- fast_finish: true
-
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ba4a1f01..1d698903 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ If you find any typos, errors, or have an better example. Just raise a new issue
<3
-These idioms list here are trying to satisfiy following goals:
+These idioms list here are trying to satisfy following goals:
[](https://speakerdeck.com/sferik/writing-fast-ruby?slide=11)
diff --git a/Gemfile b/Gemfile
index 1a42963e..00d364d9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,4 +2,7 @@ source 'https://rubygems.org'
gem 'benchmark-ips', '>= 2.0'
+gem 'activesupport', '>= 2.2.1'
+gem 'e2mmap'
+
gem 'rake'
diff --git a/README.md b/README.md
index 6834d9b8..cecf3ce9 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,21 @@
-Fast Ruby [](https://travis-ci.org/JuanitoFatas/fast-ruby)
+Fast Ruby [](https://github.com/fastruby/fast-ruby/actions/workflows/benchmarks.yml)
=======================================================================================================================================================================
In [Erik Michaels-Ober](https://github.com/sferik)'s great talk, 'Writing Fast Ruby': [Video @ Baruco 2014](https://www.youtube.com/watch?v=fGFM_UrSp70), [Slide](https://speakerdeck.com/sferik/writing-fast-ruby), he presented us with many idioms that lead to faster running Ruby code. He inspired me to document these to let more people know. I try to link to real commits so people can see that this can really have benefits in the real world. **This does not mean you can always blindly replace one with another. It depends on the context (e.g. `gsub` versus `tr`). Friendly reminder: Use with caution!**
Each idiom has a corresponding code example that resides in [code](code).
-All results listed in README.md are running with Ruby 2.2.0p0 on OS X 10.10.1. Machine information: MacBook Pro (Retina, 15-inch, Mid 2014), 2.5 GHz Intel Core i7, 16 GB 1600 MHz DDR3. Your results may vary, but you get the idea. : )
+All results listed in README.md are running with Ruby 4.0.0 on macOS 15.6.1. Machine information: MacBook Pro (14-inch, Nov 2024), Apple M4 Pro, 48 GB RAM. Your results may vary, but you get the idea. : )
-You can checkout [the travis build](https://travis-ci.org/JuanitoFatas/fast-ruby) for these benchmark results ran against different Ruby implementations.
+You can checkout [the GitHub Actions build](https://github.com/fastruby/fast-ruby/actions) for these benchmark results ran against different Ruby implementations.
**Let's write faster code, together! <3**
+Analyze your code
+-----------------
+
+Checkout the [fasterer](https://github.com/DamirSvrtan/fasterer) project - it's a static analysis that checks speed idioms written in this repo.
+
Measurement Tool
-----------------
@@ -37,162 +42,325 @@ end
Idioms
------
+### Index
+
+- [General](#general)
+- [Array](#array)
+- [Date](#date)
+- [Enumerable](#enumerable)
+- [Hash](#hash)
+- [Proc & Block](#proc--block)
+- [String](#string)
+- [Time](#time)
+- [Range](#range)
+
### General
-##### Parallel Assignment vs Sequential Assignment [code](code/general/assignment.rb)
+##### `attr_accessor` vs `getter and setter` [code](code/general/attr-accessor-vs-getter-and-setter.rb)
-> Parallel Assignment allocates an extra array.
+> https://www.omniref.com/ruby/2.2.0/files/method.h?#annotation=4081781&line=47
```
-$ ruby -v code/general/assignment.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+$ ruby -v code/general/attr-accessor-vs-getter-and-setter.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ getter_and_setter 1.122M i/100ms
+ attr_accessor 1.303M i/100ms
Calculating -------------------------------------
- Parallel Assignment 99.146k i/100ms
-Sequential Assignment 127.143k i/100ms
--------------------------------------------------
- Parallel Assignment 2.522M (± 7.5%) i/s - 12.592M
-Sequential Assignment 5.686M (± 8.6%) i/s - 28.226M
+ getter_and_setter 10.999M (± 2.1%) i/s (90.92 ns/i) - 56.083M in 5.101077s
+ attr_accessor 12.517M (± 1.5%) i/s (79.89 ns/i) - 63.841M in 5.101477s
Comparison:
-Sequential Assignment: 5685750.0 i/s
- Parallel Assignment: 2521708.9 i/s - 2.25x slower
+ attr_accessor: 12517008.0 i/s
+ getter_and_setter: 10999275.5 i/s - 1.14x slower
```
##### `begin...rescue` vs `respond_to?` for Control Flow [code](code/general/begin-rescue-vs-respond-to.rb)
```
$ ruby -v code/general/begin-rescue-vs-respond-to.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ begin...rescue 230.601k i/100ms
+ respond_to? 1.915M i/100ms
Calculating -------------------------------------
- begin...rescue 29.452k i/100ms
- respond_to? 106.528k i/100ms
--------------------------------------------------
- begin...rescue 371.591k (± 5.4%) i/s - 1.855M
- respond_to? 3.277M (± 7.5%) i/s - 16.299M
+ begin...rescue 2.390M (± 0.9%) i/s (418.40 ns/i) - 11.991M in 5.017466s
+ respond_to? 18.745M (± 1.8%) i/s (53.35 ns/i) - 93.843M in 5.007990s
Comparison:
- respond_to?: 3276972.3 i/s
- begin...rescue: 371591.0 i/s - 8.82x slower
+ respond_to?: 18744998.8 i/s
+ begin...rescue: 2390076.1 i/s - 7.84x slower
```
##### `define_method` vs `module_eval` for Defining Methods [code](code/general/define_method-vs-module-eval.rb)
```
$ ruby -v code/general/define_method-vs-module-eval.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+module_eval with string
+ 630.000 i/100ms
+ define_method 685.000 i/100ms
+Calculating -------------------------------------
+module_eval with string
+ 5.633k (±18.1%) i/s (177.53 μs/i) - 27.090k in 5.009884s
+ define_method 7.239k (±17.6%) i/s (138.14 μs/i) - 34.250k in 5.040522s
+
+Comparison:
+ define_method: 7238.9 i/s
+module_eval with string: 5632.8 i/s - same-ish: difference falls within error
+```
+
+##### `String#constantize` vs a comparison for inflection [code](code/general/constantize-vs-comparison.rb)
+
+ActiveSupport's [String#constantize](https://guides.rubyonrails.org/active_support_core_extensions.html#constantize) "resolves the constant reference expression in its receiver".
+
+[Read the rationale here](https://github.com/fastruby/fast-ruby/pull/200)
+
+```
+$ ruby -v code/general/constantize-vs-comparison.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+using an if statement
+ 1.507M i/100ms
+ String#constantize 1.048M i/100ms
+Calculating -------------------------------------
+using an if statement
+ 15.518M (± 1.2%) i/s (64.44 ns/i) - 78.355M in 5.050041s
+ String#constantize 10.556M (± 1.0%) i/s (94.73 ns/i) - 53.448M in 5.063570s
+
+Comparison:
+using an if statement: 15517955.2 i/s
+ String#constantize: 10556362.4 i/s - 1.47x slower
+```
+
+##### `raise` vs `E2MM#Raise` for raising (and defining) exceptions [code](code/general/raise-vs-e2mmap.rb)
+
+Ruby's [Exception2MessageMapper module](http://ruby-doc.org/stdlib-2.2.0/libdoc/e2mmap/rdoc/index.html) allows one to define and raise exceptions with predefined messages.
+
+```
+$ ruby -v code/general/raise-vs-e2mmap.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Ruby exception: E2MM#Raise
+ 8.751k i/100ms
+Ruby exception: Kernel#raise
+ 254.268k i/100ms
+Calculating -------------------------------------
+Ruby exception: E2MM#Raise
+ 88.269k (± 0.5%) i/s (11.33 μs/i) - 446.301k in 5.056287s
+Ruby exception: Kernel#raise
+ 2.571M (± 1.0%) i/s (389.01 ns/i) - 12.968M in 5.044976s
+
+Comparison:
+Ruby exception: Kernel#raise: 2570660.6 i/s
+Ruby exception: E2MM#Raise: 88268.9 i/s - 29.12x slower
+
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Custom exception: E2MM#Raise
+ 8.837k i/100ms
+Custom exception: Kernel#raise
+ 263.896k i/100ms
+Calculating -------------------------------------
+Custom exception: E2MM#Raise
+ 88.322k (± 0.6%) i/s (11.32 μs/i) - 441.850k in 5.002885s
+Custom exception: Kernel#raise
+ 2.599M (± 2.1%) i/s (384.78 ns/i) - 13.195M in 5.079300s
+
+Comparison:
+Custom exception: Kernel#raise: 2598894.6 i/s
+Custom exception: E2MM#Raise: 88322.1 i/s - 29.43x slower
+```
+
+##### `loop` vs `while true` [code](code/general/loop-vs-while-true.rb)
+
+```
+$ ruby -v code/general/loop-vs-while-true.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ While Loop 1.000 i/100ms
+ Kernel loop 1.000 i/100ms
+Calculating -------------------------------------
+ While Loop 1.331 (± 0.0%) i/s (751.41 ms/i) - 7.000 in 5.260296s
+ Kernel loop 0.528 (± 0.0%) i/s (1.89 s/i) - 3.000 in 5.709880s
+
+Comparison:
+ While Loop: 1.3 i/s
+ Kernel loop: 0.5 i/s - 2.52x slower
+```
+
+##### `ancestors.include?` vs `<=` [code](code/general/inheritance-check.rb)
+```
+$ ruby -v code/general/inheritance-check.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ less than or equal 1.150M i/100ms
+ ancestors.include? 179.611k i/100ms
Calculating -------------------------------------
-module_eval with string 125.000 i/100ms
- define_method 138.000 i/100ms
--------------------------------------------------
-module_eval with string 1.130k (±20.3%) i/s - 5.500k
- define_method 1.346k (±25.9%) i/s - 6.348k
+ less than or equal 11.426M (± 1.4%) i/s (87.52 ns/i) - 57.493M in 5.032894s
+ ancestors.include? 1.880M (± 1.0%) i/s (531.84 ns/i) - 9.519M in 5.063302s
Comparison:
- define_method: 1345.6 i/s
-module_eval with string: 1129.7 i/s - 1.19x slower
+ less than or equal: 11425608.6 i/s
+ ancestors.include?: 1880262.8 i/s - 6.08x slower
```
-#### Method Invocation
+### Method Invocation
##### `call` vs `send` vs `method_missing` [code](code/method/call-vs-send-vs-method_missing.rb)
```
$ ruby -v code/method/call-vs-send-vs-method_missing.rb
-ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ call 1.670M i/100ms
+ send 1.182M i/100ms
+ method_missing 837.965k i/100ms
Calculating -------------------------------------
- call 115.094k i/100ms
- send 105.258k i/100ms
- method_missing 100.762k i/100ms
--------------------------------------------------
- call 3.811M (± 5.9%) i/s - 18.991M
- send 3.244M (± 7.2%) i/s - 16.210M
- method_missing 2.729M (± 9.8%) i/s - 13.401M
+ call 16.799M (± 0.3%) i/s (59.53 ns/i) - 85.150M in 5.068737s
+ send 11.845M (± 0.3%) i/s (84.42 ns/i) - 60.281M in 5.089071s
+ method_missing 8.392M (± 0.3%) i/s (119.17 ns/i) - 42.736M in 5.092719s
Comparison:
- call: 3811183.4 i/s
- send: 3244239.1 i/s - 1.17x slower
- method_missing: 2728893.0 i/s - 1.40x slower
+ call: 16799285.6 i/s
+ send: 11845293.0 i/s - 1.42x slower
+ method_missing: 8391692.6 i/s - 2.00x slower
```
##### Normal way to apply method vs `&method(...)` [code](code/general/block-apply-method.rb)
```
$ ruby -v code/general/block-apply-method.rb
-ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ normal 698.536k i/100ms
+ &method 322.901k i/100ms
+Calculating -------------------------------------
+ normal 6.978M (± 0.9%) i/s (143.31 ns/i) - 34.927M in 5.005888s
+ &method 3.214M (± 0.5%) i/s (311.12 ns/i) - 16.145M in 5.023139s
+
+Comparison:
+ normal: 6977718.6 i/s
+ &method: 3214214.6 i/s - 2.17x slower
+```
+##### Function with single Array argument vs splat arguments [code](code/general/array-argument-vs-splat-arguments.rb)
+
+```
+$ ruby -v code/general/array-argument-vs-splat-arguments.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Function with single Array argument
+ 1.910M i/100ms
+Function with splat arguments
+ 18.841k i/100ms
Calculating -------------------------------------
- normal 85.749k i/100ms
- &method 35.529k i/100ms
--------------------------------------------------
- normal 1.867M (± 7.6%) i/s - 9.347M
- &method 467.095k (± 6.4%) i/s - 2.345M
+Function with single Array argument
+ 18.818M (± 1.2%) i/s (53.14 ns/i) - 95.479M in 5.074468s
+Function with splat arguments
+ 217.060k (±11.2%) i/s (4.61 μs/i) - 1.093M in 5.099583s
Comparison:
- normal: 1866669.5 i/s
- &method: 467095.4 i/s - 4.00x slower
+Function with single Array argument: 18818207.9 i/s
+Function with splat arguments: 217059.9 i/s - 86.70x slower
```
-##### `define_method` vs `module_eval` for Defining Methods [code](code/general/define_method-vs-module-eval.rb)
+##### Hash vs OpenStruct on access assuming you already have a Hash or an OpenStruct [code](code/general/hash-vs-openstruct-on-access.rb)
```
-$ ruby -v code/general/define_method-vs-module-eval.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+$ ruby -v code/general/hash-vs-openstruct-on-access.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash 2.028M i/100ms
+ OpenStruct 1.478M i/100ms
+Calculating -------------------------------------
+ Hash 20.290M (± 0.5%) i/s (49.29 ns/i) - 103.430M in 5.097815s
+ OpenStruct 14.807M (± 0.6%) i/s (67.54 ns/i) - 75.387M in 5.091599s
+
+Comparison:
+ Hash: 20289714.4 i/s
+ OpenStruct: 14806564.8 i/s - 1.37x slower
+```
+##### Hash vs OpenStruct (creation) [code](code/general/hash-vs-openstruct.rb)
+
+```
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash 2.652M i/100ms
+ OpenStruct 31.241k i/100ms
Calculating -------------------------------------
-module_eval with string 125.000 i/100ms
- define_method 138.000 i/100ms
--------------------------------------------------
-module_eval with string 1.130k (±20.3%) i/s - 5.500k
- define_method 1.346k (±25.9%) i/s - 6.348k
+ Hash 26.301M (± 1.0%) i/s (38.02 ns/i) - 132.614M in 5.042769s
+ OpenStruct 313.539k (± 1.8%) i/s (3.19 μs/i) - 1.593M in 5.083245s
Comparison:
- define_method: 1345.6 i/s
-module_eval with string: 1129.7 i/s - 1.19x slower
+ Hash: 26300561.3 i/s
+ OpenStruct: 313538.7 i/s - 83.88x slower
+```
+
+##### Kernel#format vs Float#round().to_s [code](code/general/format-vs-round-and-to-s.rb)
+
```
+$ ruby -v code/general/format-vs-round-and-to-s.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Float#round 465.786k i/100ms
+ Kernel#format 618.743k i/100ms
+ String#% 562.895k i/100ms
+Calculating -------------------------------------
+ Float#round 4.780M (± 0.6%) i/s (209.21 ns/i) - 24.221M in 5.067359s
+ Kernel#format 6.200M (± 1.4%) i/s (161.29 ns/i) - 31.556M in 5.090736s
+ String#% 5.642M (± 1.1%) i/s (177.25 ns/i) - 28.708M in 5.088913s
+Comparison:
+ Kernel#format: 6199975.0 i/s
+ String#%: 5641863.3 i/s - 1.10x slower
+ Float#round: 4779984.9 i/s - 1.30x slower
+```
### Array
##### `Array#bsearch` vs `Array#find` [code](code/array/bsearch-vs-find.rb)
-**WARNING:** `bsearch` ONLY works on *sorted array*. More details please see [#29](https://github.com/JuanitoFatas/fast-ruby/issues/29).
+**WARNING:** `bsearch` ONLY works on *sorted array*. More details please see [#29](https://github.com/fastruby/fast-ruby/issues/29).
```
$ ruby -v code/array/bsearch-vs-find.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ find 1.000 i/100ms
+ bsearch 189.387k i/100ms
Calculating -------------------------------------
- find 1.000 i/100ms
- bsearch 42.216k i/100ms
--------------------------------------------------
- find 0.184 (± 0.0%) i/s - 1.000 in 5.434758s
- bsearch 577.301k (± 6.6%) i/s - 2.913M
+ find 0.703 (± 0.0%) i/s (1.42 s/i) - 4.000 in 5.692110s
+ bsearch 1.893M (± 1.6%) i/s (528.21 ns/i) - 9.469M in 5.003152s
Comparison:
- bsearch: 577300.7 i/s
- find: 0.2 i/s - 3137489.63x slower
+ bsearch: 1893194.5 i/s
+ find: 0.7 i/s - 2691605.95x slower
```
-##### `Array#count` vs `Array#size` [code](code/array/count-vs-size.rb)
+##### `Array#length` vs `Array#size` vs `Array#count` [code](code/array/length-vs-size-vs-count.rb)
-```
-$ ruby -v code/array/count-vs-size.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+Use `#length` when you only want to know how many elements in the array, `#count` could also achieve this. However `#count` should be use for counting specific elements in array. [Note `#size` is an alias of `#length`](https://github.com/ruby/ruby/blob/f8fb526ad9e9f31453bffbc908b6a986736e21a7/array.c#L5817-L5818).
+```
+$ ruby -v code/array/length-vs-size-vs-count.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#length 5.024M i/100ms
+ Array#size 4.994M i/100ms
+ Array#count 3.781M i/100ms
Calculating -------------------------------------
- #count 130.991k i/100ms
- #size 135.312k i/100ms
--------------------------------------------------
- #count 6.697M (± 7.1%) i/s - 33.403M
- #size 7.562M (± 9.1%) i/s - 37.481M
+ Array#length 50.668M (± 0.8%) i/s (19.74 ns/i) - 256.237M in 5.057495s
+ Array#size 50.580M (± 0.7%) i/s (19.77 ns/i) - 254.704M in 5.035880s
+ Array#count 38.202M (± 0.8%) i/s (26.18 ns/i) - 192.843M in 5.048306s
Comparison:
- #size: 7562457.4 i/s
- #count: 6696763.0 i/s - 1.13x slower
+ Array#length: 50667869.8 i/s
+ Array#size: 50580088.8 i/s - same-ish: difference falls within error
+ Array#count: 38201768.5 i/s - 1.33x slower
```
##### `Array#shuffle.first` vs `Array#sample` [code](code/array/shuffle-first-vs-sample.rb)
@@ -204,56 +372,125 @@ Comparison:
```
$ ruby -v code/array/shuffle-first-vs-sample.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#shuffle.first 88.991k i/100ms
+ Array#sample 2.503M i/100ms
Calculating -------------------------------------
- Array#shuffle.first 25.406k i/100ms
- Array#sample 125.101k i/100ms
--------------------------------------------------
- Array#shuffle.first 304.341k (± 4.3%) i/s - 1.524M
- Array#sample 5.727M (± 8.6%) i/s - 28.523M
+ Array#shuffle.first 891.780k (± 0.5%) i/s (1.12 μs/i) - 4.539M in 5.089459s
+ Array#sample 25.133M (± 0.6%) i/s (39.79 ns/i) - 127.660M in 5.079516s
Comparison:
- Array#sample: 5727032.0 i/s
- Array#shuffle.first: 304341.1 i/s - 18.82x slower
+ Array#sample: 25133099.8 i/s
+ Array#shuffle.first: 891779.8 i/s - 28.18x slower
```
##### `Array#[](0)` vs `Array#first` [code](code/array/array-first-vs-index.rb)
```
$ ruby -v code/array/array-first-vs-index.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#[0] 3.922M i/100ms
+ Array#first 3.697M i/100ms
Calculating -------------------------------------
- Array#[0] 152.751k i/100ms
- Array#first 148.088k i/100ms
--------------------------------------------------
- Array#[0] 8.614M (± 7.0%) i/s - 42.923M
- Array#first 7.465M (±10.7%) i/s - 36.874M
+ Array#[0] 39.347M (± 0.7%) i/s (25.41 ns/i) - 200.006M in 5.083307s
+ Array#first 37.289M (± 1.1%) i/s (26.82 ns/i) - 188.546M in 5.056917s
Comparison:
- Array#[0]: 8613583.7 i/s
- Array#first: 7464526.6 i/s - 1.15x slower
+ Array#[0]: 39347487.6 i/s
+ Array#first: 37288935.3 i/s - 1.06x slower
```
##### `Array#[](-1)` vs `Array#last` [code](code/array/array-last-vs-index.rb)
```
$ ruby -v code/array/array-last-vs-index.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#[-1] 3.906M i/100ms
+ Array#last 3.727M i/100ms
+Calculating -------------------------------------
+ Array#[-1] 39.303M (± 0.7%) i/s (25.44 ns/i) - 199.228M in 5.069210s
+ Array#last 37.280M (± 1.1%) i/s (26.82 ns/i) - 190.075M in 5.099254s
+
+Comparison:
+ Array#[-1]: 39303493.5 i/s
+ Array#last: 37279563.5 i/s - 1.05x slower
+```
+
+##### `Array#insert` vs `Array#unshift` [code](code/array/insert-vs-unshift.rb)
+
+```
+$ ruby -v code/array/insert-vs-unshift.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#unshift 37.000 i/100ms
+ Array#insert 1.000 i/100ms
+Calculating -------------------------------------
+ Array#unshift 379.798 (± 1.1%) i/s (2.63 ms/i) - 1.924k in 5.066300s
+ Array#insert 1.717 (± 0.0%) i/s (582.49 ms/i) - 9.000 in 5.242429s
+
+Comparison:
+ Array#unshift: 379.8 i/s
+ Array#insert: 1.7 i/s - 221.23x slower
+```
+##### `Array#concat` vs `Array#+` [code](code/array/array-concat-vs-+.rb)
+`Array#+` returns a new array built by concatenating the two arrays together to
+produce a third array. `Array#concat` appends the elements of the other array to self.
+This means that the + operator will create a new array each time it is called
+(which is expensive), while concat only appends the new element.
+```
+$ ruby -v code/array/array-concat-vs-+.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#concat 126.000 i/100ms
+ Array#+ 1.000 i/100ms
+Calculating -------------------------------------
+ Array#concat 1.259k (± 1.1%) i/s (793.98 μs/i) - 6.300k in 5.002769s
+ Array#+ 5.170 (± 0.0%) i/s (193.42 ms/i) - 26.000 in 5.033770s
+Comparison:
+ Array#concat: 1259.5 i/s
+ Array#+: 5.2 i/s - 243.60x slower
+```
+
+##### `Array#new` vs `Fixnum#times + map` [code](code/array/array-new-vs-fixnum-times-map.rb)
+
+Typical slowdown is 40-60% depending on the size of the array. See the corresponding
+[pull request](https://github.com/fastruby/fast-ruby/pull/91/) for performance characteristics.
+
+```
+$ ruby -v code/array/array-new-vs-fixnum-times-map.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#new 424.914k i/100ms
+ Fixnum#times + map 187.997k i/100ms
Calculating -------------------------------------
- Array#[-1] 151.940k i/100ms
- Array#last 153.371k i/100ms
--------------------------------------------------
- Array#[-1] 8.582M (± 4.6%) i/s - 42.847M
- Array#last 7.639M (± 5.7%) i/s - 38.189M
+ Array#new 4.251M (± 0.4%) i/s (235.25 ns/i) - 21.671M in 5.098122s
+ Fixnum#times + map 1.886M (± 0.4%) i/s (530.31 ns/i) - 9.588M in 5.084632s
Comparison:
- Array#[-1]: 8582074.3 i/s
- Array#last: 7639254.5 i/s - 1.12x slower
+ Array#new: 4250785.8 i/s
+ Fixnum#times + map: 1885679.9 i/s - 2.25x slower
+```
+
+##### `Array#sort.reverse` vs `Array#sort_by` + block [code](code/array/sort-reverse-vs-sort_by-with-block.rb)
+
```
+$ ruby -v code/array/sort-reverse-vs-sort_by-with-block.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#sort.reverse 55.279k i/100ms
+ Array#sort_by &:-@ 22.773k i/100ms
+Calculating -------------------------------------
+ Array#sort.reverse 560.570k (± 0.9%) i/s (1.78 μs/i) - 2.819M in 5.029648s
+ Array#sort_by &:-@ 229.324k (± 0.6%) i/s (4.36 μs/i) - 1.161M in 5.064717s
+Comparison:
+ Array#sort.reverse: 560570.1 i/s
+ Array#sort_by &:-@: 229323.6 i/s - 2.44x slower
+```
### Enumerable
@@ -261,36 +498,34 @@ Comparison:
```
$ ruby -v code/enumerable/each-push-vs-map.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#each + push 31.310k i/100ms
+ Array#map 51.371k i/100ms
Calculating -------------------------------------
- Array#each + push 9.025k i/100ms
- Array#map 13.947k i/100ms
--------------------------------------------------
- Array#each + push 99.634k (± 3.2%) i/s - 505.400k
- Array#map 158.091k (± 4.2%) i/s - 794.979k
+ Array#each + push 320.334k (± 0.6%) i/s (3.12 μs/i) - 1.628M in 5.082743s
+ Array#map 511.854k (± 1.3%) i/s (1.95 μs/i) - 2.569M in 5.019061s
Comparison:
- Array#map: 158090.9 i/s
- Array#each + push: 99634.2 i/s - 1.59x slower
+ Array#map: 511854.5 i/s
+ Array#each + push: 320334.1 i/s - 1.60x slower
```
##### `Enumerable#each` vs `for` loop [code](code/enumerable/each-vs-for-loop.rb)
```
$ ruby -v code/enumerable/each-vs-for-loop.rb
-ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ For loop 54.498k i/100ms
+ #each 63.984k i/100ms
Calculating -------------------------------------
- For loop 17.111k i/100ms
- #each 18.464k i/100ms
--------------------------------------------------
- For loop 198.517k (± 5.3%) i/s - 992.438k
- #each 208.157k (± 5.0%) i/s - 1.052M
+ For loop 525.548k (± 8.8%) i/s (1.90 μs/i) - 2.670M in 5.128917s
+ #each 641.789k (± 0.3%) i/s (1.56 μs/i) - 3.263M in 5.084548s
Comparison:
- #each: 208157.4 i/s
- For loop: 198517.3 i/s - 1.05x slower
+ #each: 641788.6 i/s
+ For loop: 525547.8 i/s - 1.22x slower
```
##### `Enumerable#each_with_index` vs `while` loop [code](code/enumerable/each_with_index-vs-while-loop.rb)
@@ -298,19 +533,18 @@ Comparison:
> [rails/rails#12065](https://github.com/rails/rails/pull/12065)
```
-$ ruby -v code/array/each_with_index-vs-while-loop.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+$ ruby -v code/enumerable/each_with_index-vs-while-loop.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ While Loop 49.050k i/100ms
+ each_with_index 36.684k i/100ms
Calculating -------------------------------------
- each_with_index 11.496k i/100ms
- While Loop 20.179k i/100ms
--------------------------------------------------
- each_with_index 128.855k (± 7.5%) i/s - 643.776k
- While Loop 242.344k (± 4.5%) i/s - 1.211M
+ While Loop 501.430k (± 2.9%) i/s (1.99 μs/i) - 2.551M in 5.091049s
+ each_with_index 369.364k (± 0.3%) i/s (2.71 μs/i) - 1.871M in 5.065187s
Comparison:
- While Loop: 242343.6 i/s
- each_with_index: 128854.9 i/s - 1.88x slower
+ While Loop: 501430.2 i/s
+ each_with_index: 369364.5 i/s - 1.36x slower
```
##### `Enumerable#map`...`Array#flatten` vs `Enumerable#flat_map` [code](code/enumerable/map-flatten-vs-flat_map.rb)
@@ -318,22 +552,21 @@ Comparison:
> -- @sferik [rails/rails@3413b88](https://github.com/rails/rails/commit/3413b88), [Replace map.flatten with flat_map](https://github.com/rails/rails/commit/817fe31196dd59ee31f71ef1740122b6759cf16d), [Replace map.flatten(1) with flat_map](https://github.com/rails/rails/commit/b11ebf1d80e4fb124f0ce0448cea30988256da59)
```
-ruby -v code/enumerable/map-flatten-vs-flat_map.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+$ ruby -v code/enumerable/map-flatten-vs-flat_map.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Array#map.flatten(1) 19.072k i/100ms
+ Array#map.flatten 8.810k i/100ms
+ Array#flat_map 21.362k i/100ms
Calculating -------------------------------------
-Array#map.flatten(1) 3.315k i/100ms
- Array#map.flatten 3.283k i/100ms
- Array#flat_map 5.350k i/100ms
--------------------------------------------------
-Array#map.flatten(1) 33.801k (± 4.3%) i/s - 169.065k
- Array#map.flatten 34.530k (± 6.0%) i/s - 173.999k
- Array#flat_map 55.980k (± 5.0%) i/s - 283.550k
+Array#map.flatten(1) 196.444k (± 1.2%) i/s (5.09 μs/i) - 991.744k in 5.049225s
+ Array#map.flatten 89.917k (± 1.2%) i/s (11.12 μs/i) - 458.120k in 5.095661s
+ Array#flat_map 215.149k (± 0.6%) i/s (4.65 μs/i) - 1.089M in 5.063916s
Comparison:
- Array#flat_map: 55979.6 i/s
- Array#map.flatten: 34529.6 i/s - 1.62x slower
-Array#map.flatten(1): 33800.6 i/s - 1.66x slower
+ Array#flat_map: 215149.2 i/s
+Array#map.flatten(1): 196443.8 i/s - 1.10x slower
+ Array#map.flatten: 89916.9 i/s - 2.39x slower
```
##### `Enumerable#reverse.each` vs `Enumerable#reverse_each` [code](code/enumerable/reverse-each-vs-reverse_each.rb)
@@ -345,104 +578,211 @@ Array#map.flatten(1): 33800.6 i/s - 1.66x slower
```
$ ruby -v code/enumerable/reverse-each-vs-reverse_each.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Array#reverse.each 58.610k i/100ms
+ Array#reverse_each 63.669k i/100ms
+Calculating -------------------------------------
+ Array#reverse.each 586.309k (± 0.7%) i/s (1.71 μs/i) - 2.989M in 5.098469s
+ Array#reverse_each 635.904k (± 2.3%) i/s (1.57 μs/i) - 3.183M in 5.009371s
+
+Comparison:
+ Array#reverse_each: 635904.1 i/s
+ Array#reverse.each: 586309.0 i/s - 1.08x slower
+```
+##### `Enumerable#sort_by.first` vs `Enumerable#min_by` [code](code/enumerable/sort_by-first-vs-min_by.rb)
+`Enumerable#sort_by` performs a sort of the enumerable and allocates a
+new array the size of the enumerable. `Enumerable#min_by` doesn't
+perform a sort or allocate an array the size of the enumerable.
+Similar comparisons hold for `Enumerable#sort_by.last` vs
+`Enumerable#max_by`, `Enumerable#sort.first` vs `Enumerable#min`, and
+`Enumerable#sort.last` vs `Enumerable#max`.
+
+```
+$ ruby -v code/enumerable/sort_by-first-vs-min_by.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Enumerable#min_by 40.784k i/100ms
+Enumerable#sort_by...first
+ 32.912k i/100ms
Calculating -------------------------------------
- Array#reverse.each 16.746k i/100ms
- Array#reverse_each 18.590k i/100ms
--------------------------------------------------
- Array#reverse.each 190.729k (± 4.8%) i/s - 954.522k
- Array#reverse_each 216.060k (± 4.3%) i/s - 1.078M
+ Enumerable#min_by 408.114k (± 0.3%) i/s (2.45 μs/i) - 2.080M in 5.096613s
+Enumerable#sort_by...first
+ 334.401k (± 0.7%) i/s (2.99 μs/i) - 1.679M in 5.019698s
Comparison:
- Array#reverse_each: 216060.5 i/s
- Array#reverse.each: 190729.1 i/s - 1.13x slower
+ Enumerable#min_by: 408113.9 i/s
+Enumerable#sort_by...first: 334400.9 i/s - 1.22x slower
```
##### `Enumerable#detect` vs `Enumerable#select.first` [code](code/enumerable/select-first-vs-detect.rb)
```
-$ ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+$ ruby -v code/enumerable/select-first-vs-detect.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Enumerable#select.first
+ 37.402k i/100ms
+ Enumerable#detect 247.638k i/100ms
Calculating -------------------------------------
-Enumerable#select.first 8.515k i/100ms
- Enumerable#detect 33.885k i/100ms
--------------------------------------------------
-Enumerable#select.first 89.757k (± 5.0%) i/s - 1.797M
- Enumerable#detect 434.304k (± 5.2%) i/s - 8.675M
+Enumerable#select.first
+ 373.115k (± 0.6%) i/s (2.68 μs/i) - 7.480M in 20.049160s
+ Enumerable#detect 2.462M (± 0.8%) i/s (406.12 ns/i) - 49.280M in 20.014730s
Comparison:
- Enumerable#detect: 434304.2 i/s
-Enumerable#select.first: 89757.4 i/s - 4.84x slower
+ Enumerable#detect: 2462330.2 i/s
+Enumerable#select.first: 373115.1 i/s - 6.60x slower
```
##### `Enumerable#select.last` vs `Enumerable#reverse.detect` [code](code/enumerable/select-last-vs-reverse-detect.rb)
```
$ ruby -v code/enumerable/select-last-vs-reverse-detect.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Enumerable#reverse.detect
+ 23.133k i/100ms
+Enumerable#select.last
+ 562.000 i/100ms
Calculating -------------------------------------
-Enumerable#reverse.detect 62.636k i/100ms
-Enumerable#select.last 11.687k i/100ms
--------------------------------------------------
-Enumerable#reverse.detect 1.263M (± 8.2%) i/s - 6.326M
-Enumerable#select.last 119.387k (± 5.7%) i/s - 596.037k
+Enumerable#reverse.detect
+ 241.463k (± 5.5%) i/s (4.14 μs/i) - 1.226M in 5.092374s
+Enumerable#select.last
+ 5.640k (± 0.9%) i/s (177.30 μs/i) - 28.662k in 5.082075s
Comparison:
-Enumerable#reverse.detect: 1263100.2 i/s
-Enumerable#select.last: 119386.8 i/s - 10.58x slower
+Enumerable#reverse.detect: 241463.1 i/s
+Enumerable#select.last: 5640.3 i/s - 42.81x slower
```
##### `Enumerable#sort` vs `Enumerable#sort_by` [code](code/enumerable/sort-vs-sort_by.rb)
```
$ ruby -v code/enumerable/sort-vs-sort_by.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Enumerable#sort_by (Symbol#to_proc)
+ 16.077k i/100ms
+ Enumerable#sort_by 15.265k i/100ms
+ Enumerable#sort 4.580k i/100ms
+Calculating -------------------------------------
+Enumerable#sort_by (Symbol#to_proc)
+ 162.709k (± 0.9%) i/s (6.15 μs/i) - 819.927k in 5.039602s
+ Enumerable#sort_by 152.611k (± 0.7%) i/s (6.55 μs/i) - 763.250k in 5.001512s
+ Enumerable#sort 45.814k (± 1.4%) i/s (21.83 μs/i) - 233.580k in 5.099352s
+
+Comparison:
+Enumerable#sort_by (Symbol#to_proc): 162709.4 i/s
+ Enumerable#sort_by: 152611.3 i/s - 1.07x slower
+ Enumerable#sort: 45814.3 i/s - 3.55x slower
+```
+##### `Enumerable#inject Symbol` vs `Enumerable#inject Proc` [code](code/enumerable/inject-symbol-vs-block.rb)
+
+Of note, `to_proc` for 1.8.7 is considerable slower than the block format
+
+```
+$ ruby -v code/enumerable/inject-symbol-vs-block.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ inject symbol 183.846k i/100ms
+ inject to_proc 4.100k i/100ms
+ inject block 3.882k i/100ms
Calculating -------------------------------------
- Enumerable#sort 1.158k i/100ms
- Enumerable#sort_by 2.401k i/100ms
--------------------------------------------------
- Enumerable#sort 12.140k (± 4.9%) i/s - 61.374k
- Enumerable#sort_by 24.169k (± 4.0%) i/s - 122.451k
+ inject symbol 1.841M (± 0.4%) i/s (543.31 ns/i) - 9.376M in 5.094226s
+ inject to_proc 41.165k (± 0.3%) i/s (24.29 μs/i) - 209.100k in 5.079636s
+ inject block 39.550k (± 0.6%) i/s (25.28 μs/i) - 197.982k in 5.006073s
Comparison:
- Enumerable#sort_by: 24168.9 i/s
- Enumerable#sort: 12139.8 i/s - 1.99x slower
+ inject symbol: 1840578.1 i/s
+ inject to_proc: 41164.7 i/s - 44.71x slower
+ inject block: 39550.0 i/s - 46.54x slower
```
+### Date
+
+##### `Date.iso8601` vs `Date.parse` [code](code/date/iso8601-vs-parse.rb)
+
+When expecting well-formatted data from e.g. an API, `iso8601` is faster and will raise an `ArgumentError` on malformed input.
+
+```
+$ ruby -v code/date/iso8601-vs-parse.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Date.iso8601 195.684k i/100ms
+ Date.parse 62.633k i/100ms
+Calculating -------------------------------------
+ Date.iso8601 2.008M (± 0.8%) i/s (497.99 ns/i) - 10.176M in 5.067673s
+ Date.parse 627.349k (± 0.7%) i/s (1.59 μs/i) - 3.194M in 5.092000s
+
+Comparison:
+ Date.iso8601: 2008075.4 i/s
+ Date.parse: 627349.2 i/s - 3.20x slower
+```
### Hash
-#### `Hash#[]` vs `Hash.fetch` [code](code/hash/bracket-vs-fetch.rb)
+##### `Hash#[]` vs `Hash#fetch` [code](code/hash/bracket-vs-fetch.rb)
If you use Ruby 2.2, `Symbol` could be more performant than `String` as `Hash` keys.
Read more regarding this: [Symbol GC in Ruby 2.2](http://www.sitepoint.com/symbol-gc-ruby-2-2/) and [Unraveling String Key Performance in Ruby 2.2](http://www.sitepoint.com/unraveling-string-key-performance-ruby-2-2/).
```
$ ruby -v code/hash/bracket-vs-fetch.rb
-ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#[], symbol 3.471M i/100ms
+ Hash#fetch, symbol 2.869M i/100ms
+ Hash#[], string 1.969M i/100ms
+ Hash#fetch, string 1.746M i/100ms
+Calculating -------------------------------------
+ Hash#[], symbol 34.983M (± 0.8%) i/s (28.59 ns/i) - 177.026M in 5.060743s
+ Hash#fetch, symbol 28.927M (± 1.1%) i/s (34.57 ns/i) - 146.331M in 5.059120s
+ Hash#[], string 19.711M (± 1.7%) i/s (50.73 ns/i) - 100.431M in 5.096713s
+ Hash#fetch, string 17.825M (± 1.4%) i/s (56.10 ns/i) - 90.775M in 5.093555s
+Comparison:
+ Hash#[], symbol: 34982573.5 i/s
+ Hash#fetch, symbol: 28927450.2 i/s - 1.21x slower
+ Hash#[], string: 19710678.7 i/s - 1.77x slower
+ Hash#fetch, string: 17825222.3 i/s - 1.96x slower
+```
+
+##### `Hash#dig` vs `Hash#[]` vs `Hash#fetch` [code](code/hash/dig-vs-[]-vs-fetch.rb)
+
+[Ruby 2.3 introduced `Hash#dig`](http://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig) which is a readable
+and performant option for retrieval from a nested hash, returning `nil` if an extraction step fails.
+See [#102 (comment)](https://github.com/fastruby/fast-ruby/pull/102#issuecomment-198827506) for more info.
+
+```
+$ ruby -v code/hash/dig-vs-[]-vs-fetch.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#dig 1.939M i/100ms
+ Hash#[] 1.903M i/100ms
+ Hash#[] || 1.787M i/100ms
+ Hash#[] && 725.944k i/100ms
+ Hash#fetch 1.246M i/100ms
+ Hash#fetch fallback 803.269k i/100ms
Calculating -------------------------------------
- Hash#[], symbol 143.850k i/100ms
- Hash#fetch, symbol 137.425k i/100ms
- Hash#[], string 143.083k i/100ms
- Hash#fetch, string 120.417k i/100ms
--------------------------------------------------
- Hash#[], symbol 7.531M (± 6.6%) i/s - 37.545M
- Hash#fetch, symbol 6.644M (± 8.2%) i/s - 32.982M
- Hash#[], string 6.657M (± 7.7%) i/s - 33.195M
- Hash#fetch, string 3.981M (± 8.7%) i/s - 19.748M
+ Hash#dig 19.434M (± 0.8%) i/s (51.46 ns/i) - 98.877M in 5.088095s
+ Hash#[] 19.005M (± 2.2%) i/s (52.62 ns/i) - 95.141M in 5.008511s
+ Hash#[] || 17.879M (± 0.7%) i/s (55.93 ns/i) - 91.138M in 5.097648s
+ Hash#[] && 7.303M (± 0.6%) i/s (136.93 ns/i) - 37.023M in 5.069773s
+ Hash#fetch 12.634M (± 2.0%) i/s (79.15 ns/i) - 63.570M in 5.033708s
+ Hash#fetch fallback 8.117M (± 1.0%) i/s (123.20 ns/i) - 40.967M in 5.047539s
Comparison:
- Hash#[], symbol: 7531355.8 i/s
- Hash#[], string: 6656818.8 i/s - 1.13x slower
- Hash#fetch, symbol: 6643665.5 i/s - 1.13x slower
- Hash#fetch, string: 3981166.5 i/s - 1.89x slower
+ Hash#dig: 19434078.6 i/s
+ Hash#[]: 19004999.7 i/s - same-ish: difference falls within error
+ Hash#[] ||: 17879337.5 i/s - 1.09x slower
+ Hash#fetch: 12633982.2 i/s - 1.54x slower
+ Hash#fetch fallback: 8116930.3 i/s - 2.39x slower
+ Hash#[] &&: 7302991.9 i/s - 2.66x slower
```
-##### `Hash#[]` vs `Hash#dup` [code](code/hash/bracket-vs-dup.rb)
+##### `Hash[]` vs `Hash#dup` [code](code/hash/bracket-vs-dup.rb)
Source: http://tenderlovemaking.com/2015/02/11/weird-stuff-with-hashes.html
@@ -453,36 +793,42 @@ Source: http://tenderlovemaking.com/2015/02/11/weird-stuff-with-hashes.html
```
$ ruby -v code/hash/bracket-vs-dup.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash[] 684.060k i/100ms
+ Hash#dup 612.214k i/100ms
Calculating -------------------------------------
- Hash[] 29.403k i/100ms
- Hash#dup 16.195k i/100ms
--------------------------------------------------
- Hash[] 343.987k (± 8.7%) i/s - 1.735M
- Hash#dup 163.516k (±10.2%) i/s - 825.945k
+ Hash[] 7.555M (± 2.8%) i/s (132.37 ns/i) - 38.307M in 5.074888s
+ Hash#dup 5.851M (± 2.1%) i/s (170.90 ns/i) - 29.386M in 5.024310s
Comparison:
- Hash[]: 343986.5 i/s
- Hash#dup: 163516.3 i/s - 2.10x slower
+ Hash[]: 7554758.4 i/s
+ Hash#dup: 5851408.7 i/s - 1.29x slower
```
##### `Hash#fetch` with argument vs `Hash#fetch` + block [code](code/hash/fetch-vs-fetch-with-block.rb)
+> Note that the speedup in the block version comes from avoiding repeated
+> construction of the argument. If the argument is a constant, number symbol or
+> something of that sort the argument version is actually slightly faster
+> See also [#39 (comment)](https://github.com/fastruby/fast-ruby/issues/39#issuecomment-103989335)
+
```
$ ruby -v code/hash/fetch-vs-fetch-with-block.rb
-ruby 2.2.1p85 (2015-02-26 revision 49769) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#fetch + const 3.136M i/100ms
+ Hash#fetch + block 3.313M i/100ms
+ Hash#fetch + arg 2.266M i/100ms
Calculating -------------------------------------
- Hash#fetch + block 139.880k i/100ms
- Hash#fetch + arg 119.645k i/100ms
--------------------------------------------------
- Hash#fetch + block 6.116M (± 8.9%) i/s - 30.354M
- Hash#fetch + arg 4.473M (± 9.9%) i/s - 22.134M
+ Hash#fetch + const 32.198M (± 1.0%) i/s (31.06 ns/i) - 163.090M in 5.065722s
+ Hash#fetch + block 33.319M (± 1.1%) i/s (30.01 ns/i) - 168.953M in 5.071324s
+ Hash#fetch + arg 22.838M (± 1.3%) i/s (43.79 ns/i) - 115.561M in 5.060964s
Comparison:
- Hash#fetch + block: 6116059.5 i/s
- Hash#fetch + arg: 4472636.0 i/s - 1.37x slower
+ Hash#fetch + block: 33319415.6 i/s
+ Hash#fetch + const: 32198246.2 i/s - 1.03x slower
+ Hash#fetch + arg: 22837560.4 i/s - 1.46x slower
```
##### `Hash#each_key` instead of `Hash#keys.each` [code](code/hash/keys-each-vs-each_key.rb)
@@ -494,54 +840,150 @@ Comparison:
```
$ ruby -v code/hash/keys-each-vs-each_key.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#keys.each 494.495k i/100ms
+ Hash#each_key 510.269k i/100ms
+Calculating -------------------------------------
+ Hash#keys.each 5.026M (± 0.5%) i/s (198.95 ns/i) - 25.219M in 5.017574s
+ Hash#each_key 5.118M (± 1.2%) i/s (195.39 ns/i) - 26.024M in 5.085610s
+
+Comparison:
+ Hash#each_key: 5117890.6 i/s
+ Hash#keys.each: 5026302.4 i/s - 1.02x slower
+```
+
+#### `Hash#key?` instead of `Hash#keys.include?` [code](code/hash/keys-include-vs-key.rb)
+
+> `Hash#keys.include?` allocates an array of keys and performs an O(n) search;
+> `Hash#key?` performs an O(1) hash lookup without allocating a new array.
+
+```
+$ ruby -v code/hash/keys-include-vs-key.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#keys.include? 2.217k i/100ms
+ Hash#key? 2.683M i/100ms
+Calculating -------------------------------------
+ Hash#keys.include? 26.005k (± 5.6%) i/s (38.45 μs/i) - 130.803k in 5.047937s
+ Hash#key? 26.686M (± 1.3%) i/s (37.47 ns/i) - 134.156M in 5.028106s
+
+Comparison:
+ Hash#key?: 26685586.3 i/s
+ Hash#keys.include?: 26005.2 i/s - 1026.16x slower
+```
+
+##### `Hash#value?` instead of `Hash#values.include?` [code](code/hash/values-include-vs-value.rb)
+
+> `Hash#values.include?` allocates an array of values and performs an O(n) search;
+> `Hash#value?` performs an O(n) search without allocating a new array.
+```
+$ ruby -v code/hash/values-include-vs-value.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Hash#values.include? 5.620k i/100ms
+ Hash#value? 7.474k i/100ms
Calculating -------------------------------------
- Hash#keys.each 56.690k i/100ms
- Hash#each_key 59.658k i/100ms
--------------------------------------------------
- Hash#keys.each 869.262k (± 5.0%) i/s - 4.365M
- Hash#each_key 1.049M (± 6.0%) i/s - 5.250M
+Hash#values.include? 62.053k (± 2.8%) i/s (16.12 μs/i) - 314.720k in 5.076009s
+ Hash#value? 71.151k (± 4.4%) i/s (14.05 μs/i) - 358.752k in 5.051774s
Comparison:
- Hash#each_key: 1049161.6 i/s
- Hash#keys.each: 869262.3 i/s - 1.21x slower
+ Hash#value?: 71150.8 i/s
+Hash#values.include?: 62052.8 i/s - 1.15x slower
```
##### `Hash#merge!` vs `Hash#[]=` [code](code/hash/merge-bang-vs-\[\]=.rb)
```
-$ ruby -v code/hash/merge-bang-vs-\[\]=.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+$ ruby -v code/hash/merge-bang-vs-[]=.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#merge! 8.974k i/100ms
+ Hash#[]= 20.052k i/100ms
+Calculating -------------------------------------
+ Hash#merge! 91.391k (± 0.5%) i/s (10.94 μs/i) - 457.674k in 5.008010s
+ Hash#[]= 202.686k (± 0.9%) i/s (4.93 μs/i) - 1.023M in 5.045877s
+
+Comparison:
+ Hash#[]=: 202686.0 i/s
+ Hash#merge!: 91390.8 i/s - 2.22x slower
+```
+
+##### `Hash#update` vs `Hash#[]=` [code](code/hash/update-vs-\[\]=.rb)
+
+```
+$ ruby -v code/hash/update-vs-[]=.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#[]= 19.739k i/100ms
+ Hash#update 9.126k i/100ms
+Calculating -------------------------------------
+ Hash#[]= 202.714k (± 1.1%) i/s (4.93 μs/i) - 1.026M in 5.064063s
+ Hash#update 91.615k (± 0.7%) i/s (10.92 μs/i) - 465.426k in 5.080450s
+
+Comparison:
+ Hash#[]=: 202713.9 i/s
+ Hash#update: 91615.2 i/s - 2.21x slower
+```
+##### `Hash#merge` vs `Hash#**other` [code](code/hash/merge-vs-double-splat-operator.rb)
+
+```
+$ ruby -v code/hash/merge-vs-double-splat-operator.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#**other 979.615k i/100ms
+ Hash#merge 798.516k i/100ms
Calculating -------------------------------------
- Hash#merge! 1.023k i/100ms
- Hash#[]= 2.844k i/100ms
--------------------------------------------------
- Hash#merge! 10.653k (± 4.9%) i/s - 53.196k
- Hash#[]= 28.287k (±12.4%) i/s - 142.200k
+ Hash#**other 9.846M (± 0.3%) i/s (101.57 ns/i) - 49.960M in 5.074265s
+ Hash#merge 7.968M (± 0.3%) i/s (125.50 ns/i) - 39.926M in 5.010730s
Comparison:
- Hash#[]=: 28287.1 i/s
- Hash#merge!: 10653.3 i/s - 2.66x slower
+ Hash#**other: 9845909.6 i/s
+ Hash#merge: 7968122.7 i/s - 1.24x slower
```
##### `Hash#merge` vs `Hash#merge!` [code](code/hash/merge-vs-merge-bang.rb)
```
$ ruby -v code/hash/merge-vs-merge-bang.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Hash#merge 3.800k i/100ms
+ Hash#merge! 9.310k i/100ms
+Calculating -------------------------------------
+ Hash#merge 38.042k (± 1.9%) i/s (26.29 μs/i) - 193.800k in 5.096234s
+ Hash#merge! 94.284k (± 0.4%) i/s (10.61 μs/i) - 474.810k in 5.036016s
+Comparison:
+ Hash#merge!: 94284.1 i/s
+ Hash#merge: 38041.7 i/s - 2.48x slower
+```
+
+##### `{}#merge!(Hash)` vs `Hash#merge({})` vs `Hash#dup#merge!({})` [code](code/hash/merge-bang-vs-merge-vs-dup-merge-bang.rb)
+
+> When we don't want to modify the original hash, and we want duplicates to be created
+> See [#42](https://github.com/fastruby/fast-ruby/pull/42#issue-93502261) for more details.
+
+```
+$ ruby -v code/hash/merge-bang-vs-merge-vs-dup-merge-bang.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+{}#merge!(Hash) do end
+ 11.079k i/100ms
+ Hash#merge({}) 9.290k i/100ms
+ Hash#dup#merge!({}) 6.957k i/100ms
Calculating -------------------------------------
- Hash#merge 39.000 i/100ms
- Hash#merge! 1.008k i/100ms
--------------------------------------------------
- Hash#merge 409.610 (± 7.6%) i/s - 2.067k
- Hash#merge! 9.830k (± 5.8%) i/s - 49.392k
+{}#merge!(Hash) do end
+ 111.999k (± 0.3%) i/s (8.93 μs/i) - 565.029k in 5.045023s
+ Hash#merge({}) 93.149k (± 0.4%) i/s (10.74 μs/i) - 473.790k in 5.086441s
+ Hash#dup#merge!({}) 69.802k (± 0.4%) i/s (14.33 μs/i) - 354.807k in 5.083113s
Comparison:
- Hash#merge!: 9830.3 i/s
- Hash#merge: 409.6 i/s - 24.00x slower
+{}#merge!(Hash) do end: 111998.6 i/s
+ Hash#merge({}): 93149.0 i/s - 1.20x slower
+ Hash#dup#merge!({}): 69802.3 i/s - 1.60x slower
```
##### `Hash#sort_by` vs `Hash#sort` [code](code/hash/hash-key-sort_by-vs-sort.rb)
@@ -550,20 +992,45 @@ To sort hash by key.
```
$ ruby -v code/hash/hash-key-sort_by-vs-sort.rb
-ruby 2.2.1p85 (2015-02-26 revision 49769) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ sort_by + to_h 72.056k i/100ms
+ sort + to_h 26.595k i/100ms
+Calculating -------------------------------------
+ sort_by + to_h 739.508k (± 0.7%) i/s (1.35 μs/i) - 3.747M in 5.067024s
+ sort + to_h 269.700k (± 1.9%) i/s (3.71 μs/i) - 1.356M in 5.030958s
+
+Comparison:
+ sort_by + to_h: 739507.8 i/s
+ sort + to_h: 269700.3 i/s - 2.74x slower
+```
+
+##### Native `Hash#slice` vs other slice implementations before native [code](code/hash/slice-native-vs-before-native.rb)
+Since ruby 2.5, Hash comes with a `slice` method to select hash members by keys.
+
+```
+$ ruby -v code/hash/slice-native-vs-before-native.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+Hash#native-slice 1.024M i/100ms
+Array#each 467.292k i/100ms
+Array#each_w/_object 388.528k i/100ms
+Hash#select-include 131.358k i/100ms
Calculating -------------------------------------
- sort_by + to_h 11.468k i/100ms
- sort + to_h 8.107k i/100ms
--------------------------------------------------
- sort_by + to_h 122.176k (± 6.0%) i/s - 619.272k
- sort + to_h 81.973k (± 4.7%) i/s - 413.457k
+Hash#native-slice 10.550M (± 1.4%) i/s (94.78 ns/i) - 53.251M in 5.048257s
+Array#each 4.688M (± 0.5%) i/s (213.30 ns/i) - 23.832M in 5.083384s
+Array#each_w/_object 3.890M (± 0.4%) i/s (257.07 ns/i) - 19.815M in 5.093848s
+Hash#select-include 1.343M (± 1.7%) i/s (744.63 ns/i) - 6.831M in 5.087848s
Comparison:
- sort_by + to_h: 122176.2 i/s
- sort + to_h: 81972.8 i/s - 1.49x slower
+Hash#native-slice : 10550464.1 i/s
+Array#each : 4688305.3 i/s - 2.25x slower
+Array#each_w/_object: 3890033.7 i/s - 2.71x slower
+Hash#select-include : 1342942.2 i/s - 7.86x slower
```
+
### Proc & Block
##### Block vs `Symbol#to_proc` [code](code/proc-and-block/block-vs-to_proc.rb)
@@ -574,142 +1041,247 @@ Comparison:
```
$ ruby -v code/proc-and-block/block-vs-to_proc.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Block 20.147k i/100ms
+ Symbol#to_proc 22.231k i/100ms
Calculating -------------------------------------
- Block 4.632k i/100ms
- Symbol#to_proc 5.225k i/100ms
--------------------------------------------------
- Block 47.914k (± 6.3%) i/s - 240.864k
- Symbol#to_proc 54.791k (± 4.1%) i/s - 276.925k
+ Block 202.937k (± 0.4%) i/s (4.93 μs/i) - 1.027M in 5.063220s
+ Symbol#to_proc 222.450k (± 0.9%) i/s (4.50 μs/i) - 1.134M in 5.097179s
Comparison:
- Symbol#to_proc: 54791.1 i/s
- Block: 47914.3 i/s - 1.14x slower
+ Symbol#to_proc: 222449.5 i/s
+ Block: 202937.5 i/s - 1.10x slower
```
-##### `Proc#call` vs `yield` [code](code/proc-and-block/proc-call-vs-yield.rb)
+##### `Proc#call` and block arguments vs `yield` [code](code/proc-and-block/proc-call-vs-yield.rb)
```
$ ruby -v code/proc-and-block/proc-call-vs-yield.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ block.call 2.261M i/100ms
+ block + yield 2.314M i/100ms
+ unused block 3.025M i/100ms
+ yield 2.971M i/100ms
Calculating -------------------------------------
- block.call 70.663k i/100ms
- yield 125.061k i/100ms
--------------------------------------------------
- block.call 1.309M (± 5.7%) i/s - 6.572M
- yield 6.103M (± 7.7%) i/s - 30.390M
+ block.call 22.057M (± 6.0%) i/s (45.34 ns/i) - 110.796M in 5.043129s
+ block + yield 23.280M (± 0.6%) i/s (42.96 ns/i) - 117.997M in 5.068779s
+ unused block 30.609M (± 1.3%) i/s (32.67 ns/i) - 154.268M in 5.040991s
+ yield 29.921M (± 0.6%) i/s (33.42 ns/i) - 151.512M in 5.063842s
Comparison:
- yield: 6102822.9 i/s
- block.call: 1309452.1 i/s - 4.66x slower
+ unused block: 30608512.5 i/s
+ yield: 29921356.8 i/s - 1.02x slower
+ block + yield: 23279981.0 i/s - 1.31x slower
+ block.call: 22056758.6 i/s - 1.39x slower
```
-
### String
-##### `String#casecmp` vs `String#downcase + ==` [code](code/string/casecmp-vs-downcase-==.rb)
+##### `String#dup` vs `String#+` [code](code/string/dup-vs-unary-plus.rb)
+Note that `String.new` is not the same as the options compared, since it is
+always `ASCII-8BIT` encoded instead of the script encoding (usually `UTF-8`).
+
+```
+$ ruby -v code/string/dup-vs-unary-plus.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#+@ 2.439M i/100ms
+ String#dup 2.481M i/100ms
+Calculating -------------------------------------
+ String#+@ 24.328M (± 0.5%) i/s (41.10 ns/i) - 121.962M in 5.013305s
+ String#dup 24.553M (± 1.0%) i/s (40.73 ns/i) - 124.040M in 5.052462s
+
+Comparison:
+ String#dup: 24552887.2 i/s
+ String#+@: 24328187.6 i/s - same-ish: difference falls within error
```
-$ ruby -v code/string/casecmp-vs-downcase-\=\=.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+##### `String#casecmp` vs `String#casecmp?` vs `String#downcase + ==` [code](code/string/casecmp-vs-downcase-==.rb)
+
+`String#casecmp?` is available on Ruby 2.4 or later.
+Note that `String#casecmp` only works on characters A-Z/a-z, not all of Unicode.
+
+```
+$ ruby -v code/string/casecmp-vs-downcase-==.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#casecmp? 1.053M i/100ms
+String#downcase + == 1.467M i/100ms
+ String#casecmp 1.816M i/100ms
Calculating -------------------------------------
-String#downcase + == 101.900k i/100ms
- String#casecmp 109.828k i/100ms
--------------------------------------------------
-String#downcase + == 2.915M (± 5.4%) i/s - 14.572M
- String#casecmp 3.708M (± 6.1%) i/s - 18.561M
+ String#casecmp? 10.916M (± 1.4%) i/s (91.61 ns/i) - 54.769M in 5.018429s
+String#downcase + == 14.673M (± 1.0%) i/s (68.15 ns/i) - 74.808M in 5.098814s
+ String#casecmp 18.210M (± 0.7%) i/s (54.91 ns/i) - 92.594M in 5.084879s
Comparison:
- String#casecmp: 3708258.7 i/s
-String#downcase + ==: 2914767.7 i/s - 1.27x slower
+ String#casecmp: 18210413.9 i/s
+String#downcase + ==: 14673089.8 i/s - 1.24x slower
+ String#casecmp?: 10915954.0 i/s - 1.67x slower
```
##### String Concatenation [code](code/string/concatenation.rb)
```
-$ ruby code/string/concatenation.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+$ ruby -v code/string/concatenation.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#+ 1.214M i/100ms
+ String#concat 1.407M i/100ms
+ String#append 1.561M i/100ms
+ "foo" "bar" 2.723M i/100ms
+ "#{'foo'}#{'bar'}" 2.914M i/100ms
Calculating -------------------------------------
- String#+ 96.314k i/100ms
- String#concat 99.850k i/100ms
- String#append 100.728k i/100ms
- "foo" "bar" 121.936k i/100ms
--------------------------------------------------
- String#+ 2.731M (± 4.6%) i/s - 13.677M
- String#concat 2.847M (± 5.2%) i/s - 14.279M
- String#append 2.972M (± 6.1%) i/s - 14.807M
- "foo" "bar" 4.951M (± 6.2%) i/s - 24.753M
+ String#+ 12.616M (± 0.9%) i/s (79.27 ns/i) - 63.138M in 5.005092s
+ String#concat 14.555M (± 1.1%) i/s (68.71 ns/i) - 73.188M in 5.029155s
+ String#append 15.844M (± 0.7%) i/s (63.12 ns/i) - 79.634M in 5.026390s
+ "foo" "bar" 27.318M (± 0.8%) i/s (36.61 ns/i) - 138.888M in 5.084517s
+ "#{'foo'}#{'bar'}" 29.063M (± 0.5%) i/s (34.41 ns/i) - 145.707M in 5.013531s
Comparison:
- "foo" "bar": 4950955.3 i/s
- String#append: 2972048.5 i/s - 1.67x slower
- String#concat: 2846666.4 i/s - 1.74x slower
- String#+: 2730980.7 i/s - 1.81x slower
+ "#{'foo'}#{'bar'}": 29063458.4 i/s
+ "foo" "bar": 27317882.5 i/s - 1.06x slower
+ String#append: 15843896.8 i/s - 1.83x slower
+ String#concat: 14554534.2 i/s - 2.00x slower
+ String#+: 12615859.7 i/s - 2.30x slower
```
-##### `String#match` vs `String#start_with?`/`String#end_with?` [code (start)](code/string/start-string-checking-match-vs-start_with.rb) [code (end)](code/string/end-string-checking-match-vs-start_with.rb)
+##### `String#match` vs `String.match?` vs `String#start_with?`/`String#end_with?` [code (start)](code/string/start-string-checking-match-vs-start_with.rb) [code (end)](code/string/end-string-checking-match-vs-end_with.rb)
+
+The regular expression approaches become slower as the tested string becomes
+longer. For short strings, `String#match?` performs similarly to
+`String#start_with?`/`String#end_with?`.
> :warning:
> Sometimes you cant replace regexp with `start_with?`,
> for example: `"a\nb" =~ /^b/ #=> 2` but `"a\nb" =~ /\Ab/ #=> nil`.
> :warning:
-> You can combine `start_with?` and `end_with?` to replace
-> `error.path =~ /^#{path}(\.rb)?$/` to this
-> `error.path.start_with?(path) && error.path.end_with?('.rb', '')`
-> —— @igas [rails/rails#17316](https://github.com/rails/rails/pull/17316)
```
$ ruby -v code/string/start-string-checking-match-vs-start_with.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#=~ 704.323k i/100ms
+ String#match? 1.696M i/100ms
+ String#start_with? 2.002M i/100ms
+Calculating -------------------------------------
+ String#=~ 7.155M (± 0.9%) i/s (139.76 ns/i) - 35.920M in 5.020673s
+ String#match? 17.088M (± 0.7%) i/s (58.52 ns/i) - 86.478M in 5.061156s
+ String#start_with? 20.186M (± 1.0%) i/s (49.54 ns/i) - 102.087M in 5.057969s
+Comparison:
+ String#start_with?: 20185554.4 i/s
+ String#match?: 17087554.1 i/s - 1.18x slower
+ String#=~: 7155069.8 i/s - 2.82x slower
+```
+
+```
+$ ruby -v code/string/end-string-checking-match-vs-end_with.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#=~ 459.289k i/100ms
+ String#match? 808.294k i/100ms
+ String#end_with? 1.358M i/100ms
Calculating -------------------------------------
- String#=~ 55.411k i/100ms
- String#start_with? 113.854k i/100ms
--------------------------------------------------
- String#=~ 910.625k (± 4.6%) i/s - 4.544M
- String#start_with? 3.983M (± 5.5%) i/s - 19.924M
+ String#=~ 4.900M (± 0.7%) i/s (204.10 ns/i) - 24.802M in 5.062282s
+ String#match? 7.999M (± 3.2%) i/s (125.02 ns/i) - 40.415M in 5.058576s
+ String#end_with? 13.797M (± 0.9%) i/s (72.48 ns/i) - 69.248M in 5.019420s
Comparison:
- String#start_with?: 3983284.9 i/s
- String#=~: 910625.0 i/s - 4.37x slower
+ String#end_with?: 13797085.6 i/s
+ String#match?: 7998569.4 i/s - 1.72x slower
+ String#=~: 4899556.6 i/s - 2.82x slower
```
+##### `String#start_with?` vs `String#[].==` [code](code/string/start_with-vs-substring-==.rb)
+
+```
+$ ruby -v code/string/start_with-vs-substring-==.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#start_with? 405.198k i/100ms
+ String#[0, n] == 185.548k i/100ms
+ String#[RANGE] == 183.537k i/100ms
+ String#[0...n] == 111.844k i/100ms
+Calculating -------------------------------------
+ String#start_with? 4.336M (± 1.7%) i/s (230.64 ns/i) - 21.881M in 5.048171s
+ String#[0, n] == 1.911M (± 1.8%) i/s (523.18 ns/i) - 9.648M in 5.049643s
+ String#[RANGE] == 1.835M (± 1.4%) i/s (544.95 ns/i) - 9.177M in 5.001963s
+ String#[0...n] == 1.100M (± 1.5%) i/s (909.34 ns/i) - 5.592M in 5.086419s
+
+Comparison:
+ String#start_with?: 4335677.0 i/s
+ String#[0, n] ==: 1911392.8 i/s - 2.27x slower
+ String#[RANGE] ==: 1835034.3 i/s - 2.36x slower
+ String#[0...n] ==: 1099697.5 i/s - 3.94x slower
```
-$ ruby -v code/string/end-string-checking-match-vs-start_with.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+##### `Regexp#===` vs `Regexp#match` vs `Regexp#match?` vs `String#match` vs `String#=~` vs `String#match?` [code ](code/string/===-vs-=~-vs-match.rb)
+
+`String#match?` and `Regexp#match?` are available on Ruby 2.4 or later.
+ActiveSupport [provides](http://guides.rubyonrails.org/v5.1/active_support_core_extensions.html#match-questionmark)
+a forward compatible extension of `Regexp` for older Rubies without the speed
+improvement.
+
+> :warning:
+> Sometimes you can't replace `match` with `match?`,
+> This is only useful for cases where you are checking
+> for a match and not using the resultant match object.
+> :warning:
+> `Regexp#===` is also faster than `String#match` but you need to switch the order of arguments.
+
+```
+$ ruby -v code/string/===-vs-=~-vs-match.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Regexp#match? 2.240M i/100ms
+ String#match? 2.271M i/100ms
+ String#=~ 1.322M i/100ms
+ Regexp#=== 1.252M i/100ms
+ Regexp#match 1.187M i/100ms
+ String#match 1.024M i/100ms
Calculating -------------------------------------
- String#=~ 52.811k i/100ms
- String#end_with? 100.071k i/100ms
--------------------------------------------------
- String#=~ 854.830k (± 5.8%) i/s - 4.278M
- String#end_with? 2.837M (± 5.5%) i/s - 14.210M
+ Regexp#match? 22.395M (± 1.3%) i/s (44.65 ns/i) - 111.995M in 5.001594s
+ String#match? 22.544M (± 1.4%) i/s (44.36 ns/i) - 113.533M in 5.037001s
+ String#=~ 13.285M (± 2.6%) i/s (75.27 ns/i) - 67.438M in 5.079611s
+ Regexp#=== 12.472M (± 0.6%) i/s (80.18 ns/i) - 62.618M in 5.020860s
+ Regexp#match 11.865M (± 0.8%) i/s (84.28 ns/i) - 59.340M in 5.001611s
+ String#match 10.223M (± 0.7%) i/s (97.81 ns/i) - 51.194M in 5.007796s
Comparison:
- String#end_with?: 2836536.9 i/s
- String#=~: 854830.3 i/s - 3.32x slower
+ String#match?: 22544340.1 i/s
+ Regexp#match?: 22395457.7 i/s - same-ish: difference falls within error
+ String#=~: 13285297.2 i/s - 1.70x slower
+ Regexp#===: 12471978.3 i/s - 1.81x slower
+ Regexp#match: 11864949.7 i/s - 1.90x slower
+ String#match: 10223419.6 i/s - 2.21x slower
```
-##### `String#gsub` vs `String#sub` [code](code/string/gsub-vs-sub.rb)
+See [#59](https://github.com/fastruby/fast-ruby/pull/59) and [#62](https://github.com/fastruby/fast-ruby/pull/62) for discussions.
+
+
+##### `String#gsub` vs `String#sub` vs `String#[]=` [code](code/string/gsub-vs-sub.rb)
```
$ ruby -v code/string/gsub-vs-sub.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#gsub 230.980k i/100ms
+ String#sub 465.575k i/100ms
+String#dup["string"]=
+ 838.284k i/100ms
Calculating -------------------------------------
- String#gsub 35.724k i/100ms
- String#sub 42.426k i/100ms
--------------------------------------------------
- String#gsub 486.614k (± 5.4%) i/s - 2.429M
- String#sub 611.259k (± 4.6%) i/s - 3.055M
+ String#gsub 2.338M (± 0.9%) i/s (427.64 ns/i) - 11.780M in 5.038072s
+ String#sub 4.691M (± 1.2%) i/s (213.16 ns/i) - 23.744M in 5.062169s
+String#dup["string"]=
+ 8.433M (± 1.0%) i/s (118.58 ns/i) - 42.752M in 5.070311s
Comparison:
- String#sub: 611259.4 i/s
- String#gsub: 486613.5 i/s - 1.26x slower
+String#dup["string"]=: 8432793.1 i/s
+ String#sub: 4691246.0 i/s - 1.80x slower
+ String#gsub: 2338396.7 i/s - 3.61x slower
```
##### `String#gsub` vs `String#tr` [code](code/string/gsub-vs-tr.rb)
@@ -718,110 +1290,241 @@ Comparison:
```
$ ruby -v code/string/gsub-vs-tr.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#gsub 297.314k i/100ms
+ String#tr 914.116k i/100ms
+Calculating -------------------------------------
+ String#gsub 3.032M (± 0.8%) i/s (329.83 ns/i) - 15.163M in 5.001554s
+ String#tr 9.136M (± 0.8%) i/s (109.46 ns/i) - 45.706M in 5.003201s
+
+Comparison:
+ String#tr: 9135960.5 i/s
+ String#gsub: 3031853.2 i/s - 3.01x slower
+```
+##### `String#gsub` vs `String#tr` vs `String#delete` [code](code/string/gsub-vs-tr-vs-delete.rb)
+
+```
+$ ruby -v code/string/gsub-vs-tr-vs-delete.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#gsub 314.117k i/100ms
+ String#tr 989.154k i/100ms
+ String#delete 1.159M i/100ms
+ String#delete const 1.320M i/100ms
+Calculating -------------------------------------
+ String#gsub 3.099M (± 1.1%) i/s (322.65 ns/i) - 15.706M in 5.068096s
+ String#tr 9.868M (± 1.3%) i/s (101.34 ns/i) - 49.458M in 5.012992s
+ String#delete 11.623M (± 0.6%) i/s (86.03 ns/i) - 59.109M in 5.085570s
+ String#delete const 13.192M (± 0.8%) i/s (75.80 ns/i) - 65.989M in 5.002525s
+
+Comparison:
+ String#delete const: 13191917.5 i/s
+ String#delete: 11623217.0 i/s - 1.13x slower
+ String#tr: 9867528.0 i/s - 1.34x slower
+ String#gsub: 3099363.7 i/s - 4.26x slower
+```
+
+##### `Mutable` vs `Immutable` [code](code/string/mutable_vs_immutable_strings.rb)
+
+```
+$ ruby -v code/string/mutable_vs_immutable_strings.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Without Freeze 2.671M i/100ms
+ With Freeze 4.464M i/100ms
Calculating -------------------------------------
- String#gsub 38.268k i/100ms
- String#tr 83.210k i/100ms
--------------------------------------------------
- String#gsub 516.604k (± 4.4%) i/s - 2.602M
- String#tr 1.862M (± 4.0%) i/s - 9.320M
+ Without Freeze 27.091M (± 1.6%) i/s (36.91 ns/i) - 136.216M in 5.029377s
+ With Freeze 44.763M (± 0.9%) i/s (22.34 ns/i) - 227.646M in 5.085943s
Comparison:
- String#tr: 1861860.4 i/s
- String#gsub: 516604.2 i/s - 3.60x slower
+ With Freeze: 44763124.8 i/s
+ Without Freeze: 27091288.1 i/s - 1.65x slower
```
+
##### `String#sub!` vs `String#gsub!` vs `String#[]=` [code](code/string/sub!-vs-gsub!-vs-[]=.rb)
Note that `String#[]` will throw an `IndexError` when given string or regexp not matched.
```
-$ ruby -v code/string/sub\!-vs-gsub\!-vs-\[\]\=.rb
-ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
+$ ruby -v code/string/sub!-vs-gsub!-vs-[]=.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#['string']= 867.608k i/100ms
+ String#sub!'string' 452.774k i/100ms
+String#gsub!'string' 219.002k i/100ms
+ String#[/regexp/]= 484.258k i/100ms
+ String#sub!/regexp/ 439.965k i/100ms
+String#gsub!/regexp/ 222.202k i/100ms
+Calculating -------------------------------------
+ String#['string']= 8.875M (± 0.9%) i/s (112.67 ns/i) - 45.116M in 5.083636s
+ String#sub!'string' 4.648M (± 0.6%) i/s (215.15 ns/i) - 23.544M in 5.065834s
+String#gsub!'string' 2.234M (± 0.6%) i/s (447.72 ns/i) - 11.169M in 5.000776s
+ String#[/regexp/]= 4.913M (± 0.6%) i/s (203.54 ns/i) - 24.697M in 5.026953s
+ String#sub!/regexp/ 4.474M (± 0.4%) i/s (223.50 ns/i) - 22.438M in 5.014974s
+String#gsub!/regexp/ 2.221M (± 0.7%) i/s (450.25 ns/i) - 11.110M in 5.002620s
+
+Comparison:
+ String#['string']=: 8875438.0 i/s
+ String#[/regexp/]=: 4913121.6 i/s - 1.81x slower
+ String#sub!'string': 4647843.8 i/s - 1.91x slower
+ String#sub!/regexp/: 4474302.7 i/s - 1.98x slower
+String#gsub!'string': 2233558.9 i/s - 3.97x slower
+String#gsub!/regexp/: 2220978.6 i/s - 4.00x slower
+```
+
+##### `String#sub` vs `String#delete_prefix` [code](code/string/sub-vs-delete_prefix.rb)
+
+[Ruby 2.5 introduced](https://bugs.ruby-lang.org/issues/12694) `String#delete_prefix`.
+Note that this can only be used for removing characters from the start of a string.
+```
+$ ruby -v code/string/sub-vs-delete_prefix.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+String#delete_prefix 1.446M i/100ms
+ String#sub 473.108k i/100ms
Calculating -------------------------------------
- String#['string']= 74.512k i/100ms
- String#sub!'string' 52.801k i/100ms
-String#gsub!'string' 34.480k i/100ms
- String#[/regexp/]= 55.325k i/100ms
- String#sub!/regexp/ 45.770k i/100ms
-String#gsub!/regexp/ 27.665k i/100ms
--------------------------------------------------
- String#['string']= 1.215M (± 6.2%) i/s - 6.110M
- String#sub!'string' 752.731k (± 6.2%) i/s - 3.749M
-String#gsub!'string' 481.183k (± 4.4%) i/s - 2.414M
- String#[/regexp/]= 840.615k (± 5.3%) i/s - 4.205M
- String#sub!/regexp/ 663.075k (± 7.8%) i/s - 3.295M
-String#gsub!/regexp/ 342.004k (± 7.5%) i/s - 1.715M
+String#delete_prefix 14.867M (± 1.3%) i/s (67.26 ns/i) - 75.200M in 5.059147s
+ String#sub 4.726M (± 0.7%) i/s (211.58 ns/i) - 23.655M in 5.005369s
Comparison:
- String#['string']=: 1214845.5 i/s
- String#[/regexp/]=: 840615.2 i/s - 1.45x slower
- String#sub!'string': 752731.4 i/s - 1.61x slower
- String#sub!/regexp/: 663075.3 i/s - 1.83x slower
-String#gsub!'string': 481183.5 i/s - 2.52x slower
-String#gsub!/regexp/: 342003.8 i/s - 3.55x slower
+String#delete_prefix: 14866723.8 i/s
+ String#sub: 4726255.3 i/s - 3.15x slower
```
-##### `attr_accessor` vs `getter and setter` [code](code/general/attr-accessor-vs-getter-and-setter.rb)
+##### `String#sub` vs `String#chomp` vs `String#delete_suffix` [code](code/string/sub-vs-chomp-vs-delete_suffix.rb)
-> https://www.omniref.com/ruby/2.2.0/files/method.h?#annotation=4081781&line=47
+[Ruby 2.5 introduced](https://bugs.ruby-lang.org/issues/13665) `String#delete_suffix`
+as a counterpart to `delete_prefix`. The performance gain over `chomp` is
+small and during some runs the difference falls within the error margin.
+Note that this can only be used for removing characters from the end of a string.
```
-$ ruby -v code/general/attr-accessor-vs-getter-and-setter.rb
-ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin14]
+$ ruby -v code/string/sub-vs-chomp-vs-delete_suffix.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#sub 472.278k i/100ms
+ String#chomp 1.354M i/100ms
+String#delete_suffix 1.477M i/100ms
+Calculating -------------------------------------
+ String#sub 4.809M (± 1.0%) i/s (207.93 ns/i) - 24.086M in 5.008842s
+ String#chomp 13.829M (± 1.2%) i/s (72.31 ns/i) - 70.425M in 5.093208s
+String#delete_suffix 14.936M (± 0.9%) i/s (66.95 ns/i) - 75.317M in 5.042961s
+
+Comparison:
+String#delete_suffix: 14936380.8 i/s
+ String#chomp: 13829135.7 i/s - 1.08x slower
+ String#sub: 4809249.5 i/s - 3.11x slower
+```
+
+##### `String#unpack1` vs `String#unpack[0]` [code](code/string/unpack1-vs-unpack[0].rb)
+
+[Ruby 2.4.0 introduced `unpack1`](https://bugs.ruby-lang.org/issues/12752) to skip creating the intermediate array object.
+
+```
+$ ruby -v code/string/unpack1-vs-unpack[0].rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#unpack1 1.372M i/100ms
+ String#unpack[0] 1.095M i/100ms
Calculating -------------------------------------
- getter_and_setter 61.240k i/100ms
- attr_accessor 66.535k i/100ms
--------------------------------------------------
- getter_and_setter 1.660M (± 9.7%) i/s - 8.267M
- attr_accessor 1.865M (± 9.2%) i/s - 9.248M
+ String#unpack1 14.219M (± 0.7%) i/s (70.33 ns/i) - 71.349M in 5.018051s
+ String#unpack[0] 11.071M (± 1.2%) i/s (90.33 ns/i) - 55.839M in 5.044380s
Comparison:
- attr_accessor: 1865408.4 i/s
- getter_and_setter: 1660021.9 i/s - 1.12x slower
+ String#unpack1: 14219190.8 i/s
+ String#unpack[0]: 11071030.6 i/s - 1.28x slower
```
+##### Remove extra spaces (or other contiguous characters) [code](code/string/remove-extra-spaces-or-other-chars.rb)
+
+The code is tested against contiguous spaces but should work for other chars too.
+
+```
+$ ruby -v code/string/remove-extra-spaces-or-other-chars.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ String#gsub/regex+/ 15.087k i/100ms
+ String#squeeze 429.561k i/100ms
+Calculating -------------------------------------
+ String#gsub/regex+/ 152.509k (± 0.6%) i/s (6.56 μs/i) - 769.437k in 5.045412s
+ String#squeeze 4.264M (± 1.7%) i/s (234.51 ns/i) - 21.478M in 5.038218s
+
+Comparison:
+ String#squeeze: 4264261.4 i/s
+ String#gsub/regex+/: 152508.7 i/s - 27.96x slower
+```
+
+### Time
+
+##### `Time.iso8601` vs `Time.parse` [code](code/time/iso8601-vs-parse.rb)
+
+When expecting well-formatted data from e.g. an API, `iso8601` is faster and will raise an `ArgumentError` on malformed input.
+
+```
+$ ruby -v code/time/iso8601-vs-parse.rb
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ Time.iso8601 105.846k i/100ms
+ Time.parse 19.826k i/100ms
+Calculating -------------------------------------
+ Time.iso8601 1.076M (± 0.5%) i/s (929.16 ns/i) - 5.398M in 5.015850s
+ Time.parse 200.370k (± 0.8%) i/s (4.99 μs/i) - 1.011M in 5.046598s
+
+Comparison:
+ Time.iso8601: 1076241.3 i/s
+ Time.parse: 200370.2 i/s - 5.37x slower
+```
### Range
-#### `cover?` vs `include?` [code](code/range/cover-vs-include.rb)
+##### `cover?` vs `include?` [code](code/range/cover-vs-include.rb)
`cover?` only check if it is within the start and end, `include?` needs to traverse the whole range.
```
$ ruby -v code/range/cover-vs-include.rb
-ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
-
+ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]
+Warming up --------------------------------------
+ range#cover? 811.176k i/100ms
+ range#include? 34.101k i/100ms
+ range#member? 35.591k i/100ms
+ plain compare 1.131M i/100ms
+ value.between? 1.520M i/100ms
Calculating -------------------------------------
- Range#cover? 95.445k i/100ms
- Range#include? 9.326k i/100ms
--------------------------------------------------
- Range#cover? 2.327M (± 4.7%) i/s - 11.644M
- Range#include? 99.652k (± 5.4%) i/s - 503.604k
+ range#cover? 8.324M (± 0.3%) i/s (120.14 ns/i) - 42.181M in 5.067623s
+ range#include? 352.846k (± 1.7%) i/s (2.83 μs/i) - 1.773M in 5.027064s
+ range#member? 349.526k (± 1.9%) i/s (2.86 μs/i) - 1.780M in 5.093144s
+ plain compare 11.296M (± 1.3%) i/s (88.53 ns/i) - 56.554M in 5.007498s
+ value.between? 15.214M (± 0.9%) i/s (65.73 ns/i) - 77.527M in 5.096183s
Comparison:
- Range#cover?: 2327220.4 i/s
- Range#include?: 99651.6 i/s - 23.35x slower
+ value.between?: 15213944.1 i/s
+ plain compare: 11295903.4 i/s - 1.35x slower
+ range#cover?: 8323723.8 i/s - 1.83x slower
+ range#include?: 352846.2 i/s - 43.12x slower
+ range#member?: 349526.0 i/s - 43.53x slower
```
## Less idiomatic but with significant performance ruby
-Checkout: https://github.com/JuanitoFatas/fast-ruby/wiki/Less-idiomatic-but-with-significant-performance-diffrence
+Checkout: https://github.com/fastruby/fast-ruby/wiki/Less-idiomatic-but-with-significant-performance-difference
## Submit New Entry
-Please! [Edit this README.md](https://github.com/JuanitoFatas/fast-ruby/edit/master/README.md) then [Submit a Awesome Pull Request](https://github.com/JuanitoFatas/fast-ruby/pulls)!
+Please! [Edit this README.md](https://github.com/fastruby/fast-ruby/edit/main/README.md) then [Submit a Awesome Pull Request](https://github.com/fastruby/fast-ruby/pulls)!
## Something went wrong
Code example is wrong? :cry: Got better example? :heart_eyes: Excellent!
-[Please open an issue](https://github.com/JuanitoFatas/fast-ruby/issues/new) or [Open a Pull Request](https://github.com/JuanitoFatas/fast-ruby/pulls) to fix it.
+[Please open an issue](https://github.com/fastruby/fast-ruby/issues/new) or [Open a Pull Request](https://github.com/fastruby/fast-ruby/pulls) to fix it.
Thank you in advance! :wink: :beer:
@@ -837,6 +1540,10 @@ Feel free to talk with me on Twitter! <3
## Also Checkout
+- [Derailed Benchmarks](https://github.com/schneems/derailed_benchmarks)
+
+ Go faster, off the Rails - Benchmarks for your whole Rails app
+
- [Benchmarking Ruby](https://speakerdeck.com/davystevenson/benchmarking-ruby)
Talk by Davy Stevenson @ RubyConf 2014.
@@ -845,7 +1552,7 @@ Feel free to talk with me on Twitter! <3
Provides Big O notation benchmarking for Ruby.
-- [The Ruby Challenge](https://therubychallenge.com/)
+- [The Ruby Challenge](https://www.youtube.com/watch?v=aDeP7FGQBig)
Talk by Prem Sichanugrist @ Ruby Kaigi 2014.
diff --git a/code/array/array-concat-vs-+.rb b/code/array/array-concat-vs-+.rb
new file mode 100644
index 00000000..acfc6539
--- /dev/null
+++ b/code/array/array-concat-vs-+.rb
@@ -0,0 +1,19 @@
+require 'benchmark/ips'
+
+RANGE = (0..10_000).freeze
+
+def fast
+ array = []
+ RANGE.each { |number| array.concat(Array.new(10, number)) }
+end
+
+def slow
+ array = []
+ RANGE.each { |number| array += Array.new(10, number) }
+end
+
+Benchmark.ips do |x|
+ x.report('Array#concat') { fast }
+ x.report('Array#+') { slow }
+ x.compare!
+end
diff --git a/code/array/array-new-vs-fixnum-times-map.rb b/code/array/array-new-vs-fixnum-times-map.rb
new file mode 100644
index 00000000..fcd72605
--- /dev/null
+++ b/code/array/array-new-vs-fixnum-times-map.rb
@@ -0,0 +1,17 @@
+require "benchmark/ips"
+
+ELEMENTS = 9
+
+def fast
+ Array.new(ELEMENTS) { |i| i }
+end
+
+def slow
+ ELEMENTS.times.map { |i| i }
+end
+
+Benchmark.ips do |x|
+ x.report("Array#new") { fast }
+ x.report("Fixnum#times + map") { slow }
+ x.compare!
+end
diff --git a/code/array/count-vs-size.rb b/code/array/count-vs-size.rb
deleted file mode 100644
index a14fa399..00000000
--- a/code/array/count-vs-size.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'benchmark/ips'
-
-ARRAY = [*1..100]
-
-def slow
- ARRAY.count
-end
-
-def fast
- ARRAY.size
-end
-
-Benchmark.ips do |x|
- x.report('#count') { slow }
- x.report('#size') { fast }
- x.compare!
-end
diff --git a/code/array/insert-vs-unshift.rb b/code/array/insert-vs-unshift.rb
new file mode 100644
index 00000000..992f24f6
--- /dev/null
+++ b/code/array/insert-vs-unshift.rb
@@ -0,0 +1,15 @@
+require 'benchmark/ips'
+
+Benchmark.ips do |x|
+ x.report('Array#unshift') do
+ array = []
+ 100_000.times { |i| array.unshift(i) }
+ end
+
+ x.report('Array#insert') do
+ array = []
+ 100_000.times { |i| array.insert(0, i) }
+ end
+
+ x.compare!
+end
diff --git a/code/array/length-vs-size-vs-count.rb b/code/array/length-vs-size-vs-count.rb
new file mode 100644
index 00000000..6b7ff243
--- /dev/null
+++ b/code/array/length-vs-size-vs-count.rb
@@ -0,0 +1,10 @@
+require 'benchmark/ips'
+
+ARRAY = [*1..100]
+
+Benchmark.ips do |x|
+ x.report("Array#length") { ARRAY.length }
+ x.report("Array#size") { ARRAY.size }
+ x.report("Array#count") { ARRAY.count }
+ x.compare!
+end
diff --git a/code/array/sort-reverse-vs-sort_by-with-block.rb b/code/array/sort-reverse-vs-sort_by-with-block.rb
new file mode 100644
index 00000000..7cafc50f
--- /dev/null
+++ b/code/array/sort-reverse-vs-sort_by-with-block.rb
@@ -0,0 +1,18 @@
+require "benchmark/ips"
+
+ARRAY = 100.times.map { rand(1_000_000_000) }
+
+def fast
+ ARRAY.sort.reverse
+end
+
+def slow
+ ARRAY.sort_by(&:-@)
+end
+
+Benchmark.ips do |x|
+ x.report('Array#sort.reverse') { fast }
+ x.report('Array#sort_by &:-@') { slow }
+
+ x.compare!
+end
diff --git a/code/date/iso8601-vs-parse.rb b/code/date/iso8601-vs-parse.rb
new file mode 100644
index 00000000..6d0bce44
--- /dev/null
+++ b/code/date/iso8601-vs-parse.rb
@@ -0,0 +1,18 @@
+require 'benchmark/ips'
+require 'date'
+
+STRING = '2018-03-21'.freeze
+
+def fast
+ Date.iso8601(STRING)
+end
+
+def slow
+ Date.parse(STRING)
+end
+
+Benchmark.ips do |x|
+ x.report('Date.iso8601') { fast }
+ x.report('Date.parse') { slow }
+ x.compare!
+end
diff --git a/code/enumerable/each_with_index-vs-while-loop.rb b/code/enumerable/each_with_index-vs-while-loop.rb
index aff5a93d..4cf3f3e2 100644
--- a/code/enumerable/each_with_index-vs-while-loop.rb
+++ b/code/enumerable/each_with_index-vs-while-loop.rb
@@ -1,23 +1,24 @@
-require 'benchmark/ips'
+require "benchmark/ips"
ARRAY = [*1..100]
-def slow
- ARRAY.each_with_index do |number, index|
- number + index
- end
-end
-
def fast
index = 0
while index < ARRAY.size
ARRAY[index] + index
index += 1
end
+ ARRAY
+end
+
+def slow
+ ARRAY.each_with_index do |number, index|
+ number + index
+ end
end
Benchmark.ips do |x|
- x.report('each_with_index') { slow }
- x.report('While Loop') { fast }
+ x.report("While Loop") { fast }
+ x.report("each_with_index") { slow }
x.compare!
end
diff --git a/code/enumerable/inject-symbol-vs-block.rb b/code/enumerable/inject-symbol-vs-block.rb
new file mode 100644
index 00000000..c261f718
--- /dev/null
+++ b/code/enumerable/inject-symbol-vs-block.rb
@@ -0,0 +1,24 @@
+require "rubygems"
+require "benchmark/ips"
+
+ARRAY = (1..1000).to_a
+
+def fastest
+ ARRAY.inject(:+)
+end
+
+def fast
+ ARRAY.inject(&:+)
+end
+
+def slow
+ ARRAY.inject { |a, i| a + i }
+end
+
+Benchmark.ips do |x|
+ x.report('inject symbol') { fastest }
+ x.report('inject to_proc') { fast }
+ x.report('inject block') { slow }
+
+ x.compare!
+end
diff --git a/code/enumerable/select-last-vs-reverse-detect.rb b/code/enumerable/select-last-vs-reverse-detect.rb
index f2d64b5a..f06ed603 100644
--- a/code/enumerable/select-last-vs-reverse-detect.rb
+++ b/code/enumerable/select-last-vs-reverse-detect.rb
@@ -1,13 +1,13 @@
require 'benchmark/ips'
-ARRAY = [*1..100]
+ARRAY = [*1..7777]
def fast
- ARRAY.reverse.detect { |x| (x % 10).zero? }
+ ARRAY.reverse.detect { |x| (x % 5).zero? }
end
def slow
- ARRAY.select { |x| (x % 10).zero? }.last
+ ARRAY.select { |x| (x % 5).zero? }.last
end
Benchmark.ips do |x|
diff --git a/code/enumerable/sort-vs-sort_by.rb b/code/enumerable/sort-vs-sort_by.rb
index 979324f4..61032448 100644
--- a/code/enumerable/sort-vs-sort_by.rb
+++ b/code/enumerable/sort-vs-sort_by.rb
@@ -1,20 +1,26 @@
-require 'benchmark/ips'
+require "benchmark/ips"
User = Struct.new(:name)
ARRAY = Array.new(100) do
User.new(sprintf "%010d", rand(1_000_000_000))
end
-def slow
- ARRAY.sort { |a, b| a.name <=> b.name }
+def fastest
+ ARRAY.sort_by(&:name)
end
-def fast
- ARRAY.sort_by(&:name)
+def faster
+ ARRAY.sort_by { |element| element.name }
+end
+
+def slow
+ ARRAY.sort { |a, b| a.name <=> b.name }
end
Benchmark.ips do |x|
+ x.report('Enumerable#sort_by (Symbol#to_proc)') { fastest }
+ x.report('Enumerable#sort_by') { faster }
x.report('Enumerable#sort') { slow }
- x.report('Enumerable#sort_by') { fast }
+
x.compare!
end
diff --git a/code/enumerable/sort_by-first-vs-min_by.rb b/code/enumerable/sort_by-first-vs-min_by.rb
new file mode 100644
index 00000000..75cd2ecc
--- /dev/null
+++ b/code/enumerable/sort_by-first-vs-min_by.rb
@@ -0,0 +1,17 @@
+require 'benchmark/ips'
+
+ARRAY = [*1..100]
+
+def fast
+ ARRAY.min_by { |x| x.succ }
+end
+
+def slow
+ ARRAY.sort_by { |x| x.succ }.first
+end
+
+Benchmark.ips do |x|
+ x.report('Enumerable#min_by') { fast }
+ x.report('Enumerable#sort_by...first') { slow }
+ x.compare!
+end
diff --git a/code/general/array-argument-vs-splat-arguments.rb b/code/general/array-argument-vs-splat-arguments.rb
new file mode 100644
index 00000000..28de4604
--- /dev/null
+++ b/code/general/array-argument-vs-splat-arguments.rb
@@ -0,0 +1,22 @@
+require "benchmark/ips"
+
+module M
+ ITEMS = (1..10000).to_a.freeze
+
+ def self.func(*args)
+ end
+end
+
+def fast
+ M.func(M::ITEMS)
+end
+
+def slow
+ M.func(*M::ITEMS)
+end
+
+Benchmark.ips do |x|
+ x.report("Function with single Array argument") { fast }
+ x.report("Function with splat arguments") { slow }
+ x.compare!
+end
diff --git a/code/general/assignment.rb b/code/general/assignment.rb
deleted file mode 100644
index 320af1ba..00000000
--- a/code/general/assignment.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'benchmark/ips'
-
-def slow
- a, b, c, d, e, f, g, h = 1, 2, 3, 4, 5, 6, 7, 8
-end
-
-def fast
- a = 1
- b = 2
- c = 3
- d = 4
- e = 5
- f = 6
- g = 7
- h = 8
-end
-
-Benchmark.ips do |x|
- x.report('Parallel Assignment') { slow }
- x.report('Sequential Assignment') { fast }
- x.compare!
-end
diff --git a/code/general/constantize-vs-comparison.rb b/code/general/constantize-vs-comparison.rb
new file mode 100644
index 00000000..ab797b4b
--- /dev/null
+++ b/code/general/constantize-vs-comparison.rb
@@ -0,0 +1,20 @@
+require "active_support/core_ext/string/inflections.rb"
+require "benchmark/ips"
+
+class Foo; end
+
+def fast(s)
+ klass = Foo if s == "Foo"
+ nil
+end
+
+def slow(s)
+ klass = s.constantize
+ nil
+end
+
+Benchmark.ips do |x|
+ x.report("using an if statement") { fast("Foo") }
+ x.report("String#constantize") { slow("Foo") }
+ x.compare!
+end
diff --git a/code/general/format-vs-round-and-to-s.rb b/code/general/format-vs-round-and-to-s.rb
new file mode 100644
index 00000000..2556d96c
--- /dev/null
+++ b/code/general/format-vs-round-and-to-s.rb
@@ -0,0 +1,22 @@
+require 'benchmark/ips'
+
+NUM = 1.12678.freeze
+
+def fast
+ NUM.round(2).to_s
+end
+
+def avg
+ format('%.2f', NUM)
+end
+
+def slow
+ '%.2f' % NUM
+end
+
+Benchmark.ips do |x|
+ x.report('Float#round') { fast }
+ x.report('Kernel#format') { avg }
+ x.report('String#%') { slow }
+ x.compare!
+end
diff --git a/code/general/hash-vs-openstruct-on-access.rb b/code/general/hash-vs-openstruct-on-access.rb
new file mode 100644
index 00000000..bf5a65eb
--- /dev/null
+++ b/code/general/hash-vs-openstruct-on-access.rb
@@ -0,0 +1,19 @@
+require "benchmark/ips"
+require "ostruct"
+
+ HASH = { field_1: 1, field_2: 2}
+ OPENSTRUCT = OpenStruct.new(field_1: 1, field_2: 2)
+
+def fast
+ [HASH[:field_1], HASH[:field_2]]
+end
+
+def slow
+ [OPENSTRUCT.field_1, OPENSTRUCT.field_2]
+end
+
+Benchmark.ips do |x|
+ x.report("Hash") { fast }
+ x.report("OpenStruct") { slow }
+ x.compare!
+end
diff --git a/code/general/hash-vs-openstruct.rb b/code/general/hash-vs-openstruct.rb
new file mode 100644
index 00000000..bb342d89
--- /dev/null
+++ b/code/general/hash-vs-openstruct.rb
@@ -0,0 +1,16 @@
+require "benchmark/ips"
+require "ostruct"
+
+def fast
+ { field_1: 1, field_2: 2 }
+end
+
+def slow
+ OpenStruct.new(field_1: 1, field_2: 2)
+end
+
+Benchmark.ips do |x|
+ x.report("Hash") { fast }
+ x.report("OpenStruct") { slow }
+ x.compare!
+end
diff --git a/code/general/inheritance-check.rb b/code/general/inheritance-check.rb
new file mode 100644
index 00000000..2c272212
--- /dev/null
+++ b/code/general/inheritance-check.rb
@@ -0,0 +1,38 @@
+require 'benchmark/ips'
+
+# You may ask: 'Is there a project that still using `ancestors.include?`?'
+# By quick searching, I found the following popular repositories are still using it:
+# - rake
+# - https://github.com/ruby/rake/blob/7d0c08fe4e97083a92d2c8fc740cb421fd062117/lib/rake/task_manager.rb#L28
+# - warden
+# - https://github.com/hassox/warden/blob/090ed153dbd2f5bf4a1ca672b3018877e21223a4/lib/warden/strategies.rb#L16
+# - metasploit-framework
+# - https://github.com/rapid7/metasploit-framework/blob/cac890a797d0d770260074dfe703eb5cfb63bd46/lib/msf/core/payload_set.rb#L239
+# - https://github.com/rapid7/metasploit-framework/blob/cb82015c8782280d964e222615b54c881bd36bbe/lib/msf/core/exploit.rb#L1440
+# - hanami
+# - https://github.com/hanami/hanami/blob/506a35e5262939eb4dce9195ade3268e19928d00/lib/hanami/components/routes_inspector.rb#L54
+# - https://github.com/hanami/hanami/blob/aec069b602c772e279aa0a7f48d1a04d01756ee3/lib/hanami/configuration.rb#L114
+raise unless Object.ancestors.include?(Kernel)
+raise unless (Object <= Kernel)
+
+def fast
+ (Class <= Class)
+ (Class <= Module)
+ (Class <= Object)
+ (Class <= Kernel)
+ (Class <= BasicObject)
+end
+
+def slow
+ Class.ancestors.include?(Class)
+ Class.ancestors.include?(Module)
+ Class.ancestors.include?(Object)
+ Class.ancestors.include?(Kernel)
+ Class.ancestors.include?(BasicObject)
+end
+
+Benchmark.ips do |x|
+ x.report('less than or equal') { fast }
+ x.report('ancestors.include?') { slow }
+ x.compare!
+end
diff --git a/code/general/loop-vs-while-true.rb b/code/general/loop-vs-while-true.rb
new file mode 100644
index 00000000..33ae1391
--- /dev/null
+++ b/code/general/loop-vs-while-true.rb
@@ -0,0 +1,25 @@
+require "benchmark/ips"
+
+NUMBER = 100_000_000
+
+def fast
+ index = 0
+ while true
+ break if index > NUMBER
+ index += 1
+ end
+end
+
+def slow
+ index = 0
+ loop do
+ break if index > NUMBER
+ index += 1
+ end
+end
+
+Benchmark.ips do |x|
+ x.report("While Loop") { fast }
+ x.report("Kernel loop") { slow }
+ x.compare!
+end
diff --git a/code/general/raise-vs-e2mmap.rb b/code/general/raise-vs-e2mmap.rb
new file mode 100644
index 00000000..2d8ee4ee
--- /dev/null
+++ b/code/general/raise-vs-e2mmap.rb
@@ -0,0 +1,73 @@
+require 'benchmark/ips'
+require 'e2mmap'
+
+class WithE2MM
+ extend Exception2MessageMapper
+
+ def_e2message TypeError, 'argument must be a %s'
+ def_exception :FooError, 'foo: %s'
+
+ def self.raise_ruby_defined
+ Raise TypeError, 'Hash'
+ end
+
+ def self.raise_user_defined
+ Raise FooError, 'bar!'
+ end
+end
+
+class WithoutE2MM
+ FooError = Class.new(StandardError)
+
+ def self.raise_ruby_defined
+ raise TypeError, 'argument must be a Hash'
+ end
+
+ def self.raise_user_defined
+ raise FooError, 'foo: bar!'
+ end
+end
+
+def slow_ruby_defined
+ begin
+ WithE2MM.raise_ruby_defined
+ rescue
+ 'fast ruby'
+ end
+end
+
+def fast_ruby_defined
+ begin
+ WithoutE2MM.raise_ruby_defined
+ rescue
+ 'fast ruby'
+ end
+end
+
+def slow_user_defined
+ begin
+ WithE2MM.raise_user_defined
+ rescue
+ 'fast ruby'
+ end
+end
+
+def fast_user_defined
+ begin
+ WithoutE2MM.raise_user_defined
+ rescue
+ 'fast ruby'
+ end
+end
+
+Benchmark.ips do |x|
+ x.report('Ruby exception: E2MM#Raise') { slow_ruby_defined }
+ x.report('Ruby exception: Kernel#raise') { fast_ruby_defined }
+ x.compare!
+end
+
+Benchmark.ips do |x|
+ x.report('Custom exception: E2MM#Raise') { slow_user_defined }
+ x.report('Custom exception: Kernel#raise') { fast_user_defined }
+ x.compare!
+end
diff --git a/code/hash/dig-vs-[]-vs-fetch.rb b/code/hash/dig-vs-[]-vs-fetch.rb
new file mode 100644
index 00000000..d2964416
--- /dev/null
+++ b/code/hash/dig-vs-[]-vs-fetch.rb
@@ -0,0 +1,31 @@
+require "benchmark/ips"
+
+h = { a: { b: { c: { d: { e: "foo" } } } } }
+
+Benchmark.ips do |x|
+ x.report "Hash#dig" do
+ h.dig(:a, :b, :c, :d, :e)
+ end
+
+ x.report "Hash#[]" do
+ h[:a][:b][:c][:d][:e]
+ end
+
+ x.report "Hash#[] ||" do
+ ((((h[:a] || {})[:b] || {})[:c] || {})[:d] || {})[:e]
+ end
+
+ x.report "Hash#[] &&" do
+ h[:a] && h[:a][:b] && h[:a][:b][:c] && h[:a][:b][:c][:d] && h[:a][:b][:c][:d][:e]
+ end
+
+ x.report "Hash#fetch" do
+ h.fetch(:a).fetch(:b).fetch(:c).fetch(:d).fetch(:e)
+ end
+
+ x.report "Hash#fetch fallback" do
+ h.fetch(:a, {}).fetch(:b, {}).fetch(:c, {}).fetch(:d, {}).fetch(:e, nil)
+ end
+
+ x.compare!
+end
diff --git a/code/hash/fetch-vs-fetch-with-block.rb b/code/hash/fetch-vs-fetch-with-block.rb
index 94e2a49c..68badccf 100644
--- a/code/hash/fetch-vs-fetch-with-block.rb
+++ b/code/hash/fetch-vs-fetch-with-block.rb
@@ -1,17 +1,11 @@
require "benchmark/ips"
HASH = { writing: :fast_ruby }
-
-def fast
- HASH.fetch(:writing) { "fast ruby" }
-end
-
-def slow
- HASH.fetch(:writing, "fast ruby")
-end
+DEFAULT = "fast ruby"
Benchmark.ips do |x|
- x.report("Hash#fetch + block") { fast }
- x.report("Hash#fetch + arg") { slow }
+ x.report("Hash#fetch + const") { HASH.fetch(:writing, DEFAULT) }
+ x.report("Hash#fetch + block") { HASH.fetch(:writing) { "fast ruby" } }
+ x.report("Hash#fetch + arg") { HASH.fetch(:writing, "fast ruby") }
x.compare!
end
diff --git a/code/hash/keys-include-vs-key.rb b/code/hash/keys-include-vs-key.rb
new file mode 100644
index 00000000..1dbd4cbe
--- /dev/null
+++ b/code/hash/keys-include-vs-key.rb
@@ -0,0 +1,18 @@
+require "benchmark/ips"
+
+HASH = Hash[*("a".."zzz").to_a.shuffle]
+KEY = "zz"
+
+def key_fast
+ HASH.key? KEY
+end
+
+def key_slow
+ HASH.keys.include? KEY
+end
+
+Benchmark.ips do |x|
+ x.report("Hash#keys.include?") { key_slow }
+ x.report("Hash#key?") { key_fast }
+ x.compare!
+end
diff --git a/code/hash/merge-bang-vs-merge-vs-dup-merge-bang.rb b/code/hash/merge-bang-vs-merge-vs-dup-merge-bang.rb
new file mode 100644
index 00000000..b4fd86b5
--- /dev/null
+++ b/code/hash/merge-bang-vs-merge-vs-dup-merge-bang.rb
@@ -0,0 +1,29 @@
+require "benchmark/ips"
+
+ENUM = (1..100)
+ORIGINAL_HASH = { foo: "foo" }
+
+def fast
+ ENUM.inject([]) do |accumulator, element|
+ accumulator << ({ bar: element }.merge!(ORIGINAL_HASH){ |_key, left, _right| left })
+ end
+end
+
+def slow
+ ENUM.inject([]) do |accumulator, element|
+ accumulator << ORIGINAL_HASH.merge(bar: element)
+ end
+end
+
+def slow_dup
+ ENUM.inject([]) do |accumulator, element|
+ accumulator << ORIGINAL_HASH.dup.merge!(bar: element)
+ end
+end
+
+Benchmark.ips do |x|
+ x.report("{}#merge!(Hash) do end") { fast }
+ x.report("Hash#merge({})") { slow }
+ x.report("Hash#dup#merge!({})") { slow_dup }
+ x.compare!
+end
diff --git a/code/hash/merge-vs-double-splat-operator.rb b/code/hash/merge-vs-double-splat-operator.rb
new file mode 100644
index 00000000..b0b63e0d
--- /dev/null
+++ b/code/hash/merge-vs-double-splat-operator.rb
@@ -0,0 +1,17 @@
+require 'benchmark/ips'
+
+def fast
+ h2 = { a: 'a' }
+ { one: 1, **h2 }
+end
+
+def slow
+ h2 = { a: 'a' }
+ { one: 1 }.merge(h2)
+end
+
+Benchmark.ips do |x|
+ x.report('Hash#**other') { fast }
+ x.report('Hash#merge') { slow }
+ x.compare!
+end
diff --git a/code/hash/slice-native-vs-before-native.rb b/code/hash/slice-native-vs-before-native.rb
new file mode 100644
index 00000000..85cc70f3
--- /dev/null
+++ b/code/hash/slice-native-vs-before-native.rb
@@ -0,0 +1,49 @@
+require 'benchmark/ips'
+
+HASH = {
+ title: "awesome",
+ description: "a description",
+ author: "styd",
+ published_at: Time.now
+}
+
+HASH.dup.each{ |k, v| HASH[:"other_#{k}"], HASH[:"more_#{k}"] = v, v }
+
+# Native implementation since v2.5
+#
+def fastest
+ HASH.slice(*%i[title author other])
+end
+
+# @ixti's faster implementation than that of ActiveSupport below
+#
+# Source:
+# https://github.com/JuanitoFatas/fast-ruby/pull/173#issuecomment-470554483
+#
+def faster
+ memo = {}
+ %i[title author other].each { |k| memo[k] = HASH[k] if HASH.key?(k) }
+ memo
+end
+
+# ActiveSupport implementation of `slice` when it's not defined.
+#
+# Source:
+# https://github.com/rails/rails/blob/01ae39660243bc5f0a986e20f9c9bff312b1b5f8/activesupport/lib/active_support/core_ext/hash/slice.rb#L24
+#
+def fast
+ %i[title author other].each_with_object({}){ |k, h| h[k] = HASH[k] if HASH.key?(k) }
+end
+
+def slow
+ keys = %i[title author other]
+ HASH.select { |k, _| keys.include? k }
+end
+
+Benchmark.ips do |x|
+ x.report('Hash#native-slice ') { fastest }
+ x.report('Array#each ') { faster }
+ x.report('Array#each_w/_object') { fast }
+ x.report('Hash#select-include ') { slow }
+ x.compare!
+end
diff --git a/code/hash/update-vs-[]=.rb b/code/hash/update-vs-[]=.rb
new file mode 100644
index 00000000..a004edf5
--- /dev/null
+++ b/code/hash/update-vs-[]=.rb
@@ -0,0 +1,21 @@
+require 'benchmark/ips'
+
+ENUM = (1..100)
+
+def fast
+ ENUM.each_with_object({}) do |e, h|
+ h[e] = e
+ end
+end
+
+def slow
+ ENUM.each_with_object({}) do |e, h|
+ h.update(e => e)
+ end
+end
+
+Benchmark.ips do |x|
+ x.report('Hash#[]=') { fast }
+ x.report('Hash#update') { slow }
+ x.compare!
+end
diff --git a/code/hash/values-include-vs-value.rb b/code/hash/values-include-vs-value.rb
new file mode 100644
index 00000000..ae4342dc
--- /dev/null
+++ b/code/hash/values-include-vs-value.rb
@@ -0,0 +1,18 @@
+require "benchmark/ips"
+
+HASH = Hash[*("a".."zzz").to_a.shuffle]
+VALUE = "zz"
+
+def value_fast
+ HASH.value? VALUE
+end
+
+def value_slow
+ HASH.values.include? VALUE
+end
+
+Benchmark.ips do |x|
+ x.report("Hash#values.include?") { value_slow }
+ x.report("Hash#value?") { value_fast }
+ x.compare!
+end
diff --git a/code/proc-and-block/proc-call-vs-yield.rb b/code/proc-and-block/proc-call-vs-yield.rb
index f2e4545d..3e95968c 100644
--- a/code/proc-and-block/proc-call-vs-yield.rb
+++ b/code/proc-and-block/proc-call-vs-yield.rb
@@ -1,15 +1,25 @@
require 'benchmark/ips'
-def slow &block
+def slow(&block)
block.call
end
+def slow2(&block)
+ yield
+end
+
+def slow3(&block)
+
+end
+
def fast
yield
end
Benchmark.ips do |x|
x.report('block.call') { slow { 1 + 1 } }
+ x.report('block + yield') { slow2 { 1 + 1 } }
+ x.report('unused block') { slow3 { 1 + 1 } }
x.report('yield') { fast { 1 + 1 } }
x.compare!
end
diff --git a/code/range/cover-vs-include.rb b/code/range/cover-vs-include.rb
index 34085ae2..8d23c1b4 100644
--- a/code/range/cover-vs-include.rb
+++ b/code/range/cover-vs-include.rb
@@ -5,16 +5,12 @@
END_OF_JULY = Date.new(2015, 7, 31)
DAY_IN_JULY = Date.new(2015, 7, 15)
-def fast
- (BEGIN_OF_JULY..END_OF_JULY).cover? DAY_IN_JULY
-end
-
-def slow
- (BEGIN_OF_JULY..END_OF_JULY).include? DAY_IN_JULY
-end
-
Benchmark.ips do |x|
- x.report("Range#cover?") { fast }
- x.report("Range#include?") { slow }
+ x.report('range#cover?') { (BEGIN_OF_JULY..END_OF_JULY).cover? DAY_IN_JULY }
+ x.report('range#include?') { (BEGIN_OF_JULY..END_OF_JULY).include? DAY_IN_JULY }
+ x.report('range#member?') { (BEGIN_OF_JULY..END_OF_JULY).member? DAY_IN_JULY }
+ x.report('plain compare') { BEGIN_OF_JULY < DAY_IN_JULY && DAY_IN_JULY < END_OF_JULY }
+ x.report('value.between?') { DAY_IN_JULY.between?(BEGIN_OF_JULY, END_OF_JULY) }
+
x.compare!
end
diff --git a/code/string/===-vs-=~-vs-match.rb b/code/string/===-vs-=~-vs-match.rb
new file mode 100644
index 00000000..6ac401bd
--- /dev/null
+++ b/code/string/===-vs-=~-vs-match.rb
@@ -0,0 +1,35 @@
+require "benchmark/ips"
+
+def fastest
+ /boo/.match?('foo'.freeze)
+end
+
+def fast
+ "foo".freeze.match?(/boo/)
+end
+
+def slow
+ "foo".freeze =~ /boo/
+end
+
+def slower
+ /boo/ === "foo".freeze
+end
+
+def even_slower
+ /boo/.match('foo'.freeze)
+end
+
+def slowest
+ "foo".freeze.match(/boo/)
+end
+
+Benchmark.ips do |x|
+ x.report("Regexp#match?") { fastest } if RUBY_VERSION >= "2.4.0".freeze
+ x.report("String#match?") { fast } if RUBY_VERSION >= "2.4.0".freeze
+ x.report("String#=~") { slow }
+ x.report("Regexp#===") { slower }
+ x.report("Regexp#match") { even_slower }
+ x.report("String#match") { slowest }
+ x.compare!
+end
diff --git a/code/string/casecmp-vs-downcase-==.rb b/code/string/casecmp-vs-downcase-==.rb
index 027f7483..be46803f 100644
--- a/code/string/casecmp-vs-downcase-==.rb
+++ b/code/string/casecmp-vs-downcase-==.rb
@@ -2,6 +2,10 @@
SLUG = 'ABCD'
+def slowest
+ SLUG.casecmp?('abcd')
+end
+
def slow
SLUG.downcase == 'abcd'
end
@@ -11,6 +15,7 @@ def fast
end
Benchmark.ips do |x|
+ x.report("String#casecmp?") { slowest } if RUBY_VERSION >= "2.4.0".freeze
x.report('String#downcase + ==') { slow }
x.report('String#casecmp') { fast }
x.compare!
diff --git a/code/string/concatenation.rb b/code/string/concatenation.rb
index 1d697c32..e249a307 100644
--- a/code/string/concatenation.rb
+++ b/code/string/concatenation.rb
@@ -20,10 +20,15 @@ def fast
'foo' 'bar'
end
+def fast_interpolation
+ "#{'foo'}#{'bar'}"
+end
+
Benchmark.ips do |x|
- x.report('String#+') { slow_plus }
- x.report('String#concat') { slow_concat }
- x.report('String#append') { slow_append }
- x.report('"foo" "bar"') { fast }
+ x.report('String#+') { slow_plus }
+ x.report('String#concat') { slow_concat }
+ x.report('String#append') { slow_append }
+ x.report('"foo" "bar"') { fast }
+ x.report('"#{\'foo\'}#{\'bar\'}"') { fast_interpolation }
x.compare!
end
diff --git a/code/string/concatenation_randomized.rb b/code/string/concatenation_randomized.rb
new file mode 100644
index 00000000..8a5e7a9f
--- /dev/null
+++ b/code/string/concatenation_randomized.rb
@@ -0,0 +1,154 @@
+require 'benchmark/ips'
+
+module RandStr
+ RND_STRINGS_AMOUNT = 1000
+ @rand_strs = {
+ lt100: [],
+ lt10: [],
+ lt1000: [],
+ eq10: [],
+ eq100: [],
+ }
+
+ def self.generate_rand_strs
+ chars = ('A'..'z').to_a * 20
+ @rand_strs[:lt10] = Array.new(RND_STRINGS_AMOUNT) { chars.sample(rand(10)).join }
+ @rand_strs[:lt100] = Array.new(RND_STRINGS_AMOUNT) { chars.sample(rand(100)).join }
+ @rand_strs[:lt1000] = Array.new(RND_STRINGS_AMOUNT) { chars.sample(rand(1000)).join }
+ @rand_strs[:eq10] = Array.new(RND_STRINGS_AMOUNT) { chars.sample(10).join }
+ @rand_strs[:eq100] = Array.new(RND_STRINGS_AMOUNT) { chars.sample(100).join }
+ end
+
+ self.generate_rand_strs
+
+ def self.rand_str(named_range)
+ @rand_strs[named_range][rand(RND_STRINGS_AMOUNT)]
+ end
+
+ def self.method_missing(symbol)
+ return super unless @rand_strs.keys.include?(symbol)
+
+ define_singleton_method(symbol) { rand_str(symbol) }
+ return rand_str(symbol)
+ end
+
+end
+
+
+# 2 + 1 = 3 object
+def fastest_plus(foo, bar)
+ foo + bar
+end
+
+# 2 + 1 = 3 object
+def slow_concat(foo, bar)
+ foo.concat bar
+end
+
+# 2 + 1 = 3 object
+def slow_append(foo, bar)
+ foo << bar
+end
+
+
+def fast_interpolation(foo, bar)
+ "#{foo}#{bar}"
+end
+
+# bench_100_to_100
+# Rehearsal -----------------------------------------------------------
+# String#+ 1.263725 0.027868 1.291593 ( 1.292498)
+# "#{foo}#{bar}" 1.139442 0.022956 1.162398 ( 1.163574)
+# String#concat 2.017746 0.014836 2.032582 ( 2.034682)
+# String#append 1.320778 0.000000 1.320778 ( 1.321896)
+# Collateral actions only 0.713309 0.000000 0.713309 ( 0.714402)
+# -------------------------------------------------- total: 6.520660sec
+#
+# user system total real nomalized ratio
+# Collateral actions only 0.703668 0.000000 0.703668 ( 0.705658)
+# String#+ 1.014123 0.000000 1.014123 ( 1.015003) 0.30934
+# "#{foo}#{bar}" 1.101751 0.000585 1.102336 ( 1.103558) 0.3979 x 1.3 slower
+# String#concat 1.382647 0.000000 1.382647 ( 1.385333) 0.679675 x 2.2 slower
+# String#append 1.319974 0.000000 1.319974 ( 1.324772) 0.619114 x 2 slower
+
+def bench_100_to_100
+ Benchmark.ips do |x|
+ # 1M for rehearsal + 1M for bm
+ sarr1 = Array.new(2_000_000) { RandStr.eq100.dup }
+ sarr2 = Array.new(2_000_000) { RandStr.eq100.dup }
+
+ i, j = 0, 0
+ # if we want compare apples with apples, we need to measure and exclude "collateral" operations:
+ # integer += 1, access to an array of randomized strings 100 symbols length,
+ # then two methods invocation from RandStr module eq100 / lt100.
+ #
+ # and only then we can compare string concat methods properly
+ x.report("Collateral actions only") { k=0; 1_000_000.times { k+=1; RandStr.eq100; sarr2[k]; RandStr.lt100; } }
+
+ x.report("String#+") { k=0; 1_000_000.times { k+=1; sarr1[k]; fastest_plus(RandStr.eq100, RandStr.lt100) } }
+ x.report('"#{foo}#{bar}"') { k=0; 1_000_000.times { k+=1; sarr2[k]; fast_interpolation(RandStr.eq100, RandStr.lt100) } }
+ x.report("String#concat") { 1_000_000.times { RandStr.eq100; slow_concat(sarr1[i], RandStr.lt100); i+=1; } }
+ x.report("String#append") { 1_000_000.times { RandStr.eq100; slow_append(sarr2[j], RandStr.lt100); j+=1; } }
+ end
+end
+
+# bench_100_to_1000
+# Rehearsal -----------------------------------------------------------
+# Collateral actions only 0.674168 0.000016 0.674184 ( 0.675031)
+# String#+ 2.148756 0.032954 2.181710 ( 2.187042)
+# "#{foo}#{bar}" 1.570816 0.004948 1.575764 ( 1.579080)
+# String#concat 2.223220 0.160917 2.384137 ( 2.387601)
+# String#append 2.005056 0.202962 2.208018 ( 2.211476)
+# -------------------------------------------------- total: 9.023813sec
+#
+# user system total real nomalized ratio
+# Collateral actions only 0.666190 0.000000 0.666190 ( 0.666398)
+# String#+ 1.077629 0.036944 1.114573 ( 1.115465) 0.449067
+# "#{foo}#{bar}" 1.230489 0.001029 1.231518 ( 1.232423) 0.566025 x 1.25 slower
+# String#concat 1.881313 0.149949 2.031262 ( 2.033965) 1.367567 x 3.05 slower
+# String#append 1.913785 0.177921 2.091706 ( 2.094298) 1.4279 x 3.18 slower
+
+def bench_100_to_1000
+ Benchmark.ips do |x|
+ sarr1 = Array.new(2_000_000) { RandStr.eq100.dup }
+ sarr2 = Array.new(2_000_000) { RandStr.eq100.dup }
+
+ i, j = 0, 0
+ x.report("Collateral actions only") { k=0; 1_000_000.times { k+=1; RandStr.eq100; sarr2[k]; RandStr.lt1000; } }
+
+ x.report("String#+") { k=0; 1_000_000.times { k+=1; sarr1[k]; fastest_plus(RandStr.eq100, RandStr.lt1000) } }
+ x.report('"#{foo}#{bar}"') { k=0; 1_000_000.times { k+=1; sarr2[k]; fast_interpolation(RandStr.eq100, RandStr.lt1000) } }
+ x.report("String#concat") { 1_000_000.times { RandStr.eq100; slow_concat(sarr1[i], RandStr.lt1000); i+=1; } }
+ x.report("String#append") { 1_000_000.times { RandStr.eq100; slow_append(sarr2[j], RandStr.lt1000); j+=1; } }
+ end
+end
+
+# bench_10_to_100
+# Rehearsal -----------------------------------------------------------
+# Collateral actions only 0.681273 0.000000 0.681273 ( 0.681611)
+# String#+ 1.188326 0.000701 1.189027 ( 1.196455)
+# "#{foo}#{bar}" 1.182554 0.003851 1.186405 ( 1.191678)
+# String#concat 1.707191 0.006764 1.713955 ( 1.720055)
+# String#append 1.177368 0.000831 1.178199 ( 1.184116)
+# -------------------------------------------------- total: 5.948859sec
+#
+# user system total real nomalized ratio
+# Collateral actions only 0.682486 0.000000 0.682486 ( 0.682818)
+# String#+ 0.914002 0.000000 0.914002 ( 0.917294) 0.234476
+# "#{foo}#{bar}" 1.096633 0.000966 1.097599 ( 1.100782) 0.417964 x 1.78 slower
+# String#concat 1.373582 0.000910 1.374492 ( 1.375239) 0.692421 x 2.95 slower
+# String#append 1.300632 0.000000 1.300632 ( 1.300807) 0.617989 x 2.63 slower
+
+def bench_10_to_100
+ Benchmark.ips do |x|
+ sarr1 = Array.new(2_000_000) { RandStr.eq100.dup }
+ sarr2 = Array.new(2_000_000) { RandStr.eq100.dup }
+
+ i, j = 0, 0
+ x.report("Collateral actions only") { k=0; 1_000_000.times { k+=1; RandStr.eq10; sarr2[k]; RandStr.lt100; } }
+ x.report("String#+") { k=0; 1_000_000.times { k+=1; sarr1[k]; fastest_plus(RandStr.eq10, RandStr.lt100) } }
+ x.report('"#{foo}#{bar}"') { k=0; 1_000_000.times { k+=1; sarr2[k]; fast_interpolation(RandStr.eq10, RandStr.lt100) } }
+ x.report("String#concat") { 1_000_000.times { RandStr.eq10; slow_concat(sarr1[i], RandStr.lt100); i+=1; } }
+ x.report("String#append") { 1_000_000.times { RandStr.eq10; slow_append(sarr2[j], RandStr.lt100); j+=1; } }
+ end
+end
diff --git a/code/string/dup-vs-unary-plus.rb b/code/string/dup-vs-unary-plus.rb
new file mode 100644
index 00000000..e31381b3
--- /dev/null
+++ b/code/string/dup-vs-unary-plus.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'benchmark/ips'
+
+if RUBY_VERSION >= '2.3.0'
+ def fast
+ +''
+ end
+
+ def slow
+ ''.dup
+ end
+
+ Benchmark.ips do |x|
+ x.report('String#+@') { fast }
+ x.report('String#dup') { slow }
+ x.compare!
+ end
+end
diff --git a/code/string/end-string-checking-match-vs-end_with.rb b/code/string/end-string-checking-match-vs-end_with.rb
new file mode 100644
index 00000000..e95d87ab
--- /dev/null
+++ b/code/string/end-string-checking-match-vs-end_with.rb
@@ -0,0 +1,22 @@
+require 'benchmark/ips'
+
+SLUG = "some_kind_of_root_url"
+
+def slower
+ SLUG =~ /_(path|url)$/
+end
+
+def slow
+ SLUG.match?(/_(path|url)$/)
+end
+
+def fast
+ SLUG.end_with?('_path', '_url')
+end
+
+Benchmark.ips do |x|
+ x.report('String#=~') { slower }
+ x.report('String#match?') { slow } if RUBY_VERSION >= "2.4.0".freeze
+ x.report('String#end_with?') { fast }
+ x.compare!
+end
diff --git a/code/string/end-string-checking-match-vs-start_with.rb b/code/string/end-string-checking-match-vs-start_with.rb
deleted file mode 100644
index c73602dd..00000000
--- a/code/string/end-string-checking-match-vs-start_with.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'benchmark/ips'
-
-SLUG = 'root_url'
-
-def slow
- SLUG =~ /_(path|url)$/
-end
-
-def fast
- SLUG.end_with?('_path', '_url')
-end
-
-Benchmark.ips do |x|
- x.report('String#=~') { slow }
- x.report('String#end_with?') { fast }
- x.compare!
-end
diff --git a/code/string/gsub-vs-sub.rb b/code/string/gsub-vs-sub.rb
index c4bb0325..1d0804dd 100644
--- a/code/string/gsub-vs-sub.rb
+++ b/code/string/gsub-vs-sub.rb
@@ -10,8 +10,15 @@ def fast
URL.sub('http://', 'https://')
end
+def fastest
+ str = URL.dup
+ str['http://'] = 'https://'
+ str
+end
+
Benchmark.ips do |x|
x.report('String#gsub') { slow }
x.report('String#sub') { fast }
+ x.report('String#dup["string"]=') { fastest }
x.compare!
end
diff --git a/code/string/gsub-vs-tr-vs-delete.rb b/code/string/gsub-vs-tr-vs-delete.rb
new file mode 100644
index 00000000..6afd0393
--- /dev/null
+++ b/code/string/gsub-vs-tr-vs-delete.rb
@@ -0,0 +1,28 @@
+require 'benchmark/ips'
+
+WORDS = 'writing fast ruby'
+SPACE = ' '
+
+def use_gsub
+ WORDS.gsub(' ', '')
+end
+
+def use_tr
+ WORDS.tr(' ', '')
+end
+
+def use_delete
+ WORDS.delete(' ')
+end
+
+def use_delete_const
+ WORDS.delete(SPACE)
+end
+
+Benchmark.ips do |x|
+ x.report('String#gsub') { use_gsub }
+ x.report('String#tr') { use_tr }
+ x.report('String#delete') { use_delete }
+ x.report('String#delete const') { use_delete_const }
+ x.compare!
+end
diff --git a/code/string/mutable_vs_immutable_strings.rb b/code/string/mutable_vs_immutable_strings.rb
new file mode 100644
index 00000000..d02a739a
--- /dev/null
+++ b/code/string/mutable_vs_immutable_strings.rb
@@ -0,0 +1,17 @@
+require "benchmark/ips"
+
+# Allocates new string over and over again
+def without_freeze
+ "To freeze or not to freeze"
+end
+
+# Keeps and reuses shared string
+def with_feeze
+ "To freeze or not to freeze".freeze
+end
+
+Benchmark.ips do |x|
+ x.report("Without Freeze") { without_freeze }
+ x.report("With Freeze") { with_feeze }
+ x.compare!
+end
diff --git a/code/string/remove-extra-spaces-or-other-chars.rb b/code/string/remove-extra-spaces-or-other-chars.rb
new file mode 100644
index 00000000..fb1349f7
--- /dev/null
+++ b/code/string/remove-extra-spaces-or-other-chars.rb
@@ -0,0 +1,24 @@
+require 'benchmark/ips'
+
+PASSAGE = <<~LIPSUM
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+LIPSUM
+
+raise unless PASSAGE.gsub(/ +/, " ") == PASSAGE.squeeze(" ")
+
+def slow
+ PASSAGE.gsub(/ +/, " ")
+end
+
+def fast
+ PASSAGE.squeeze(" ")
+end
+
+Benchmark.ips do |x|
+ x.report('String#gsub/regex+/') { slow }
+ x.report('String#squeeze') { fast }
+ x.compare!
+end
diff --git a/code/string/start-string-checking-match-vs-start_with.rb b/code/string/start-string-checking-match-vs-start_with.rb
index eb529f1b..a58eb35d 100644
--- a/code/string/start-string-checking-match-vs-start_with.rb
+++ b/code/string/start-string-checking-match-vs-start_with.rb
@@ -1,17 +1,22 @@
require 'benchmark/ips'
-SLUG = 'test_reverse_merge.rb'
+SLUG = 'test_some_kind_of_long_file_name.rb'
-def slow
+def slower
SLUG =~ /^test_/
end
+def slow
+ SLUG.match?(/^test_/)
+end
+
def fast
SLUG.start_with?('test_')
end
Benchmark.ips do |x|
- x.report('String#=~') { slow }
+ x.report('String#=~') { slower }
+ x.report('String#match?') { slow } if RUBY_VERSION >= "2.4.0".freeze
x.report('String#start_with?') { fast }
x.compare!
end
diff --git a/code/string/start_with-vs-substring-==.rb b/code/string/start_with-vs-substring-==.rb
new file mode 100755
index 00000000..32d8c180
--- /dev/null
+++ b/code/string/start_with-vs-substring-==.rb
@@ -0,0 +1,33 @@
+require "benchmark/ips"
+
+PREFIX = "_"
+STRINGS = (0..9).map{|n| "#{PREFIX if n.odd?}#{n}" }
+
+START_WITH = STRINGS.each_index.map do |i|
+ "STRINGS[#{i}].start_with?(PREFIX)"
+end.join(";")
+
+EQL_USING_LENGTH = STRINGS.each_index.map do |i|
+ # use `eql?` instead of `==` to prevent warnings
+ "STRINGS[#{i}][0, PREFIX.length].eql?(PREFIX)"
+end.join(";")
+
+RANGE = 0...PREFIX.length
+
+EQL_USING_RANGE_PREALLOC = STRINGS.each_index.map do |i|
+ # use `eql?` instead of `==` to prevent warnings
+ "STRINGS[#{i}][RANGE].eql?(PREFIX)"
+end.join(";")
+
+EQL_USING_RANGE = STRINGS.each_index.map do |i|
+ # use `eql?` instead of `==` to prevent warnings
+ "STRINGS[#{i}][0...PREFIX.length].eql?(PREFIX)"
+end.join(";")
+
+Benchmark.ips do |x|
+ x.report("String#start_with?", START_WITH)
+ x.report("String#[0, n] ==", EQL_USING_LENGTH)
+ x.report("String#[RANGE] ==", EQL_USING_RANGE_PREALLOC)
+ x.report("String#[0...n] ==", EQL_USING_RANGE)
+ x.compare!
+end
diff --git a/code/string/sub-vs-chomp-vs-delete_suffix.rb b/code/string/sub-vs-chomp-vs-delete_suffix.rb
new file mode 100644
index 00000000..6ecfcf9f
--- /dev/null
+++ b/code/string/sub-vs-chomp-vs-delete_suffix.rb
@@ -0,0 +1,22 @@
+require 'benchmark/ips'
+
+SLUG = 'YourSubclassType'
+
+def slow
+ SLUG.sub(/Type\z/, '')
+end
+
+def fast
+ SLUG.chomp('Type')
+end
+
+def faster
+ SLUG.delete_suffix('Type')
+end
+
+Benchmark.ips do |x|
+ x.report('String#sub') { slow }
+ x.report("String#chomp") { fast }
+ x.report("String#delete_suffix") { faster } if RUBY_VERSION >= '2.5.0'
+ x.compare!
+end
diff --git a/code/string/sub-vs-delete_prefix.rb b/code/string/sub-vs-delete_prefix.rb
new file mode 100644
index 00000000..19ac1382
--- /dev/null
+++ b/code/string/sub-vs-delete_prefix.rb
@@ -0,0 +1,19 @@
+require "benchmark/ips"
+
+if RUBY_VERSION >= '2.5.0'
+ STRING = 'Foo::Foo::Bar'.freeze
+
+ def fast
+ STRING.delete_prefix('Foo::')
+ end
+
+ def slow
+ STRING.sub(/\AFoo::/, '')
+ end
+
+ Benchmark.ips do |x|
+ x.report("String#delete_prefix") { fast }
+ x.report('String#sub') { slow }
+ x.compare!
+ end
+end
diff --git a/code/string/unpack1-vs-unpack[0].rb b/code/string/unpack1-vs-unpack[0].rb
new file mode 100644
index 00000000..746909a8
--- /dev/null
+++ b/code/string/unpack1-vs-unpack[0].rb
@@ -0,0 +1,19 @@
+require 'benchmark/ips'
+
+if RUBY_VERSION >= '2.4.0'
+ STRING = "foobarbaz".freeze
+
+ def fast
+ STRING.unpack1('h*')
+ end
+
+ def slow
+ STRING.unpack('h*')[0]
+ end
+
+ Benchmark.ips do |x|
+ x.report('String#unpack1') { fast }
+ x.report('String#unpack[0]') { slow }
+ x.compare!
+ end
+end
diff --git a/code/time/iso8601-vs-parse.rb b/code/time/iso8601-vs-parse.rb
new file mode 100644
index 00000000..6cf7f9f8
--- /dev/null
+++ b/code/time/iso8601-vs-parse.rb
@@ -0,0 +1,18 @@
+require 'benchmark/ips'
+require 'time'
+
+STRING = '2018-03-21T11:26:50Z'.freeze
+
+def fast
+ Time.iso8601(STRING)
+end
+
+def slow
+ Time.parse(STRING)
+end
+
+Benchmark.ips do |x|
+ x.report('Time.iso8601') { fast }
+ x.report('Time.parse') { slow }
+ x.compare!
+end