It is not easy to find examples that impress every audience. For some people I show this kind of thing: computing some relative property of elements in a list, but only for pairs in which one element satisfies one condition, and the other another condition. This makes it at least not completely trivial to implement a vectorized version of the code, and tries to convey that the more complex or specific the calculation is, the more important is the standard performance of the straightforward implementations in a language.
For example, let us build a data set Pets, “Cats” or “Dogs”, in which the data structure carries the type of the Pet and its weight. (We can show off by putting units to the weights). We will compute the minimum difference between the weight of any dog or cat in this list:
using Unitful
struct Pet{T}
name::String
weight::T
end
function min_diff_weight(pets)
min_diff_weight = typemax(pets[1].weight)
for i in 1:length(pets)-1
if pets[i].name == "Dog"
for j in i+1:length(pets)
if pets[j].name == "Cat"
min_diff_weight = min(min_diff_weight, abs(pets[i].weight - pets[j].weight))
end
end
end
end
return min_diff_weight
end
which takes for 10^4 pets:
julia> pets = [ Pet(rand(("Dog","Cat")), rand()u"kg") for _ in 1:10^4 ];
julia> @btime min_diff_weight($pets)
147.122 ms (0 allocations: 0 bytes)
3.578299201389967e-8 kg
We did nothing sophisticated there, we literally wrote the double loop that does exactly and explicitly what we wanted.
Now that in Python:
import random
class Pet :
def __init__(self,name,weight) :
self.name = name
self.weight = weight
def min_diff_weight(pets) :
min_diff_weight = float('inf')
for i in range(0,len(pets)-1) :
if pets[i].name == "Dog" :
for j in range(i+1,len(data)) :
if pets[j].name == "Cat" :
min_diff_weight = min(min_diff_weight, abs(pets[i].weight - pets[j].weight))
return min_diff_weight
The codes are similar (although I would say Julia syntax is prettier…). But in Python this takes:
In [10]: pets = [ Pet(random.choice(("Dog","Cat")), random.random()) for _ in range(0,10_000) ]
In [11]: %timeit min_diff_weight(pets)
3.07 s ± 14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Thus, it is about 20 times slower.
It is clear than anyone can come up with faster versions of these codes, using other tools, vectorizing, adding flags, numba, jits, etc. But with a basic knowledge of Julia you can get close to optimal performance for custom tasks like this without having to use any external tool or having to rethink your problem to adapt to those tools. When the problems become complex, this becomes more and more important.