1. Execution time and measurement
It's time to explore a concept critical not just in Julia but all programming languages - execution time.
2. Initial function definition
Something we've seen so far is the ability to solve problems in multiple ways. Each method has its advantages and disadvantages - one being the time it takes the code to run. We want our code to run as fast as possible. Whether providing a fast and responsive user interface or training a neural network, speed matters - the old saying 'time is money' rings true!
Loops are a common cause of long execution time, so our example here will focus on this.
my_function creates an empty vector x which accepts character values. As we loop over the string Anthony, iterating over each character in the string, we push each character from the string into the x vector, one by one. At the end of the loop, we print out the vector x. This function will be what we time.
3. Base @time macro
A timing method is used to time how long it takes for a function to run. In Julia, we use macros to time our functions. A macro can be thought of as 'code of code' - something we call before a function that will operate on that function.
The first method we will look at is the base @time macro. This is in Julia's base package. It's simple and gives an easy result.
However, it is limited in flexibility, it will only run our function once, and the first time we call this macro, there is compilation overhead. Compilation overhead is the extra time taken for Julia to run our code as we are importing extra packages.
Let's use the macro @time to time our function. To do this, we place @time in front of our function call.
We can see the output generated here. It shows the time it took to execute, the number of allocations, and the total number of bytes allocated. Most important is this compilation time:
99-point-79% compilation time
This means that of the 0-point-27 seconds, 99-point-79% of that time wasn't executing our function but compiling. This happens the first time the @time macro is called. To get an execution time without this, we need to call the macro again:
Now we can see a more accurate time - the memory allocation has gone from an initial 6mb to 3kb!
4. BenchmarkTools - @benchmark
One other macro we will look at is @benchmark. This is not in the base Julia package, but requires an external package called BenchmarkTools. This macro is one of the more flexible and in-depth options in the package, as it gives the max/min run time, the mean/median, and finally, a histogram of all runtimes. It also provides other in-depth statistics that we will not touch on here.
Let's look at the same function but using the @benchmark macro instead. We call the macro in a similar way to our previous calls.
This gives us a much better idea of how our function runs. It has been sampled 6,178 times, with one evaluation run. We have our minimum and maximum runtimes, and we can see that this minimum runtime is similar to what we obtained with @time.
The histogram is very useful - we can see some runs of the function have a significantly longer execution time - as long as 8 milliseconds, which, compared to the median of 333 microseconds, is a lot!
@benchmark allows all these parameters to be specified, so you can drill down on any statistic you want.
5. Let's practice!
You've seen how to measure the execution time of your code. Let's practice it!