├── CC-BY-SA.png
├── .gitignore
├── code
├── array
│ ├── bsearch-vs-find.cr
│ ├── array-last-vs-index.cr
│ ├── array-first-vs-index.cr
│ ├── shuffle-first-vs-sample.cr
│ └── insert-vs-unshift.cr
├── string
│ ├── dup-vs-unary-plus.cr
│ ├── gsub-vs-tr.cr
│ ├── concatenation.cr
│ ├── compare-vs-downcase-==.cr
│ ├── sub-vs-chomp.cr
│ ├── sub-vs-lchop.cr
│ ├── ===-vs-=~-vs-match.cr
│ ├── checking-match-vs-starts_with.cr
│ ├── checking-match-vs-ends_with.cr
│ ├── gsub-vs-sub.cr
│ ├── sub-vs-gsub_with_regex.cr
│ └── remove-extra-spaces-or-other-chars.cr
├── enumerable
│ ├── reduce-proc-vs-block.cr
│ ├── each-push-vs-map.cr
│ ├── reverse-each-vs-reverse_each.cr
│ ├── select-first-vs-find.cr
│ ├── map-flatten-vs-flat_map.cr
│ ├── sort_by-first-vs-min_by.cr
│ ├── select-last-vs-reverse-find.cr
│ ├── each_with_index-vs-while-loop.cr
│ ├── sort-vs-sort_by.cr
│ └── each-vs-loop-vs-while-vs-step-vs-upto-vs-downto-vs-times.cr
├── proc-and-block
│ ├── block-vs-to_proc.cr
│ └── proc-call-vs-yield.cr
├── time
│ └── iso8601-vs-parse.cr
├── hash
│ ├── hash-key-sort_by-vs-sort.cr
│ ├── fetch-vs-fetch-with-block.cr
│ ├── keys-includes-vs-has_key.cr
│ ├── merge-vs-merge-bang.cr
│ ├── values-includes-vs-has_value.cr
│ ├── merge-bang-vs-brackets.cr
│ ├── merge-bang-vs-merge-vs-dup-merge-bang.cr
│ ├── bracket-vs-fetch-vs-dig.cr
│ ├── bracket-vs-fetch-vs-dig-nested-hash.cr
│ └── keys-each-vs-each_key.cr
└── range
│ └── covers-vs-includes.cr
├── LICENSE
└── README.md
/CC-BY-SA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/konung/fast-crystal/HEAD/CC-BY-SA.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /docs/
2 | /lib/
3 | /bin/
4 | /.shards/
5 | *.dwarf
6 |
7 | # Libraries don't need dependency lock
8 | # Dependencies will be locked in applications that use them
9 | /shard.lock
10 |
--------------------------------------------------------------------------------
/code/array/bsearch-vs-find.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (0..100_000_000).to_a
5 |
6 | Benchmark.ips do |x|
7 | x.report("find") { ARRAY.find { |number| number > 77_777_777 } }
8 | x.report("bsearch") { ARRAY.bsearch { |number| number > 77_777_777 } }
9 | end
10 |
--------------------------------------------------------------------------------
/code/string/dup-vs-unary-plus.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | def fast
5 | "crystal" + ""
6 | end
7 |
8 | def slow
9 | "crystal".dup
10 | end
11 |
12 | Benchmark.ips do |x|
13 | x.report("String#+@") { fast }
14 | x.report("String#dup") { slow }
15 | x.compare!
16 | end
17 |
--------------------------------------------------------------------------------
/code/array/array-last-vs-index.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def fast
7 | ARRAY[-1]
8 | end
9 |
10 | def slow
11 | ARRAY.last
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Array#[-1]") { fast }
16 | x.report("Array#last") { slow }
17 | end
18 |
--------------------------------------------------------------------------------
/code/array/array-first-vs-index.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def fast
7 | ARRAY[0]
8 | end
9 |
10 | def slow
11 | ARRAY.first
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Array#[0]") { fast }
16 | x.report("Array#first") { slow }
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/code/string/gsub-vs-tr.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | SLUG = "writing-fast-ruby"
5 |
6 | def slow
7 | SLUG.gsub("-", " ")
8 | end
9 |
10 | def fast
11 | SLUG.tr("-", " ")
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("String#gsub") { slow }
16 | x.report("String#tr") { fast }
17 | end
18 |
--------------------------------------------------------------------------------
/code/array/shuffle-first-vs-sample.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def slow
7 | ARRAY.shuffle.first
8 | end
9 |
10 | def fast
11 | ARRAY.sample
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Array#sample") { fast }
16 | x.report("Array#shuffle.first") { slow }
17 | end
18 |
--------------------------------------------------------------------------------
/code/enumerable/reduce-proc-vs-block.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..1000).to_a
5 |
6 | def fast
7 | ARRAY.reduce(&.+)
8 | end
9 |
10 | def slow
11 | ARRAY.reduce { |a, i| a + i }
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("reduce to_proc") { fast }
16 | x.report("reduce block") { slow }
17 | end
18 |
--------------------------------------------------------------------------------
/code/string/concatenation.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | # 2 + 1 = 3 object
5 | def slow_plus
6 | "foo" + "bar"
7 | end
8 |
9 | def fast_interpolation
10 | "#{"foo"}#{"bar"}"
11 | end
12 |
13 | Benchmark.ips do |x|
14 | x.report("String#+") { slow_plus }
15 | x.report("{\"foo\"}{\"bar\"}") { fast_interpolation }
16 | end
17 |
--------------------------------------------------------------------------------
/code/string/compare-vs-downcase-==.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | SLUG = "ABCD"
5 |
6 | def slow
7 | SLUG.downcase == "abcd"
8 | end
9 |
10 | def fast
11 | SLUG.compare("abcd", true) == 0
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("String#downcase + ==") { slow }
16 | x.report("String#compare") { fast }
17 | end
18 |
--------------------------------------------------------------------------------
/code/array/insert-vs-unshift.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | Benchmark.ips do |x|
5 | x.report("Array#unshift") do
6 | array = [] of Int32
7 | 100_000.times { |i| array.unshift(i) }
8 | end
9 |
10 | x.report("Array#insert") do
11 | array = [] of Int32
12 | 100_000.times { |i| array.insert(0, i) }
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/code/proc-and-block/block-vs-to_proc.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | RANGE = (1..100)
5 |
6 | def slow
7 | RANGE.map { |i| i.to_s }
8 | end
9 |
10 | def fast
11 | RANGE.map(&.to_s)
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Block .map{|i| i.some_method}") { slow }
16 | x.report("Shortcut .map(&.some_method)") { fast }
17 | end
18 |
--------------------------------------------------------------------------------
/code/enumerable/each-push-vs-map.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def slow
7 | array = [] of Int32
8 | ARRAY.each { |i| array.push i }
9 | end
10 |
11 | def fast
12 | ARRAY.map { |i| i }
13 | end
14 |
15 | Benchmark.ips do |x|
16 | x.report("Array#each + push") { slow }
17 | x.report("Array#map") { fast }
18 | end
19 |
--------------------------------------------------------------------------------
/code/enumerable/reverse-each-vs-reverse_each.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def slow
7 | ARRAY.reverse.each { |x| x }
8 | end
9 |
10 | def fast
11 | ARRAY.reverse_each { |x| x }
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Array#reverse.each") { slow }
16 | x.report("Array#reverse_each") { fast }
17 | end
18 |
--------------------------------------------------------------------------------
/code/enumerable/select-first-vs-find.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def slow
7 | ARRAY.select { |x| x == 15 }.first?
8 | end
9 |
10 | def fast
11 | ARRAY.find { |x| x == 15 }
12 | end
13 |
14 | Benchmark.ips(20) do |x|
15 | x.report("Enumerable#select.first") { slow }
16 | x.report("Enumerable#find") { fast }
17 | end
18 |
--------------------------------------------------------------------------------
/code/enumerable/map-flatten-vs-flat_map.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def slow_flatten
7 | ARRAY.map { |e| [e, e] }.flatten
8 | end
9 |
10 | def fast
11 | ARRAY.flat_map { |e| [e, e] }
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Array#map.flatten") { slow_flatten }
16 | x.report("Array#flat_map") { fast }
17 | end
18 |
--------------------------------------------------------------------------------
/code/enumerable/sort_by-first-vs-min_by.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def fast
7 | ARRAY.min_by { |x| x.succ }
8 | end
9 |
10 | def slow
11 | ARRAY.sort_by { |x| x.succ }.first
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Enumerable#min_by") { fast }
16 | x.report("Enumerable#sort_by...first") { slow }
17 | end
18 |
--------------------------------------------------------------------------------
/code/string/sub-vs-chomp.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | SLUG = "YourSubclassTypes"
5 |
6 | def slow
7 | SLUG.sub(/Types\z/, "")
8 | end
9 |
10 | def fast
11 | SLUG.chomp("Types")
12 | end
13 |
14 | raise Exception.new unless (fast == slow)
15 |
16 | Benchmark.ips do |x|
17 | x.report("String#sub") { slow }
18 | x.report("String#chomp") { fast }
19 | end
20 |
--------------------------------------------------------------------------------
/code/string/sub-vs-lchop.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | SLUG = "YourSubclassType"
5 |
6 | def fast
7 | SLUG.lchop("Your")
8 | end
9 |
10 | def slow
11 | SLUG.sub(/\AYour/, "")
12 | end
13 |
14 | raise Exception.new unless (fast == slow)
15 |
16 | Benchmark.ips do |x|
17 | x.report("String#lchop") { fast }
18 | x.report("String#sub") { slow }
19 | end
20 |
--------------------------------------------------------------------------------
/code/time/iso8601-vs-parse.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | STRING = "2018-03-21T11:26:50Z"
5 |
6 | def fast
7 | Time.parse_iso8601(STRING)
8 | end
9 |
10 | def slow
11 | Time.parse(STRING, "%FT%TZ", Time::Location.load("Europe/London"))
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Time.parse_iso8601") { fast }
16 | x.report("Time.parse") { slow }
17 | end
18 |
--------------------------------------------------------------------------------
/code/hash/hash-key-sort_by-vs-sort.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | RANGE = ("a".."zzz")
5 | HASH = RANGE.to_a.shuffle.zip(RANGE.to_a).to_h
6 |
7 | def fast
8 | HASH.to_a.sort_by { |k, _v| k }.to_h
9 | end
10 |
11 | def slow
12 | HASH.to_a.sort.to_h
13 | end
14 |
15 | Benchmark.ips do |x|
16 | x.report("sort_by + to_h") { fast }
17 | x.report("sort + to_h") { slow }
18 | end
19 |
--------------------------------------------------------------------------------
/code/enumerable/select-last-vs-reverse-find.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def fast
7 | ARRAY.reverse.find { |x| (x % 10).zero? }
8 | end
9 |
10 | def slow
11 | ARRAY.select { |x| (x % 10).zero? }.last?
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("Enumerable#reverse.detect") { fast }
16 | x.report("Enumerable#select.last") { slow }
17 | end
18 |
--------------------------------------------------------------------------------
/code/hash/fetch-vs-fetch-with-block.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | HASH = {:writing => :fast_ruby}
5 | DEFAULT = "fast ruby"
6 |
7 | Benchmark.ips do |x|
8 | x.report("Hash#fetch + const") { HASH.fetch(:writing, DEFAULT) }
9 | x.report("Hash#fetch + block") { HASH.fetch(:writing) { "fast ruby" } }
10 | x.report("Hash#fetch + arg") { HASH.fetch(:writing, "fast ruby") }
11 | end
12 |
--------------------------------------------------------------------------------
/code/hash/keys-includes-vs-has_key.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | RANGE = ("a".."zzz")
5 | HASH = RANGE.to_a.shuffle.zip(RANGE.to_a).to_h
6 | KEY = "zz"
7 |
8 | def key_fast
9 | HASH.has_key? KEY
10 | end
11 |
12 | def key_slow
13 | HASH.keys.includes? KEY
14 | end
15 |
16 | Benchmark.ips do |x|
17 | x.report("Hash#keys.includes?") { key_slow }
18 | x.report("Hash#has_key?") { key_fast }
19 | end
20 |
--------------------------------------------------------------------------------
/code/hash/merge-vs-merge-bang.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ENUM = (1..100)
5 |
6 | def slow
7 | ENUM.reduce({} of Int32 => Int32) do |h, e|
8 | h.merge({e => e})
9 | end
10 | end
11 |
12 | def fast
13 | ENUM.reduce({} of Int32 => Int32) do |h, e|
14 | h.merge!({e => e})
15 | end
16 | end
17 |
18 | Benchmark.ips do |x|
19 | x.report("Hash#merge") { slow }
20 | x.report("Hash#merge!") { fast }
21 | end
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ## License
2 |
3 | 
4 |
5 | This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
6 |
7 |
8 | ## Code License
9 |
10 | ### CC0 1.0 Universal
11 |
12 | To the extent possible under law, @konung has waived all copyright and related or neighboring rights to "fast-crystal".
13 |
14 | This work belongs to the community.
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/code/hash/values-includes-vs-has_value.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | RANGE = ("a".."zzz")
5 | HASH = RANGE.to_a.shuffle.zip(RANGE.to_a).to_h
6 | VALUE = "zz"
7 |
8 | def value_fast
9 | HASH.has_value? VALUE
10 | end
11 |
12 | def value_slow
13 | HASH.values.includes? VALUE
14 | end
15 |
16 | Benchmark.ips do |x|
17 | x.report("Hash#values.includes?") { value_slow }
18 | x.report("Hash#has_value?") { value_fast }
19 | end
20 |
--------------------------------------------------------------------------------
/code/string/===-vs-=~-vs-match.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | def fast
5 | return "Not found" unless "foo".match(/boo/)
6 | end
7 |
8 | def slow
9 | return "Not found" unless "foo" =~ /boo/
10 | end
11 |
12 | def slower
13 | return "Not found" unless /boo/ === "foo"
14 | end
15 |
16 | Benchmark.ips do |x|
17 | x.report("String#=~") { slow }
18 | x.report("Regexp#===") { slower }
19 | x.report("String#match") { fast }
20 | end
21 |
--------------------------------------------------------------------------------
/code/string/checking-match-vs-starts_with.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | SLUG = "test_some_kind_of_long_file_name.cr"
5 |
6 | def slower
7 | (SLUG =~ /^test_/) == 0
8 | end
9 |
10 | def slow
11 | SLUG.match(/^test_/) == 0
12 | end
13 |
14 | def fast
15 | SLUG.starts_with?("test_")
16 | end
17 |
18 | Benchmark.ips do |x|
19 | x.report("String#=~") { slower }
20 | x.report("String#match") { slow }
21 | x.report("String#start_with?") { fast }
22 | end
23 |
--------------------------------------------------------------------------------
/code/hash/merge-bang-vs-brackets.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ENUM = (1..100)
5 |
6 | def slow
7 | hsh = {} of Int32 => Int32
8 | ENUM.each_with_object(hsh) do |e, h|
9 | h.merge!({e => e})
10 | end
11 | end
12 |
13 | def fast
14 | hsh = {} of Int32 => Int32
15 | ENUM.each_with_object(hsh) do |e, h|
16 | h[e] = e
17 | end
18 | end
19 |
20 | Benchmark.ips do |x|
21 | x.report("Hash#merge!") { slow }
22 | x.report("Hash#[]=") { fast }
23 | end
24 |
--------------------------------------------------------------------------------
/code/enumerable/each_with_index-vs-while-loop.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def fast
7 | index = 0
8 | while index < ARRAY.size
9 | ARRAY[index] + index
10 | index += 1
11 | end
12 | ARRAY
13 | end
14 |
15 | def slow
16 | ARRAY.each_with_index do |number, index|
17 | number + index
18 | end
19 | end
20 |
21 | Benchmark.ips do |x|
22 | x.report("While Loop") { fast }
23 | x.report("each_with_index") { slow }
24 | end
25 |
--------------------------------------------------------------------------------
/code/string/checking-match-vs-ends_with.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | SLUG = "some_kind_of_root_url"
5 |
6 | def slower
7 | SLUG =~ /_(path|url)$/
8 | end
9 |
10 | def slow
11 | SLUG.match(/_(path|url)$/) != nil
12 | end
13 |
14 | def fast
15 | SLUG.ends_with?("_path") || SLUG.ends_with?("_url")
16 | end
17 |
18 | Benchmark.ips do |x|
19 | x.report("String#=~") { slower }
20 | x.report("String#match") { slow }
21 | x.report("String#ends_with?") { fast }
22 | end
23 |
--------------------------------------------------------------------------------
/code/proc-and-block/proc-call-vs-yield.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | def slow(&block)
5 | block.call
6 | end
7 |
8 | def slow2(&block)
9 | yield
10 | end
11 |
12 | def slow3(&block)
13 |
14 | end
15 |
16 | def fast
17 | yield
18 | end
19 |
20 | Benchmark.ips do |x|
21 | x.report("block.call") { slow { 1 + 1 } }
22 | x.report("block + yield") { slow2 { 1 + 1 } }
23 | x.report("unused block") { slow3 { 1 + 1 } }
24 | x.report("yield") { fast { 1 + 1 } }
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/code/range/covers-vs-includes.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 |
5 | BEGIN_OF_JULY = Time.new(2015, 7, 1)
6 | END_OF_JULY = Time.new(2015, 7, 31)
7 | DAY_IN_JULY = Time.new(2015, 7, 15)
8 |
9 | Benchmark.ips do |x|
10 | x.report("range#covers?") { (BEGIN_OF_JULY..END_OF_JULY).covers? DAY_IN_JULY }
11 | x.report("range#includes?") { (BEGIN_OF_JULY..END_OF_JULY).includes? DAY_IN_JULY }
12 | x.report("plain compare") { BEGIN_OF_JULY < DAY_IN_JULY && DAY_IN_JULY < END_OF_JULY }
13 | end
14 |
15 |
--------------------------------------------------------------------------------
/code/string/gsub-vs-sub.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | URL = "http://www.thelongestlistofthelongeststuffatthelongestdomainnameatlonglast.com/wearejustdoingthistobestupidnowsincethiscangoonforeverandeverandeverbutitstilllookskindaneatinthebrowsereventhoughitsabigwasteoftimeandenergyandhasnorealpointbutwehadtodoitanyways.html"
5 |
6 | def slow
7 | URL.gsub("http://", "https://")
8 | end
9 |
10 | def fast
11 | URL.sub("http://", "https://")
12 | end
13 |
14 | Benchmark.ips do |x|
15 | x.report("String#gsub") { slow }
16 | x.report("String#sub") { fast }
17 | end
18 |
--------------------------------------------------------------------------------
/code/enumerable/sort-vs-sort_by.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | struct User
5 | property name
6 |
7 | def initialize(@name : String)
8 | end
9 | end
10 |
11 | ARRAY = Array.new(100) do
12 | User.new(sprintf "%010d", rand(1_000_000_000))
13 | end
14 |
15 | def fastest
16 | ARRAY.sort_by(&.name)
17 | end
18 |
19 | def faster
20 | ARRAY.sort_by { |element| element.name }
21 | end
22 |
23 | def slow
24 | ARRAY.sort { |a, b| a.name <=> b.name }
25 | end
26 |
27 | Benchmark.ips do |x|
28 | x.report("Enumerable#sort_by (Symbol#to_proc)") { fastest }
29 | x.report("Enumerable#sort_by") { faster }
30 | x.report("Enumerable#sort") { slow }
31 | end
32 |
--------------------------------------------------------------------------------
/code/string/sub-vs-gsub_with_regex.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | URL = "http://www.thelongestlistofthelongeststuffatthelongestdomainnameatlonglast.com/wearejustdoingthistobestupidnowsincethiscangoonforeverandeverandeverbutitstilllookskindaneatinthebrowsereventhoughitsabigwasteoftimeandenergyandhasnorealpointbutwehadtodoitanyways.html"
5 |
6 | def slow_1
7 | s = URL.dup
8 | s.sub "http://", ""
9 | end
10 |
11 | def slow_2
12 | s = URL.dup
13 | s.gsub "http://", ""
14 | end
15 |
16 | def slow_3
17 | s = URL.dup
18 | s.sub %r{http://}, ""
19 | end
20 |
21 | def slow_4
22 | s = URL.dup
23 | s.gsub %r{http://}, ""
24 | end
25 |
26 | Benchmark.ips do |x|
27 | x.report("String#sub(string)") { slow_1 }
28 | x.report("String#gsub(string)") { slow_2 }
29 | x.report("String#sub/regexp/") { slow_3 }
30 | x.report("String#gsub/regexp/") { slow_4 }
31 | end
32 |
--------------------------------------------------------------------------------
/code/hash/merge-bang-vs-merge-vs-dup-merge-bang.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ENUM = (1..100).to_a
5 |
6 | alias Hashie = Hash(String | Symbol, String | Int32)
7 | ORIGINAL_HASH = Hashie{"foo" => "bar"}
8 |
9 | def fast
10 | ENUM.reduce([] of Hashie) do |accumulator, element|
11 | accumulator << (Hashie{:bar => element}.merge!(ORIGINAL_HASH) { |_key, left, _right| left })
12 | end
13 | end
14 |
15 | def slow
16 | ENUM.reduce([] of Hashie) do |accumulator, element|
17 | accumulator << ORIGINAL_HASH.merge(Hashie{:bar => element})
18 | end
19 | end
20 |
21 | def slow_dup
22 | ENUM.reduce([] of Hashie) do |accumulator, element|
23 | accumulator << ORIGINAL_HASH.dup.merge!(Hashie{:bar => element})
24 | end
25 | end
26 |
27 | Benchmark.ips do |x|
28 | x.report("{}#merge!(Hash) do end") { fast }
29 | x.report("Hash#merge({})") { slow }
30 | x.report("Hash#dup#merge!({})") { slow_dup }
31 | end
32 |
--------------------------------------------------------------------------------
/code/string/remove-extra-spaces-or-other-chars.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | PASSAGE = "
5 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
6 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
7 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
8 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
9 | "
10 |
11 | raise ArgumentError.new unless PASSAGE.gsub(/ +/, " ") == PASSAGE.squeeze(" ")
12 |
13 | def slow
14 | PASSAGE.gsub(/ +/, " ")
15 | end
16 |
17 | def fast
18 | PASSAGE.squeeze(" ")
19 | end
20 |
21 | Benchmark.ips do |x|
22 | x.report("String#gsub/regex+/") { slow }
23 | x.report("String#squeeze") { fast }
24 | end
25 |
--------------------------------------------------------------------------------
/code/hash/bracket-vs-fetch-vs-dig.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | HASH_SYMBOLS = {:cool => "ruby", :fast => "crystal"}
5 | HASH_STRINGS = {"cool" => "ruby", "fast" => "crystal"}
6 |
7 | def bracket_with_symbol
8 | HASH_SYMBOLS[:fast]
9 | end
10 |
11 | def fetch_with_symbol
12 | HASH_SYMBOLS.fetch(:fast, nil)
13 | end
14 |
15 | def dig_with_symbol
16 | HASH_SYMBOLS.dig(:fast)
17 | end
18 |
19 | def bracket_with_string
20 | HASH_STRINGS["fast"]
21 | end
22 |
23 | def fetch_with_string
24 | HASH_STRINGS.fetch("fast", nil)
25 | end
26 |
27 | def dig_with_string
28 | HASH_STRINGS.dig("fast")
29 | end
30 |
31 | Benchmark.ips do |x|
32 | x.report("Hash#[], symbol") { bracket_with_symbol }
33 | x.report("Hash#fetch, symbol") { fetch_with_symbol }
34 | x.report("Hash#dig, symbol") { bracket_with_symbol }
35 | x.report("Hash#[], string") { bracket_with_string }
36 | x.report("Hash#fetch, string") { fetch_with_string }
37 | x.report("Hash#dig, string") { bracket_with_string }
38 | end
39 |
--------------------------------------------------------------------------------
/code/hash/bracket-vs-fetch-vs-dig-nested-hash.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | h = {:a => {:b => {:c => {:d => {:e => "foo"}}}}}
5 |
6 | alias HS = Hash(Symbol, String)
7 |
8 | Benchmark.ips do |x|
9 | x.report "Hash#dig?" do
10 | h.dig?(:a, :b, :c, :d, :e)
11 | end
12 |
13 | x.report "Hash#dig" do
14 | h.dig(:a, :b, :c, :d, :e)
15 | end
16 |
17 | x.report "Hash#[]" do
18 | h[:a][:b][:c][:d][:e]
19 | end
20 |
21 | x.report "Hash#[] &&" do
22 | h[:a] && h[:a][:b] && h[:a][:b][:c] && h[:a][:b][:c][:d] && h[:a][:b][:c][:d][:e]
23 | end
24 |
25 | # Both of these approaches break on 3rd level of nesting with error > no overload matches 'String#[]' with type Symbol
26 | # or undefined method 'fetch' for String
27 |
28 | # x.report "Hash#[] ||" do
29 | # (((((((h[:a] || HS.new)[:b]) || HS.new)[:c]) || HS.new)[:d]) || HS.new)[:e]
30 | # end
31 |
32 | # x.report "Hash#fetch fallback" do
33 | # h.fetch(:a, HS.new).fetch(:b, HS.new).fetch(:c, HS.new).fetch(:d, HS.new).fetch(:e, nil)
34 | # end
35 | end
36 |
--------------------------------------------------------------------------------
/code/enumerable/each-vs-loop-vs-while-vs-step-vs-upto-vs-downto-vs-times.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | ARRAY = (1..100).to_a
5 |
6 | def using_step
7 | 0.step(by: 1) do |i|
8 | break unless ARRAY[i]?
9 | ARRAY[i] ** 2
10 | end
11 | end
12 |
13 | def using_upto
14 | 0.upto(ARRAY.size - 1) do |i|
15 | ARRAY[i] ** 2
16 | end
17 | end
18 |
19 | def using_downto
20 | ARRAY.size.downto(1) do |i|
21 | ARRAY[ARRAY.size - i] ** 2
22 | end
23 | end
24 |
25 | def using_times
26 | ARRAY.size.times do |i|
27 | ARRAY[i] ** 2
28 | end
29 | end
30 |
31 | def using_each
32 | ARRAY.each do |number|
33 | number ** 2
34 | end
35 | end
36 |
37 | def using_while
38 | i = 0
39 | while i < ARRAY.size
40 | ARRAY[i] ** 2
41 |
42 | i += 1
43 | end
44 | end
45 |
46 | def using_loop
47 | i = 0
48 | loop do
49 | break unless ARRAY[i]?
50 | ARRAY[i] ** 2
51 | i += 1
52 | end
53 | end
54 |
55 | Benchmark.ips do |x|
56 | x.report("#step") { using_step }
57 | x.report("#upto") { using_upto }
58 | x.report("#downto") { using_downto }
59 | x.report("#times") { using_times }
60 | x.report("#each") { using_each }
61 | x.report("while") { using_while }
62 | x.report("loop") { using_loop }
63 | end
64 |
--------------------------------------------------------------------------------
/code/hash/keys-each-vs-each_key.cr:
--------------------------------------------------------------------------------
1 | require "benchmark"
2 | puts Crystal::DESCRIPTION
3 |
4 | HASH = {
5 | "provider" => "facebook",
6 | "uid" => "1234567",
7 | "info" => {
8 | "nickname" => "jbloggs",
9 | "email" => "joe@bloggs.com",
10 | "name" => "Joe Bloggs",
11 | "first_name" => "Joe",
12 | "last_name" => "Bloggs",
13 | "image" => "http://graph.facebook.com/1234567/picture?type=square",
14 | "urls" => {"Facebook" => "http://www.facebook.com/jbloggs"},
15 | "location" => "Palo Alto, California",
16 | "verified" => true,
17 | },
18 | "credentials" => {
19 | "token" => "ABCDEF...",
20 | "expires_at" => 1321747205,
21 | "expires" => true,
22 | },
23 | "extra" => {
24 | "raw_info" => {
25 | "id" => "1234567",
26 | "name" => "Joe Bloggs",
27 | "first_name" => "Joe",
28 | "last_name" => "Bloggs",
29 | "link" => "http://www.facebook.com/jbloggs",
30 | "username" => "jbloggs",
31 | "location" => {"id" => "123456789", "name" => "Palo Alto, California"},
32 | "gender" => "male",
33 | "email" => "joe@bloggs.com",
34 | "timezone" => -8,
35 | "locale" => "en_US",
36 | "verified" => true,
37 | "updated_time" => "2011-11-11T06:21:03+0000",
38 | },
39 | },
40 | }
41 |
42 | def slow
43 | HASH.keys.each(&.chars)
44 | end
45 |
46 | def fast
47 | HASH.each_key(&.chars)
48 | end
49 |
50 | Benchmark.ips do |x|
51 | x.report("Hash#keys.each") { slow }
52 | x.report("Hash#each_key") { fast }
53 | end
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fast-crystal
2 | Benchmarks of common idioms in Crystal, to help write more performant code.
3 |
4 | This project is a port to Crystal of [fast-ruby](https://github.com/JuanitoFatas/fast-ruby) project by [JuanitoFatas](https://github.com/JuanitoFatas).
5 |
6 | I did this while learning Crystal, as an exercise to dive deeper into various aspects of the superb Crystal language. I also added some Crystal-specific benchmarks, such as comparing NamedTuple vs Hash. ( Spoiler: NamedTuples are much faster!), and removed some that don't make sense for Crystal (there is only Array#size in Crystal, no Array#length and Array#count is provided by Enumerable).
7 |
8 | This is also a useful tool for those coming to Crystal from Ruby and other dynamic languages. Some of the assumptions I had about certain idioms in Ruby, are no longer true and maybe reverse in Crystal. For example in Crystal `Array#find` is faster on a sorted Array than `Array#bsearch`, but in Ruby it's the [reverse](https://github.com/JuanitoFatas/fast-ruby#array)
9 |
10 | I'm planning to add some benchmarks for Sets, Tuples & NamedTuples as well as showcase some usa-cases vs Arrays & Hashes. If you have any intersting benchmarks, idioms, tips - please submit them!
11 |
12 | --------------------------------
13 |
14 | ## How to contribute new benchmarks
15 | * Fork & clone .
16 | * Add a benchmark.
17 | * Update README.md with the results.
18 | * Make a Pull Request with a short explanation / rationale.
19 | * Kick-back and have a drink.
20 | * If you are not sure if the benchmark is warranted, open an issue - let's discuss it.
21 |
22 | ### Template for the benchmark
23 |
24 | ```crystal
25 | require "benchmark"
26 | puts Crystal::DESCRIPTION # Always include this, so that we know which OS & version of Crystal, you used.
27 |
28 | def fast
29 | end
30 |
31 | def slow
32 | end
33 |
34 | Benchmark.ips do |x|
35 | x.report("fast code description") { fast }
36 | x.report("slow code description") { slow }
37 | end
38 | ```
39 |
40 | When you run your code don't forget to use `--release` flag, or you are not going to get accurate benchmarks (you don't need to build first), i.e.:
41 |
42 | ```shell
43 | crystal code/array/array-first-vs-index.cr --release
44 | ```
45 |
46 | Copy and paste output from console into README.md under appropriate section.
47 |
48 | --------------------------------
49 |
50 | ## Results
51 |
52 | Idioms
53 | ------
54 |
55 | ### Index
56 |
57 |
58 | - [Array](#array)
59 | - [Range](#range)
60 | - [Time](#time)
61 | - [Proc & Block](#proc--block)
62 | - [String](#string)
63 | - [Hash](#hash)
64 | - [Enumerable](#enumerable)
65 |
66 |
67 | --------------------------------
68 |
69 | ### Array
70 |
71 | ##### `Array#[](0)` vs `Array#first` [code](code/array/array-first-vs-index.cr)
72 |
73 | ```
74 | crystal array-first-vs-index.cr --release
75 | Crystal 0.27.2 (2019-02-05)
76 |
77 | LLVM: 6.0.1
78 | Default target: x86_64-apple-macosx
79 |
80 | Array#[0] 405.11M ( 2.47ns) (± 3.92%) 0 B/op fastest
81 | Array#first 358.67M ( 2.79ns) (± 3.73%) 0 B/op 1.13× slower
82 |
83 | ```
84 |
85 | ##### `Array#[](-1)` vs `Array#last` [code](code/array/array-last-vs-index.cr)
86 |
87 | ```
88 | Crystal 0.27.2 (2019-02-05)
89 |
90 | LLVM: 6.0.1
91 | Default target: x86_64-apple-macosx
92 |
93 | Array#[-1] 404.99M ( 2.47ns) (± 3.10%) 0 B/op fastest
94 | Array#last 358.27M ( 2.79ns) (± 4.23%) 0 B/op 1.13× slower
95 |
96 | ```
97 |
98 | ##### `Array#bsearch` vs `Array#find` [code](code/array/bsearch-vs-find.cr)
99 |
100 | **WARNING:** `bsearch` ONLY works on *sorted array*. `bsearch` is a bit faster on smaller Arrays, but still lags behind.
101 |
102 | ```
103 | crystal code/array/bsearch-vs-find.cr --release
104 | Crystal 0.27.2 (2019-02-05)
105 |
106 | LLVM: 6.0.1
107 | Default target: x86_64-apple-macosx
108 |
109 | find 396.3M ( 2.52ns) (± 3.52%) 0 B/op fastest
110 | bsearch 12.11M ( 82.61ns) (± 2.32%) 0 B/op 32.74× slower
111 | ```
112 |
113 |
114 |
115 | ##### `Array#shuffle.first` vs `Array#sample` [code](code/array/shuffle-first-vs-sample.cr)
116 |
117 | > `Array#shuffle` allocates an extra array.
118 | > `Array#sample` indexes into the array without allocating an extra array.
119 |
120 | ```
121 | crystal code/array/shuffle-first-vs-sample.cr --release
122 | Crystal 0.27.2 (2019-02-05)
123 |
124 | LLVM: 6.0.1
125 | Default target: x86_64-apple-macosx
126 |
127 | Array#sample 76.52M ( 13.07ns) (± 3.39%) 0 B/op fastest
128 | Array#shuffle.first 535.8k ( 1.87µs) (± 2.43%) 832 B/op 142.81× slower
129 | ```
130 |
131 |
132 |
133 | ##### `Array#insert` vs `Array#unshift` [code](code/array/insert-vs-unshift.cr)
134 |
135 | ```
136 | crystal code/array/insert-vs-unshift.cr --release
137 | Crystal 0.27.2 (2019-02-05)
138 |
139 | LLVM: 6.0.1
140 | Default target: x86_64-apple-macosx
141 |
142 | Array#unshift 1.35 (742.07ms) (± 1.42%) 1573696 B/op fastest
143 | Array#insert 1.34 (746.59ms) (± 1.20%) 1574201 B/op 1.01× slower
144 | ```
145 |
146 | --------------------------------
147 |
148 |
149 | ### Range
150 |
151 | #### `covers?` vs `includes?` vs `plain comparison` [code](code/range/covers-vs-includes.cr)
152 |
153 | `covers?` is essentially an alias for `includes?`, so unlike in Ruby the performance is identical, however they maybe be faster than using `>` and `<` depending on the type of data you are dealing with. For Int32 there is no difference, but for Time(Dates) `includes?` is 1.5x faster.
154 |
155 | ```
156 | crystal code/range/cover-vs-include.cr --release
157 | Crystal 0.27.2 (2019-02-05)
158 |
159 | LLVM: 6.0.1
160 | Default target: x86_64-apple-macosx
161 |
162 | range#covers? 461.38M ( 2.17ns) (± 4.44%) 0 B/op 1.00× slower
163 | range#includes? 463.13M ( 2.16ns) (± 3.97%) 0 B/op fastest
164 | plain compare 292.48M ( 3.42ns) (± 4.50%) 0 B/op 1.58× slowe
165 | ```
166 |
167 | --------------------------------
168 |
169 |
170 | ### Time (Date)
171 |
172 | ##### `Time.iso8601` vs `Time.parse` [code](code/time/iso8601-vs-parse.cr)
173 |
174 | If you know incoming datetime format it's better to use specific function. Unlike in Ruby there is no separate basic type - Date & Time. Everything is just Time. So there is no reason for a separate "Date" benchmark
175 |
176 | ```
177 | crystal code/time/iso8601-vs-parse.cr --release
178 | Crystal 0.27.2 (2019-02-05)
179 |
180 | LLVM: 6.0.1
181 | Default target: x86_64-apple-macosx
182 |
183 | Time.parse_iso8601 9.12M (109.63ns) (± 1.95%) 0 B/op fastest
184 | Time.parse 49.88k ( 20.05µs) (± 2.94%) 360 B/op 182.88× slower
185 | ```
186 |
187 |
188 |
189 |
190 |
191 | --------------------------------
192 |
193 |
194 | ### Proc & Block
195 |
196 | ##### Block vs `Symbol#to_proc` [code](code/proc-and-block/block-vs-to_proc.cr)
197 |
198 | There is a small performance penalty in Ruby for using `&:shortcut` , but not in crystal, so use away!
199 |
200 | ```
201 | crystal code/proc-and-block/block-vs-to_proc.cr --release
202 | Crystal 0.27.2 (2019-02-05)
203 |
204 | LLVM: 6.0.1
205 | Default target: x86_64-apple-macosx
206 | Block 283.4k ( 3.53µs) (± 3.31%) 2656 B/op fastest
207 | Symbol#to_proc 281.9k ( 3.55µs) (± 3.10%) 2656 B/op 1.01× slower
208 | ```
209 |
210 | ##### `Proc#call` and block arguments vs `yield`[code](code/proc-and-block/proc-call-vs-yield.cr)
211 |
212 | In Ruby, method with `yield` can be much faster, but in Crystal, it's almost identical to it's counterparts.
213 | This benchmark has more context in Ruby, because block arguments were passed differently in different versions, incurring heap allocation and implementing lazy Proc allocation and conversion. This benchmark was ported to Crystal, just to see if there is any penalty (none!) and to see how it works in Crystal. Pay attention to how what you do with block arguments and methods.
214 |
215 | ```crystal
216 |
217 | def slow(&block)
218 | block.call
219 | end
220 |
221 | slow {1+1}
222 | # In Crystal returns
223 | nil
224 |
225 | # In Ruby returns
226 | 2
227 |
228 | ```
229 |
230 |
231 | ```
232 | Crystal 0.27.2 (2019-02-05)
233 |
234 | LLVM: 6.0.1
235 | Default target: x86_64-apple-macosx
236 |
237 | block.call 531.71M ( 1.88ns) (± 5.89%) 0 B/op 1.00× slower
238 | block + yield 518.12M ( 1.93ns) (± 7.24%) 0 B/op 1.03× slower
239 | unused block 533.64M ( 1.87ns) (± 5.46%) 0 B/op fastest
240 | yield 525.73M ( 1.9ns) (± 6.76%) 0 B/op 1.02× slower
241 | ```
242 |
243 |
244 |
245 | --------------------------------
246 |
247 | ### String
248 |
249 | ##### `String#dup` vs `String#+` [code](code/string/dup-vs-unary-plus.cr)
250 |
251 | In Crystal, dup is much more effecient at creating shallow copies. No reason to use `"string" + ""` trick anymore ( if you did before)
252 |
253 | ```
254 | crystal code/string/dup-vs-unary-plus.cr --release
255 | Crystal 0.27.2 (2019-02-05)
256 |
257 | LLVM: 6.0.1
258 | Default target: x86_64-apple-macosx
259 |
260 | String#+@ 471.96M ( 2.12ns) (± 9.13%) 0 B/op 1.07× slower
261 | String#dup 502.68M ( 1.99ns) (± 5.32%) 0 B/op fastest
262 | ```
263 |
264 | ##### `String#compare` vs `String#downcase + ==` [code](code/string/casecmp-vs-downcase-==.cr)
265 |
266 | ```
267 | crystal code/string/compare-vs-downcase-==.cr --release
268 | Crystal 0.27.2 (2019-02-05)
269 |
270 | LLVM: 6.0.1
271 | Default target: x86_64-apple-macosx
272 |
273 | String#downcase + == 26.74M ( 37.4ns) (± 9.13%) 32 B/op fastest
274 | String#compare 21.83M ( 45.8ns) (± 3.37%) 0 B/op 1.22× slower
275 | ```
276 |
277 | ##### String Concatenation [code](code/string/concatenation.cr)
278 | In Crystal there is no `<<`, `String#concat` or `String#append`. You really only have 2 choices and one is almost 20x faster than the other. So it's not even really a choice: use interpolation whenever possible.
279 |
280 | ```
281 | Crystal 0.27.2 (2019-02-05)
282 |
283 | LLVM: 6.0.1
284 | Default target: x86_64-apple-macosx
285 |
286 | String#+ 27.71M ( 36.09ns) (± 4.35%) 32 B/op 19.28× slower
287 | {"foo"}{"bar"} 534.16M ( 1.87ns) (± 4.21%) 0 B/op fastest
288 | ```
289 |
290 | ##### `String#match` vs `String.=~` vs `String#starts_with?`/`String#ends_with?` [code (start)](code/string/checking-match-vs-starts_with.cr) [code (end)](code/string/checking-match-vs-ends_with.cr)
291 |
292 | If you have limited number of possibilities to match against, prefer to use starts_with/ends_with whenever possible, because it's orders of magnitude faster.
293 |
294 | ```
295 | crystal code/string/end-string-checking-match-vs-end_with.cr --release
296 | \Crystal 0.27.2 (2019-02-05)
297 |
298 | LLVM: 6.0.1
299 | Default target: x86_64-apple-macosx
300 |
301 | String#=~ 2.93M (341.26ns) (± 5.67%) 32 B/op 133.53× slower
302 | String#match 3.07M (325.78ns) (± 2.29%) 32 B/op 127.47× slower
303 | String#ends_with? 391.28M ( 2.56ns) (± 3.56%) 0 B/op fastest
304 | ```
305 |
306 |
307 | ##### `Regexp#===` vs `String#match` vs `String#=~` [code ](code/string/===-vs-=~-vs-match.cr)
308 |
309 | While there is speed advantage to using `=~` in Ruby, there is no difference in Crystal in equivalent situations. Keep in mind that `===` returns `true/false` while `=~` returns start position of the match and `.match` returns `MatchData` object which ok if you are using to check for `falsy/thruthy` type values. Also keep in mind that these expressions are not 100% equivalent. ie.:
310 |
311 | ```crystal
312 | "boo".match(/boo/)` #=> truthy
313 | /boo/ === "boo" #=> true
314 | "book".match(/boo/) #=> truthy
315 | /book/ === "boo" #=> false!!! So be very careful!
316 | ```
317 |
318 | ```
319 | crystal code/string/===-vs-=\~-vs-match.cr --release
320 | Crystal 0.27.2 (2019-02-05)
321 |
322 | LLVM: 6.0.1
323 | Default target: x86_64-apple-macosx
324 |
325 | String#=~ 13.76M ( 72.66ns) (± 1.00%) 16 B/op 1.00× slower
326 | Regexp#=== 13.81M ( 72.4ns) (± 2.75%) 16 B/op fastest
327 | String#match 13.77M ( 72.63ns) (± 2.24%) 16 B/op 1.00× slower
328 | ```
329 |
330 |
331 | ##### `String#gsub` vs `String#sub` [code](code/string/gsub-vs-sub.cr)
332 |
333 | ```
334 | Crystal 0.27.2 (2019-02-05)
335 |
336 | LLVM: 6.0.1
337 | Default target: x86_64-apple-macosx
338 |
339 | String#sub 2.21M (451.77ns) (± 4.22%) 1249 B/op fastest
340 | String#gsub 1.15M (869.95ns) (± 2.92%) 1249 B/op 1.93× slower
341 | ```
342 |
343 | ##### `String#gsub` vs `String#tr` [code](code/string/gsub-vs-tr.cr)
344 |
345 | In Ruby .tr is a few times faster, but in Crystal you are slightly faster using gsub.
346 |
347 | ```
348 | Crystal 0.27.2 (2019-02-05)
349 |
350 | LLVM: 6.0.1
351 | Default target: x86_64-apple-macosx
352 |
353 | String#gsub 24.88M ( 40.19ns) (± 2.33%) 32 B/op fastest
354 | String#tr 22.68M ( 44.09ns) (± 2.88%) 32 B/op 1.10× slower
355 | ```
356 |
357 |
358 | ##### `String#sub!` vs `String#gsub!` vs `String#[]=` [code](code/string/sub-vs-gsub_with_regex.cr)
359 |
360 | Whenever possible `sub` will provide fastest performance. (Keep in mind unlike Ruby, Crystal has IMMUTABLE strings, so string[2] = "Ruby", doesn't work!)
361 |
362 | ```
363 | crystal code/string/sub-vs-gsub_with_regex.cr --release
364 | Crystal 0.27.2 (2019-02-05)
365 |
366 | LLVM: 6.0.1
367 | Default target: x86_64-apple-macosx
368 |
369 | String#sub(string) 2.66M (375.53ns) (± 6.95%) 384 B/op fastest
370 | String#gsub(string) 1.22M (817.47ns) (± 5.45%) 384 B/op 2.18× slower
371 | String#sub/regexp/ 1.9M (526.88ns) (± 8.28%) 432 B/op 1.40× slower
372 | String#gsub/regexp/ 1.13M (884.29ns) (± 6.07%) 449 B/op 2.35× slower
373 |
374 | ```
375 |
376 | ##### `String#sub` vs `String#lchop` [code](code/string/sub-vs-lchop.cr)
377 |
378 | Keep in mind that String#delete is faster than Sting#sub, but it's greedy! ( Thus remove from this test)
379 |
380 | ```
381 | crystal code/string/sub-vs-lchop.cr --release
382 | Crystal 0.27.2 (2019-02-05)
383 |
384 | LLVM: 6.0.1
385 | Default target: x86_64-apple-macosx
386 | String#lchop 29.8M ( 33.56ns) (± 9.20%) 32 B/op fastest
387 | String#sub 3.2M (312.77ns) (± 5.80%) 176 B/op 9.32× slower
388 | ```
389 |
390 | ##### `String#sub` vs `String#chomp` [code](code/string/sub-vs-chomp-vs-delete.cr)
391 |
392 |
393 | ```
394 | crystal code/string/sub-vs-chomp.cr --release
395 | Crystal 0.27.2 (2019-02-05)
396 |
397 | LLVM: 6.0.1
398 | Default target: x86_64-apple-macosx
399 |
400 | String#sub 3.16M ( 316.3ns) (± 2.58%) 176 B/op 8.60× slower
401 | String#chomp 27.2M ( 36.76ns) (± 5.01%) 32 B/op fastest
402 | ```
403 |
404 |
405 |
406 | ##### Remove extra spaces (or other contiguous characters) [code](code/string/remove-extra-spaces-or-other-chars.cr)
407 |
408 | The code is tested against contiguous spaces but should work for other chars too.
409 |
410 | ```
411 | crystal code/string/remove-extra-spaces-or-other-chars.cr --release
412 | Crystal 0.27.2 (2019-02-05)
413 |
414 | LLVM: 6.0.1
415 | Default target: x86_64-apple-macosx
416 |
417 | String#gsub/regex+/ 87.68k ( 11.41µs) (± 1.44%) 2976 B/op 1.55× slower
418 | String#squeeze 135.8k ( 7.36µs) (± 1.26%) 608 B/op fastest
419 | ```
420 |
421 |
422 | --------------------------------
423 |
424 | ### Hash
425 |
426 | ##### `Hash#[]` vs `Hash#fetch` vs `Hash#dig` [code](code/hash/bracket-vs-fetch-vs-dig.cr)
427 |
428 | Using symbols as keys is generally faster, and using `.dig` while slightly slower, is definitely more convenient (then `fetch`) and safer(!), especially on a deeply nested hash.
429 | Using Union type for keys `Hash(String | Symbol, String` slows down access on hash. Use Hash#dig?(:a, :b, :c) if you are not sure if :b or :c are "diggable"
430 |
431 | ```
432 | crystal code/hash/bracket-vs-fetch-vs-dig.cr --release
433 | Crystal 0.27.2 (2019-02-05)
434 |
435 | LLVM: 6.0.1
436 | Default target: x86_64-apple-macosx
437 | Hash#[], symbol 63.59M ( 15.73ns) (± 4.75%) 0 B/op 1.10× slower
438 | Hash#fetch, symbol 69.67M ( 14.35ns) (± 2.91%) 0 B/op fastest
439 | Hash#dig, symbol 66.98M ( 14.93ns) (± 2.39%) 0 B/op 1.04× slower
440 | Hash#[], string 56.32M ( 17.75ns) (± 2.92%) 0 B/op 1.24× slower
441 | Hash#fetch, string 59.74M ( 16.74ns) (± 2.93%) 0 B/op 1.17× slower
442 | Hash#dig, string 56.82M ( 17.6ns) (± 2.68%) 0 B/op 1.23× slower
443 | ```
444 |
445 | ##### `Hash#dig` vs `Hash#[]` vs `Hash#fetch` [code](code/hash/bracket-vs-fetch-vs-dig-nested-hash.cr)
446 |
447 | `fetch` seems to break on deeply nested hash. Using `dig?` is the preferred method as the one being safer
448 |
449 | ```
450 | crystal code/hash/bracket-vs-fetch-vs-dig-nested-hash.cr --release
451 | Crystal 0.27.2 (2019-02-05)
452 |
453 | LLVM: 6.0.1
454 | Default target: x86_64-apple-macosx
455 | Hash#dig? 12.78M ( 78.23ns) (± 1.99%) 0 B/op 1.01× slower
456 | Hash#dig 12.72M ( 78.64ns) (± 2.79%) 0 B/op 1.01× slower
457 | Hash#[] 12.85M ( 77.81ns) (± 1.95%) 0 B/op fastest
458 | Hash#[] && 4.54M (220.02ns) (± 2.08%) 0 B/op 2.83× slower
459 | ```
460 |
461 |
462 | ##### `Hash#fetch` with argument vs `Hash#fetch` + block [code](code/hash/fetch-vs-fetch-with-block.cr)
463 |
464 | There is no difference in Crystal, while in Ruby fetch with non-constant argument is 30% slower
465 |
466 | ```
467 | crystal code/hash/fetch-vs-fetch-with-block.cr --release
468 | Crystal 0.27.2 (2019-02-05)
469 |
470 | LLVM: 6.0.1
471 | Default target: x86_64-apple-macosx
472 | Hash#fetch + const 73.21M ( 13.66ns) (± 3.06%) 0 B/op fastest
473 | Hash#fetch + block 72.93M ( 13.71ns) (± 2.67%) 0 B/op 1.00× slower
474 | Hash#fetch + arg 72.17M ( 13.86ns) (± 3.11%) 0 B/op 1.01× slower
475 | ```
476 |
477 | ##### `Hash#each_key` instead of `Hash#keys.each` [code](code/hash/keys-each-vs-each_key.cr)
478 |
479 |
480 | ```
481 | crystal code/hash/keys-each-vs-each_key.cr --release
482 | Crystal 0.27.2 (2019-02-05)
483 |
484 | LLVM: 6.0.1
485 | Default target: x86_64-apple-macosx
486 |
487 | Hash#keys.each 1.79M (559.32ns) (± 4.86%) 416 B/op 1.07× slower
488 | Hash#each_key 1.92M (521.93ns) (± 7.75%) 338 B/op fastest
489 | ```
490 |
491 | #### `Hash#key?` instead of `Hash#keys.include?` [code](code/hash/keys-includes-vs-has_key.cr)
492 |
493 | > `Hash#keys.include?` allocates an array of keys and performs an O(n) search;
494 | > `Hash#has_key?` performs an O(1) hash lookup without allocating a new array.
495 |
496 | ```
497 | crystal code/hash/keys-includes-vs-has_key.cr --release
498 | Crystal 0.27.2 (2019-02-05)
499 |
500 | LLVM: 6.0.1
501 | Default target: x86_64-apple-macosx
502 | Hash#keys.includes? 5.87k ( 170.3µs) (± 3.24%) 146272 B/op 6472.68× slower
503 | Hash#has_key? 38.01M ( 26.31ns) (± 4.81%) 0 B/op fastest
504 | ```
505 |
506 | ##### `Hash#value?` instead of `Hash#values.include?` [code](code/hash/values-include-vs-value.cr)
507 |
508 | > `Hash#values.includes?` allocates an array of values and performs an O(n) search;
509 | > `Hash#has_value?` performs an O(n) search without allocating a new array.
510 |
511 | ```
512 | crystal code/hash/values-includes-vs-has_value.cr --release
513 | Crystal 0.27.2 (2019-02-05)
514 |
515 | LLVM: 6.0.1
516 | Default target: x86_64-apple-macosx
517 | Hash#values.includes? 5.73k (174.43µs) (± 3.79%) 146272 B/op 90033.84× slower
518 | Hash#has_value? 516.16M ( 1.94ns) (± 6.66%) 0 B/op fastest
519 | ```
520 |
521 | ##### `Hash#merge!` vs `Hash#[]=` [code](code/hash/merge-bang-vs-brackets.cr)
522 |
523 | ```
524 | crystal code/hash/merge-bang-vs-brackets.cr --release Mon Feb 25 22:20:32 2019
525 | Crystal 0.27.2 (2019-02-05)
526 |
527 | LLVM: 6.0.1
528 | Default target: x86_64-apple-macosx
529 | Hash#merge! 33.69k ( 29.68µs) (± 4.23%) 26415 B/op 3.74× slower
530 | Hash#[]= 125.96k ( 7.94µs) (± 3.21%) 5538 B/op fastest
531 | ```
532 |
533 |
534 | ##### `Hash#merge` vs `Hash#merge!` [code](code/hash/merge-vs-merge-bang.cr)
535 |
536 | ```
537 | crystal code/hash/merge-vs-merge-bang.cr --release
538 | Crystal 0.27.2 (2019-02-05)
539 |
540 | LLVM: 6.0.1
541 | Default target: x86_64-apple-macosx
542 |
543 | Hash#merge 2.38k (420.05µs) (± 3.01%) 304740 B/op 13.86× slower
544 | Hash#merge! 32.99k ( 30.32µs) (± 1.83%) 26415 B/op fastest
545 | ```
546 |
547 | ##### `{}#merge!(Hash)` vs `Hash#merge({})` vs `Hash#dup#merge!({})` [code](code/hash/merge-bang-vs-merge-vs-dup-merge-bang.cr)
548 |
549 | ```
550 | crystal code/hash/merge-bang-vs-merge-vs-dup-merge-bang.cr --release
551 | Crystal 0.27.2 (2019-02-05)
552 |
553 | LLVM: 6.0.1
554 | Default target: x86_64-apple-macosx
555 | {}#merge!(Hash) do end 24.35k ( 41.06µs) (± 6.10%) 35758 B/op fastest
556 | Hash#merge({}) 15.08k ( 66.32µs) (± 8.26%) 59754 B/op 1.61× slower
557 | Hash#dup#merge!({}) 15.11k ( 66.17µs) (± 5.16%) 59763 B/op 1.61× slower
558 | ```
559 |
560 | ##### `Hash#sort_by` vs `Hash#sort` [code](code/hash/hash-key-sort_by-vs-sort.cr)
561 |
562 | To sort hash by key. Keep in mind that if you need to do this, is a code smell of a wrong data structure. Even in Ruby, `sort` & `sort_by` make implicit calls `.to_a` first.
563 | But remember, in Crystal we have Tuples!, so consider using: Array(Tuple(Symbol, String)) instead?
564 | Having said that, this is probably not a bottleneck in your app :)
565 |
566 | ```
567 | crystal code/hash/hash-key-sort_by-vs-sort.cr --release
568 | Crystal 0.27.2 (2019-02-05)
569 |
570 | LLVM: 6.0.1
571 | Default target: x86_64-apple-macosx
572 | sort_by + to_h 156.46k ( 6.39µs) (± 2.96%) 5159 B/op 1.19× slower
573 | sort + to_h 185.7k ( 5.39µs) (± 4.60%) 4330 B/op fastest
574 | ```
575 |
576 |
577 | --------------------------------
578 |
579 |
580 |
581 |
582 | ### Enumerable
583 |
584 | ##### `Enumerable#each + push` vs `Enumerable#map` [code](code/enumerable/each-push-vs-map.cr)
585 |
586 | ```
587 | crystal code/enumerable/each-push-vs-map.cr --release
588 | Crystal 0.27.2 (2019-02-05)
589 |
590 | LLVM: 6.0.1
591 | Default target: x86_64-apple-macosx
592 |
593 | Array#each + push 849.93k ( 1.18µs) (± 0.75%) 1712 B/op 2.65× slower
594 | Array#map 2.25M (443.66ns) (± 7.10%) 480 B/op fastest
595 | ```
596 |
597 | ##### `Enumerable#each` vs `loop` vs `while` vs `step` vs `upto` vs `times` vs `downto` [code](code/enumerable/each-vs-loop-vs-while-vs-step-vs-upto-vs-downto-vs-times.cr)
598 | `each` is the fastest way to iterate over an array. But if another approach makes your code clearer - use it.
599 |
600 | ```
601 | crystal code/enumerable/each-vs-loop-vs-while-vs-step-vs-upto-vs-downto-vs-times.cr --release
602 | Crystal 0.27.2 (2019-02-05)
603 |
604 | LLVM: 6.0.1
605 | Default target: x86_64-apple-macosx
606 |
607 | #step 6.07M (164.82ns) (± 2.22%) 0 B/op 65.18× slower
608 | #upto 8.95M ( 111.7ns) (± 1.91%) 0 B/op 44.17× slower
609 | #downto 8.49M (117.72ns) (± 1.85%) 0 B/op 46.55× slower
610 | #times 12.92M ( 77.42ns) (± 2.37%) 0 B/op 30.62× slower
611 | #each 395.48M ( 2.53ns) (± 4.25%) 0 B/op fastest
612 | while 7.57M (132.03ns) (± 2.05%) 0 B/op 52.21× slower
613 | loop 6.03M (165.79ns) (± 2.12%) 0 B/op 65.56× slower
614 | ```
615 |
616 | ##### `Enumerable#each_with_index` vs `while` loop [code](code/enumerable/each_with_index-vs-while-loop.cr)
617 |
618 | ```
619 | crystal code/enumerable/each_with_index-vs-while-loop.cr --release
620 | Crystal 0.27.2 (2019-02-05)
621 |
622 | LLVM: 6.0.1
623 | Default target: x86_64-apple-macosx
624 |
625 | While Loop 9.54M (104.83ns) (± 2.32%) 0 B/op 41.38× slower
626 | each_with_index 394.74M ( 2.53ns) (± 4.30%) 0 B/op fastest
627 | ```
628 |
629 | ##### `Enumerable#map`. vs `Enumerable#flat_map` [code](code/enumerable/map-flatten-vs-flat_map.cr)
630 |
631 |
632 | ```
633 | crystal code/enumerable/map-flatten-vs-flat_map.cr --release
634 | Crystal 0.27.2 (2019-02-05)
635 |
636 | LLVM: 6.0.1
637 | Default target: x86_64-apple-macosx
638 |
639 | Array#map.flatten 99.74k ( 10.03µs) (± 4.40%) 9646 B/op 1.30× slower
640 | Array#flat_map 129.47k ( 7.72µs) (± 3.14%) 7366 B/op fastest
641 | ```
642 |
643 | ##### `Enumerable#reverse.each` vs `Enumerable#reverse_each` [code](code/enumerable/reverse-each-vs-reverse_each.cr)
644 |
645 | `Enumerable#reverse` allocates an extra array.
646 | `Enumerable#reverse_each` yields each value without allocating an extra array.
647 |
648 |
649 | ```
650 | crystal code/enumerable/reverse-each-vs-reverse_each.cr --release
651 | Crystal 0.27.2 (2019-02-05)
652 |
653 | LLVM: 6.0.1
654 | Default target: x86_64-apple-macosx
655 | Array#reverse.each 2.16M ( 463.7ns) (± 5.72%) 480 B/op 175.64× slower
656 | Array#reverse_each 378.79M ( 2.64ns) (± 5.22%) 0 B/op fastes
657 | ```
658 |
659 | ##### `Enumerable#sort_by.first` vs `Enumerable#min_by` [code](code/enumerable/sort_by-first-vs-min_by.cr)
660 | `Enumerable#sort_by` performs a sort of the enumerable and allocates a
661 | new array the size of the enumerable. `Enumerable#min_by` doesn't
662 | perform a sort or allocate an array the size of the enumerable.
663 | Similar comparisons hold for `Enumerable#sort_by.last` vs
664 | `Enumerable#max_by`, `Enumerable#sort.first` vs `Enumerable#min`, and
665 | `Enumerable#sort.last` vs `Enumerable#max`.
666 |
667 | ```
668 | crystal code/enumerable/sort_by-first-vs-min_by.cr --release Tue Feb 26 00:57:16 2019
669 | Crystal 0.27.2 (2019-02-05)
670 |
671 | LLVM: 6.0.1
672 | Default target: x86_64-apple-macosx
673 |
674 | Enumerable#min_by 404.49M ( 2.47ns) (± 3.90%) 0 B/op fastest
675 | Enumerable#sort_by...first 360.16k ( 2.78µs) (± 4.20%) 1888 B/op 1123.08× slower
676 | ```
677 |
678 | ##### `Enumerable#find` vs `Enumerable#select.first` [code](code/enumerable/select-first-vs-find.cr)
679 |
680 | Keeping with Crystal's convention of having more or less 1 obvious way of doing stuff, for example there is no `Enumerable#detect`, like in Ruby.
681 |
682 | ```
683 | crystal code/enumerable/select-first-vs-find.cr --release 1.3m Tue Feb 26 00:47:29 2019
684 | Crystal 0.27.2 (2019-02-05)
685 |
686 | LLVM: 6.0.1
687 | Default target: x86_64-apple-macosx
688 |
689 | Enumerable#select.first 6.79M (147.18ns) (± 3.54%) 48 B/op 66.37× slower
690 | Enumerable#find 450.97M ( 2.22ns) (± 5.47%) 0 B/op fastest
691 | ```
692 |
693 | ##### `Enumerable#select.last` vs `Enumerable#reverse.detect` [code](code/enumerable/select-last-vs-reverse-find.cr)
694 |
695 | ```
696 | crystal code/enumerable/select-last-vs-reverse-find.cr --release
697 | Crystal 0.27.2 (2019-02-05)
698 |
699 | LLVM: 6.0.1
700 | Default target: x86_64-apple-macosx
701 |
702 | Enumerable#reverse.find 2.05M (487.74ns) (± 3.14%) 480 B/op 1.44× slower
703 | Enumerable#select.last 2.95M (338.92ns) (± 3.59%) 145 B/op fastest
704 | ```
705 |
706 | ##### `Enumerable#sort` vs `Enumerable#sort_by` [code](code/enumerable/sort-vs-sort_by.cr)
707 |
708 | ```
709 | crystal code/enumerable/sort-vs-sort_by.cr --release
710 | Crystal 0.27.2 (2019-02-05)
711 |
712 | LLVM: 6.0.1
713 | Default target: x86_64-apple-macosx
714 |
715 | Enumerable#sort_by (Symbol#to_proc) 104.9k ( 9.53µs) (± 2.24%) 3136 B/op 1.58× slower
716 | Enumerable#sort_by 104.28k ( 9.59µs) (± 2.73%) 3136 B/op 1.59× slower
717 | Enumerable#sort 165.91k ( 6.03µs) (± 2.02%) 1056 B/op fastest
718 | ```
719 |
720 | ##### `Enumerable#reduce Block` vs `Enumerable#reduce Proc` [code](code/enumerable/inject-symbol-vs-block.cr)
721 |
722 |
723 | ```
724 | crystal code/enumerable/reduce-proc-vs-block.cr --release
725 | Crystal 0.27.2 (2019-02-05)
726 |
727 | LLVM: 6.0.1
728 | Default target: x86_64-apple-macosx
729 |
730 | reduce to_proc 389.91M ( 2.56ns) (± 5.00%) 0 B/op 1.03× slower
731 | reduce block 401.7M ( 2.49ns) (± 4.65%) 0 B/op fastest
732 | ```
733 |
734 | --------------------------------
735 |
736 | ## License
737 |
738 | 
739 |
740 | This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).
741 |
742 |
743 | ## Code License
744 |
745 | ### CC0 1.0 Universal
746 |
747 | To the extent possible under law, @konung has waived all copyright and related or neighboring rights to "fast-crystal".
748 |
749 | This work belongs to the community.
750 |
751 |
--------------------------------------------------------------------------------