You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -12,7 +12,7 @@ Once you have tested the functionality, you can start exploring the performance
12
12
## Checking type stability
13
13
Recall that type stable function is written in a way, that allows Julia's compiler to infer all the types of all the variables and produce an efficient native code implementation without the need of boxing some variables in a structure whose types is known only during runtime. Probably unbeknown to you we have already seen an example of type unstable function (at least in some situations) in the first lab, where we have defined the `polynomial` function:
14
14
15
-
```julia
15
+
```@repl lab05_polynomial
16
16
function polynomial(a, x)
17
17
accumulator = 0
18
18
for i in length(a):-1:1
@@ -25,20 +25,20 @@ end
25
25
The exact form of compiled code and also the type stability depends on the arguments of the function. Let's explore the following two examples of calling the function:
26
26
27
27
- Integer number valued arguments
28
-
```julia
28
+
```@example lab05_polynomial
29
29
a = [-19, 7, -4, 6]
30
30
x = 3
31
31
polynomial(a, x)
32
32
```
33
33
34
34
- Float number valued arguments
35
-
```julia
35
+
```@example lab05_polynomial
36
36
xf = 3.0
37
37
polynomial(a, xf)
38
38
```
39
39
40
40
The result they produce is the "same" numerically, however it differs in the output type. Though you have probably not noticed it, there should be a difference in runtime (assuming that you have run it once more after its compilation). It is probably a surprise to no one, that one of the methods that has been compiled is type unstable. This can be check with the `@code_warntype` macro:
41
-
```julia
41
+
```@repl lab05_polynomial
42
42
using InteractiveUtils #hide
43
43
@code_warntype polynomial(a, x) # type stable
44
44
@code_warntype polynomial(a, xf) # type unstable
@@ -56,6 +56,45 @@ We are getting a little ahead of ourselves in this lab, as understanding of thes
56
56
- run the function with the argument once before running `@time` or use `@btime` if you have `BenchmarkTools` readily available in your environment
57
57
- To see some measurable difference with this simple function, a longer vector of coefficients may be needed.
58
58
59
+
!!! details
60
+
```@repl lab05_polynomial
61
+
function polynomial_stable(a, x)
62
+
accumulator = zero(x)
63
+
for i in length(a):-1:1
64
+
accumulator += x^(i-1) * a[i]
65
+
end
66
+
accumulator
67
+
end
68
+
```
69
+
70
+
```@repl lab05_polynomial
71
+
@code_warntype polynomial_stable(a, x) # type stable
72
+
@code_warntype polynomial_stable(a, xf) # type stable
73
+
```
74
+
75
+
```@repl lab05_polynomial
76
+
polynomial(a, xf) #hide
77
+
polynomial_stable(a, xf) #hide
78
+
@time polynomial(a, xf)
79
+
@time polynomial_stable(a, xf)
80
+
```
81
+
82
+
Only really visible when evaluating multiple times.
83
+
```julia
84
+
julia> using BenchmarkTools
85
+
86
+
julia> @btime polynomial($a, $xf)
87
+
31.806 ns (0 allocations: 0 bytes)
88
+
128.0
89
+
90
+
julia> @btime polynomial_stable($a, $xf)
91
+
28.522 ns (0 allocations: 0 bytes)
92
+
128.0
93
+
```
94
+
Difference only a few nanoseconds.
95
+
96
+
97
+
*Note*: Recalling homework from lab 1. Adding `zero` also extends this function to the case of `x` being a matrix, see `?` menu.
59
98
60
99
Code stability issues are something unique to Julia, as its JIT compilation allows it to produce code that contains boxed variables, whose type can be inferred during runtime. This is one of the reasons why interpreted languages are slow to run but fast to type. Julia's way of solving it is based around compiling functions for specific arguments, however in order for this to work without the interpreter, the compiler has to be able to infer the types.
61
100
@@ -78,7 +117,7 @@ The number of samples/evaluations can be set manually, however most of the time
78
117
79
118
The most commonly used interface of `Benchmarkools` is the `@btime` macro, which returns an output similar to the regular `@time` macro however now aggregated over samples by taking their minimum (a robust estimator for the location parameter of the time distribution, should not be considered an outlier - usually the noise from other processes/tasks puts the results to the other tail of the distribution and some miraculous noisy speedups are uncommon. In order to see the underlying sampling better there is also the `@benchmark` macro, which runs in the same way as `@btime`, but prints more detailed statistics which are also returned in the `Trial` type instead of the actual code output.
80
119
81
-
```julia
120
+
```
82
121
julia> @btime sum($(rand(1000)))
83
122
174.274 ns (0 allocations: 0 bytes)
84
123
504.16236531044757
@@ -160,6 +199,20 @@ In order to get more of a visual feel for profiling, there are packages that all
160
199
!!! warning "Exercise"
161
200
Let's compare this with the type unstable situation.
162
201
202
+
!!! details
203
+
First let's define the function that allows us to run the `polynomial` multiple times.
204
+
```@repl lab05_polynomial
205
+
function run_polynomial(a, x, n)
206
+
for _ in 1:n
207
+
polynomial(a, x)
208
+
end
209
+
end
210
+
```
211
+
212
+
```julia
213
+
@profview run_polynomial(a, xf, Int(1e5)) # clears the profile for us
214
+
```
215
+

163
216
164
217
Other options for viewing profiler outputs
165
218
-[ProfileView](https://github.com/timholy/ProfileView.jl) - close cousin of `ProfileSVG`, spawns GTK window with interactive FlameGraph
@@ -176,8 +229,48 @@ We have noticed that no matter if the function is type stable or unstable the ma
176
229
177
230
**BONUS**: Profile the new method and compare the differences in traces.
178
231
179
-
[^1]: Explanation of the Horner schema can be found on [https://en.wikipedia.org/wiki/Horner%27s\_method](https://en.wikipedia.org/wiki/Horner%27s_method).
232
+
[^1]: Explanation of the Horner schema can be found on [https://en.wikipedia.org/wiki/Horner%27s\_method](https://en.wikipedia.org/wiki/Horner%27s_method).
233
+
234
+
!!! details
235
+
```julia
236
+
function polynomial(a, x)
237
+
accumulator = a[end] * one(x)
238
+
for i in length(a)-1:-1:1
239
+
accumulator = accumulator * x + a[i]
240
+
end
241
+
accumulator
242
+
end
243
+
```
244
+
245
+
Speed up:
246
+
- 49ns -> 8ns ~ 6x on integer valued input
247
+
- 59ns -> 8ns ~ 7x on real valued input
248
+
249
+
```
250
+
julia> @btime polynomial($a, $x)
251
+
8.008 ns (0 allocations: 0 bytes)
252
+
97818
253
+
254
+
julia> @btime polynomial_stable($a, $x)
255
+
49.173 ns (0 allocations: 0 bytes)
256
+
97818
257
+
258
+
julia> @btime polynomial($a, $xf)
259
+
8.008 ns (0 allocations: 0 bytes)
260
+
97818.0
261
+
262
+
julia> @btime polynomial_stable($a, $xf)
263
+
58.773 ns (0 allocations: 0 bytes)
264
+
97818.0
265
+
```
266
+
These numbers will be different on different HW.
267
+
268
+
**BONUS**: The profile trace does not even contain the calling of mathematical operators and is mainly dominated by the iteration utilities. In this case we had to increase the number of runs to `1e6` to get some meaningful trace.
180
269
270
+
```julia
271
+
@profview run_polynomial(a, xf, Int(1e6))
272
+
```
273
+

181
274
182
275
---
183
276
@@ -270,7 +363,7 @@ BenchmarkTools.Trial: 10000 samples with 7 evaluations.
270
363
Let's now apply what we have learned so far on the much bigger codebase of our
271
364
`Ecosystem`.
272
365
273
-
```julia
366
+
```@example block
274
367
include("ecosystems/lab04/Ecosystem.jl")
275
368
276
369
function make_counter()
@@ -295,7 +388,6 @@ world = create_world();
295
388
nothing # hide
296
389
```
297
390
298
-
299
391
!!! warning "Exercise"
300
392
Use `@profview` and `@code_warntype` to find the type unstable and slow parts of
Julia still has to perform runtime dispatch on the small `Union` of `Agent`s that is in our dictionary.
@@ -460,6 +632,7 @@ end
460
632
world = create_world();
461
633
nothing # hide
462
634
```
635
+
463
636
```@example newblock
464
637
using InteractiveUtils # hide
465
638
w = Wolf(4000)
@@ -468,5 +641,3 @@ find_food(w, world)
468
641
```
469
642
470
643
## Useful resources
471
-
472
-
The problem we have been touching in the latter part is quite pervasive in some systems with many agents. One solution we have not used here is to use SumTypes. Julia does not have a native support, but offers solutions thourough libraries like [SumTypes.jl](https://github.com/JuliaDynamics/LightSumTypes.jl), [UniTyper.jl](https://github.com/YingboMa/Unityper.jl) or [Moshi](https://rogerluo.dev/Moshi.jl/).
0 commit comments