├── 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 | ![CC-BY-SA](CC-BY-SA.png) 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 | ![CC-BY-SA](CC-BY-SA.png) 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 | --------------------------------------------------------------------------------