With --release flag (built in alpine docker container and statically linked and also with build on developer machine):
β― time mint build
Mint - Building for production
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Ensuring dependencies... 0ΞΌs
β Clearing the "dist" directory... 3.392ms
β Copying "public" folder contents... 14.85ms
β Compiling your application:
β Parsing 153 source files... 18.442s
β Type checking: 132.299ms
β Compiling: 206.04ms
β Writing index.html... 445ΞΌs
β Writing manifest.json...51ΞΌs
β Generating icons... 120.198ms
β Creating service worker...17.826ms
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
All done in 22.701s!
./mint build 23,23s user 0,20s system 103% cpu 22,717 total
Without --release flag (built in development machine):
β― time mint-dev build
Mint - Building for production
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Ensuring dependencies... 0ΞΌs
β Clearing the "dist" directory... 790ΞΌs
β Copying "public" folder contents... 3.201ms
β Compiling your application:
β Parsing 153 source files... 8.391s
β Type checking: 218.748ms
β Compiling: 823.635ms
β Writing index.html... 697ΞΌs
β Writing manifest.json...145ΞΌs
β Generating icons... 342.277ms
β Creating service worker...12.597ms
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
All done in 10.825s!
mint-dev build 11,59s user 0,46s system 105% cpu 11,377 total
It seems to me that IO related tasks - clearing dist folder, copying public folder, parsing files - are slower while CPU heavy tasks - type checking, compiling - are quicker.
I donβt remember this being the case before, but I havenβt been checking regularly.
I have two questions:
Anyone know why it is this way?
Anyone did in-depth profiling of Crystal applications? Iβm interested in per method duration to see which parts are the bottlenecks. Flamegraph maybe?
Try compiling statically on Alpine with, and without --release. Then, the same on the developer machine directly. This way we will be able to know more precisely on which cases the binary is slower.
It would be nice if you could have a small benchmark program to see this in action, otherwise itβs pretty much impossible for others to try to figure out why this is happening.
Anyone did in-depth profiling of Crystal applications? Iβm interested in per method duration to see which parts are the bottlenecks. Flamegraph maybe?
If you are on Mac you can use XCodeβs Instruments βTime Profilerβ. But youβll need to put the offending code in a loop to get good results.
Timings for the `mint build` command with different builds.
# 0.36.1 (alpine in docker)
Build args: --static
Timings: 14,47s user 0,21s system 106% cpu 13,730 total
Build args: --static --release
Timings: 25,18s user 0,23s system 102% cpu 24,677 total
# 0.35.1 (alpine in docker)
Build args: --static
Timings: 16,47s user 0,24s system 107% cpu 15,612 total
Build args: --static --release
Timings: 14,57s user 0,19s system 105% cpu 13,963 total
# 0.36.1
Build args: --error-on-warnings --error-trace --progress
Timings: 10,48s user 0,23s system 109% cpu 9,749 total
Build args: --error-on-warnings --error-trace --progress --release
Timings: 9,86s user 0,24s system 110% cpu 9,148 total
# 0.36.0
Build args: --error-on-warnings --error-trace --progress
Timings: 11,61s user 0,26s system 106% cpu 11,197 total
Build args: --error-on-warnings --error-trace --progress --release
Timings: 28,24s user 0,19s system 101% cpu 27,928 total
# 0.35.1
Build args: --error-on-warnings --error-trace --progress
Timings: 10,00s user 0,20s system 109% cpu 9,289 total
Build args: --error-on-warnings --error-trace --progress --release
Timings: 10,57s user 0,26s system 109% cpu 9,866 total
Locally it seems that the issue was with 0.36.0 since itβs not happening on 0.36.1 any more, but on alpine it seems still the case.
If you are on Mac you can use XCodeβs Instruments βTime Profilerβ. But youβll need to put the offending code in a loop to get good results.
Thanks but Iβm on Linux and I want to find the offending code
So we finally figured out why it was slow: it wasnβt IO but exceptions:
Basically the parser of Mint used exceptions to exit early from functions and by refactoring that into checking nil caused a massive speed up in parsing.
With this and some other optimizations the parsing of the same code base went from the earlier reported 25.18s to 499.643ms.
I still donβt know why using the --release flag doubled the parsing time but I guess it does something to exceptions - if anyone has ideas let me know Iβm curious
require "benchmark"
class SomeError < Exception
end
1000000.times do
begin
raise SomeError.new
rescue
end
end
Running it in different scenarios:
β― crystal build benchmark.cr
β― time ./benchmark
./benchmark 4,00s user 0,04s system 111% cpu 3,636 total
β― crystal build benchmark.cr --release
β― time ./benchmark
./benchmark 3,02s user 0,10s system 111% cpu 2,793 total
β― crystal build benchmark.cr --release --no-debug
β― time ./benchmark
./benchmark 2,73s user 0,06s system 113% cpu 2,460 total
So it doesnβt seems to be the case that exceptions slow it down, maybe itβs a combination of exceptions and overloaded functions bur unfortunately I donβt have more time to investigate it.
If anyone wants to dig deeper into this, this is the PR that was lead to the improvement in parsing times: