README.md

fast-crystal

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).

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

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!

How to contribute new benchmarks

Fork & clone .

Add a benchmark.

Update README.md with the results.

Make a Pull Request with a short explanation / rationale.

Kick-back and have a drink.

If you are not sure if the benchmark is warranted, open an issue - let's discuss it.

Template for the benchmark

require"benchmark"putsCrystal::DESCRIPTION# Always include this, so that we know which OS & version of Crystal, you used.deffastenddefslowendBenchmark.ips do |x|
x.report("fast code description") { fast }
x.report("slow code description") { slow }
end

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.:

crystal code/array/array-first-vs-index.cr --release

Copy and paste output from console into README.md under appropriate section.

Range

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.

Time (Date)

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

In Ruby, method with yield can be much faster, but in Crystal, it's almost identical to it's counterparts.
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.

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.

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.:

Hash

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.
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"

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.
But remember, in Crystal we have Tuples!, so consider using: Array(Tuple(Symbol, String)) instead?
Having said that, this is probably not a bottleneck in your app :)

Enumerable#sort_by performs a sort of the enumerable and allocates a
new array the size of the enumerable. Enumerable#min_by doesn't
perform a sort or allocate an array the size of the enumerable.
Similar comparisons hold for Enumerable#sort_by.last vs
Enumerable#max_by, Enumerable#sort.first vs Enumerable#min, and
Enumerable#sort.last vs Enumerable#max.