Eléments de programmation#
Pour mener à bien un calcul algorithmique le nombre d’éléments de langage n’est pas très important et peut se résumer aux 3 syntaxes suivantes
Le choix conditionnel if-elseif-else-end.
La boucle for-end.
La boucle while-end
if … elseif … else … end#
Un choix simple si le test est vrai (k==1) alors le bloc d’instruction est évalué
using Random
let k = rand(1:2)
if k == 1
println("k=1")
end
end
en Julia une autre syntaxe est possible avec
let k = rand(1:2)
k == 1 && println("k=1")
end
false
Le else permet de donner un résultat par défaut…
let k = rand(1:2)
if k != 1
println("k ≠ 1")
else
println("k=1")
end
end
k=1
Une succession de elseif permet de choisir parmi plusieurs critères, dans la succession des blocs de if et elseif le premier qui est “vrai” est évaluer et l’instruction s’arrète.
let k = rand(1:10)
@show k
if k == 5
println("k = 5")
elseif k > 5
println("k > 5")
else
println("k < 5")
end
end
k = 9
k > 5
La boucle for#
Elle peut se définir à l’aide d’itérateurs ou de tableaux de valeurs les syntaxes “=” , “in” ou “\(\in\)” sont équivalentes
for i = 1:10
println(i)
end
1
2
3
4
5
6
7
8
9
10
for i in ["un" 2 im]
println(typeof(i))
end
String
Int64
Complex{Bool}
typeof.(["un", 2, im])
3-element Vector{DataType}:
String
Int64
Complex{Bool}
eltype(["un" 2 im])
Any
La commande break permet de sortir d’une boucle à tout moment
for i = rand(1:10,5) # 5 valeurs entre 1 et 10
@show i
i == 5 && break
end
i = 6
i = 10
i = 7
i = 9
i = 1
La commande continue permet elle de courtcircuiter la boucle en court et de passer à la valeur suivante
for i = 1:10
if i % 3 != 0 # i modulo 3 different de 0
continue
end
println(i)
end
3
6
9
La boucle while#
Tant que le test est “vrai” le bloc est évalué, le test se faisant en entrée de bloc
let k = 0
while k<10
k += 1 # k=k+1
println(k)
end
end
1
2
3
4
5
6
7
8
9
10
De même que la boucle for les commandes break et continue sont valables …
k=0
while k < 1000
k += 1 # k=k+1
if k % 5 != 0 # k modulo 5 diffèrent de 0
continue # retour en début de boucle avec de nouveau un test sur k
end
println(k)
if k > 30
break
end
end
5
10
15
20
25
30
35
let n = 10000000
k = 0
while n > 1
if n % 2 == 0
n = n ÷ 2
else
n = 3n+1
end
k += 1
end
@show k, n
end
(k, n) = (145, 1)
(145, 1)
Un peu d’optimisation#
Julia est dit un langage performant, regardons rapidement quelques exemples à faire ou ne pas faire.
Exemple de préallocation et utilisation de push!#
@time let n = 100000
A = zeros(0) # Tableau vide qui contiendra des Float64
for i = 1:n
A = [A; i] # a chaque itération on change la taille de A
end
println(last(A,10))
end
[99991.0, 99992.0, 99993.0, 99994.0, 99995.0, 99996.0, 99997.0, 99998.0, 99999.0, 100000.0]
19.713944 seconds (2.37 M allocations: 37.514 GiB, 18.67% gc time, 2.79% compilation time)
Cet exemple montre le coût prohibitif d’une réallocation dynamique qui impose une recopie totale de A à chaque itération. Il faut donc récrire notre code différemment pour réduire cet utilisation excessive du ramasse miettes. Heureusement plusieurs solutions s’offrent à vous
Utiliser la fonction
push!Allouer le tableau
Aavant la boucle si on connait sa tailleUtiliser un itérateur avec la fonction
collectlorsque c’est possibleUtiliser la “comprehension”
@time let n = 100000
A = zeros(0)
for i = 1:n
push!(A,i) # a chaque itération on change la taille de A
end
println(last(A,10))
end
[99991.0, 99992.0, 99993.0, 99994.0, 99995.0, 99996.0, 99997.0, 99998.0, 99999.0, 100000.0]
0.012464 seconds (167 allocations: 1.904 MiB, 65.05% gc time)
@time let n = 100000
A = zeros(Int64,n)
for i in eachindex(A)
A[i] = i
end
println(last(A,10))
end
[99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, 100000]
0.247950 seconds (188.49 k allocations: 11.776 MiB, 99.60% compilation time)
@time let n = 100000
A = collect(1:n)
println(last(A,10))
end
[99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, 100000]
0.001836 seconds (143 allocations: 788.625 KiB)
@time let n = 100000
A = [i for i=1:n]
println(last(A,10))
end
[99991, 99992, 99993, 99994, 99995, 99996, 99997, 99998, 99999, 100000]
0.001195 seconds (143 allocations: 788.625 KiB)
Exemple de vectorisation#
Regardons la vectorisation sous Julia à l’aide de la construction d’une matrice de Vandermonde
@time let n = 5000
x = LinRange(0, 1, n)
V = zeros(n,n)
for j = axes(V,2)
V[:,j] .= x.^(j-1) # calcul vectorisé
end
end
1.555369 seconds (2 allocations: 190.738 MiB, 1.75% gc time)
@time let n = 5000
x = LinRange(0, 1, n)
V = zeros(n,n)
for i = axes(V,1)
V[i,:] .= [x[i]^(j-1) for j in axes(V,2)]
end
end
2.324108 seconds (110.96 k allocations: 390.959 MiB, 7.09% gc time, 1.24% compilation time)
@time let n = 5000
x = LinRange(0, 1, n)
V = zeros(n,n)
for j=axes(V,2), i=axes(V,1)
V[i,j] = x[i]^(j-1) # calcul dévectorisé
end
end
1.218916 seconds (3 allocations: 190.738 MiB, 0.18% gc time)
@time let n = 5000
x = LinRange(0, 1, n)
V = zeros(n,n)
for i=axes(V,1), j=axes(V,2)
V[i,j] = x[i]^(j-1) # calcul dévectorisé
end
end
1.758134 seconds (3 allocations: 190.738 MiB, 1.57% gc time)
@time let n = 3000
x = LinRange(0, 1, n)
W = [x[i]^(j-1) for j=1:n, i=1:n]
end; # le ';' permet d'éviter que la matrice W soit affichée
0.650115 seconds (63.53 k allocations: 71.956 MiB, 0.57% gc time, 8.33% compilation time)
Pour obtenir de meilleures performances il faut le plus souvent possible écrire des fonctions.
using BenchmarkTools
function Vander1(n)
x = LinRange(0, 1, n)
V = zeros(n, n)
for i=1:n
V[:,i] .= x.^(i-1)
end
return V
end
@btime Z = Vander1(3000);
463.351 ms (3 allocations: 68.67 MiB)
function Vander2(n)
x = LinRange(0, 1, n)
V = zeros(n, n)
for j=1:n, i=1:n
V[i,j] = x[i]^(j-1) # calcul dévectorisé
end
return V
end
@btime Z = Vander2(3000);
469.375 ms (3 allocations: 68.67 MiB)
function Vander3(n)
x = LinRange(0, 1, n)
return [x[i]^(j-1) for i=1:n, j=1:n]
end
@btime Z=Vander3(3000);
472.353 ms (3 allocations: 68.67 MiB)
La conclusion n’est pas toujours évidente a priori… Néanmoins on peut voir que le fait de mettre le code dans une fonction impose une optimisation à la compilation et une contextualisation du type et que l’écriture explicite des boucles donne un meilleur résultat.