update on 07/04/2022: added crystal and ruby version terminal outputs below
1 2 3 4 5 6 $ crystal --version Crystal 1.4.1 (2022-04-22) LLVM: 13.0.1 Default target: aarch64-apple-darwin21.5.0 $
1 2 3 $ ruby --version ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21] $
Refresher about Crystal and Ruby
In my previous post, I introduced the Crystal programming language, and additionally compared it some to it’s major influence, the Ruby programming languange.
In this post, I want to compare and contrast their runtime performance. Yes, this topic is awesome enough (to mean they differ enough in performance) to deserve it’s own post!
We’ll be comparing the runtime performance of the same source code. This is so cool. You’ll almost never find two different programming languages where you can run the same source code on. Of course, in real-world applications, this will almost certainly never be the case, as a real-world application will almost always contain enough code that at some point they will just plain out differ in syntax (e.g. when using third-party dependencies).
Let’s get to some code and run it!
Please take this code lightly, it is primarily written to be easy to read and for educational purposes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # bubble_sort_performance.cr list = (1..1000).to_a.reverse def swap_elements(array) array.each_index do |element_index| if element_index == array.size-1 return false else if array[element_index] > array[element_index+1] next_element_value = array[element_index+1] array[element_index+1] = array[element_index] array[element_index] = next_element_value return true end end end end while(true) if !swap_elements(list) break end end
In this code, we bubble sort an array (list) of numbers that are in from greatest to least in order to from least to greatest in order (which is to say the worst possible case scenario in run complexity because every element needs to be moved).
Let’s now run this code with Crystal and with Ruby…
crystal run vs
crystal build vs
crystal build --release
Here is the summary of runtime performances:
|execution type||time (in seconds)|
|crystal build –release||00.303|
Let’s dive in a bit into each of these execution runtime performances…
1 2 $ time ruby src/bubble_sort_performance.cr ruby src/bubble_sort_performance.cr 11.55s user 0.21s system 99% cpu 11.818 total
Above, we run the bubble sort code using the
ruby interpreter, and we see it takes 11.818 seconds to interpret and run. This is our baseline performance value to compare with Crystal runs.
1 2 $ time crystal run src/bubble_sort_performance.cr crystal run src/bubble_sort_performance.cr 2.28s user 0.15s system 102% cpu 2.371 total
Above, we run the bubble sort code using the
crystal run command, where it builds and runs the resulting binary file in one command, and it takes 2.371 seconds to do so! That’s 4.98x as fast as the
ruby interpreter run!! Remember this includes the build time, which is to say this is just an alias and helper command to be used during development and testing activities.
Now imagine if we removed the build process and only measure the run time of the resulting binary file?! …
1 2 3 4 $ crystal build src/bubble_sort_performance.cr -rwxr-xr-x 1 rrevi staff 1.3M Jul 4 07:40 bubble_sort_performance $ time ./bubble_sort_performance ./bubble_sort_performance 1.80s user 0.01s system 99% cpu 1.811 total
Above, first we
crystal build a binary file from source, and notice that the resulting binary file is 1.3 megabytes in disk size. Next, we run said binary file, and it only takes 1.811 seconds to run! That’s 6.52x as fast as the
ruby interpreter run!!
For as fast as exclusively running the build execution binary file is, now imagine if during the build process we add compiler optimization flags, as if we were building for running in a production environment?! …
1 2 3 4 $ crystal build --release src/bubble_sort_performance.cr -rwxr-xr-x 1 rrevi staff 331K Jul 4 07:51 bubble_sort_performance $ time ./bubble_sort_performance ./bubble_sort_performance 0.12s user 0.01s system 42% cpu 0.303 total
And finally, above, we first build with a release flag
crystal build --release, read more about release building here. Also, from building with the release flag, notice that the disk size of the resuling binary file is only 331 kilobytes! That is only 23% the size of not using release optimizations!! Next, we run the resulting binary file, and here we see it only takes 0.303 seconds to run!!! Talk about fast!!!! That is 39x as fast as running with the
ruby interpreter!!!!! I am speechless. I hope you are as amazed and in disbelieve as I am. (and bonus thing to point out: notice how in this case, it only uses 42% of the CPU; now go compare that to the other runtime performances)
There are many takeaways from walking through the runtime performance of a Crystal program (and more so when compared to a computer language like Ruby, it’s main influence, well syntatically speaking), but there is really one ☝🏽 that I hope you go away with from this post: As much as the Crystal programming language is a language for humans (read Ruby like syntax), it is as much a language for computers! (read better utilization of the computer, hence the fast runtime speeds in this post).
Thanks for reading.