Introduction
Je suis un gros fan d’optimisation, et qui dit optimisation, dit test de rapidité.
Pour faire mes tests j’utilise principalement 3 méthodes que je vais vous décrire dans la suite de ce billet.
Méthodes
1. Le chronomètre
La première méthode peut être comparée à un chronomètre, on le déclenche au début, puis on le stop à la fin, et on affiche le temps
$Debut = (Get-Date)
< YOUR CODE HERE >
$Fin = (Get-Date)
'Temps écoulé : {0} secondes' -f ($Fin-$Debut).totalseconds
Ce qui donne par exemple :
PS C:\> $Debut = (Get-Date)
sleep 10
$Fin = (Get-Date)
'Temps écoulé : {0} secondes' -f ($Fin-$Debut).totalseconds
#Résultat affiché :
Temps écoulé : 10.0050553 secondes
Si vous faites plusieurs tests avec cette méthode, il faudra rajouter des variables afin de collecter les résultats
Exemple :
#1er test
$Debut = (Get-Date)
sleep 2
$Fin = (Get-Date)
$FirstTest = ($Fin-$Debut).TotalSeconds
#2eme test
$Debut = (Get-Date)
sleep 5
$Fin = (Get-Date)
$SecondTest = ($Fin-$Debut).TotalSeconds
#Affichafe résultat
"Temps écoulé 1er Test : $FirstTest secondes"
"Temps écoulé 2eme Test : $SecondTest secondes"
2. La commande Powershell
Pour la deuxième méthode j’utilise une commande powershell spécialement conçu pour ce genre de test : Measure-Command
(Measure-Command{ <YOUR CODE HERE> }).TotalSeconds
En pratique :
PS C:\> (Measure-Command{ sleep 5 }).TotalSeconds
#Résultat affiché :
5,0017303
Comme pour le chronomètre, si vous faites plusieurs tests, il vous faudra passer par des variables afin de stocker les résultats.
Cette fois je vais faire un peu différemment et stocker mes temps dans une hashtable
#création de la hashtable
$result = @{}
#mes tests
$query01 = (Measure-Command{ <YOUR CODE HERE> }).TotalSeconds
$query02 = (Measure-Command{ <YOUR CODE HERE> }).TotalSeconds
$query03 = (Measure-Command{ <YOUR CODE HERE> }).TotalSeconds
#Ajout des résultats dans la hashtable
$result.Add("Test 1", "$query01")
$result.Add("Test 2", "$query02")
$result.Add("Test 3", "$query03")
#Affichage des résultats trié par temps écoulé
$result.GetEnumerator() | Sort-Object Value
En pratique :
#création de la hashtable
$result = @{}
#mes tests
$query01 = (Measure-Command{ sleep 2 }).TotalSeconds
$query02 = (Measure-Command{ sleep 5 }).TotalSeconds
$query03 = (Measure-Command{ sleep 1 }).TotalSeconds
#Ajout des résultats dans la hashtable
$result.Add("Test 1", "$query01")
$result.Add("Test 2", "$query02")
$result.Add("Test 3", "$query03")
#Affichage des résultats
$result.GetEnumerator() | Sort-Object Value
#Résultat affiché :
Name Value
---- -----
Test 3 1.0005092
Test 1 2.0006838
Test 2 5.0003401
Voilà j’ai mon classement de la plus rapide à la plus lente.
La hashtable peut être utilisée avec la 1ere méthode aussi, elle a l’avantage de pouvoir trier à la fin les résultats afin d’avoir le plus rapide en 1er.
La méthode employée pour créer et alimenter la hashtable n’est pas la plus rapide, mais la plus simple pour les débutants. Si vous le souhaitez dans un prochain billet, j’exposerai différentes méthodes de création et d’utilisation des hashtables.
2. La classe .Net
Pour cette troisième et dernière méthode , je vais utiliser une class .Net : Stopwatch
Ce qui donne :
$sw = New-Object Diagnostics.Stopwatch
$sw.Start()
< YOUR CODE HERE >
$sw.Stop()
$sw.Elapsed
En pratique :
$sw = New-Object Diagnostics.Stopwatch
$sw.Start()
sleep 2
$sw.Stop()
$sw.Elapsed
#Résultat affiché :
Days : 0
Hours : 0
Minutes : 0
Seconds : 2
Milliseconds : 1
Ticks : 20019911
TotalDays : 2,3171193287037E-05
TotalHours : 0,000556108638888889
TotalMinutes : 0,0333665183333333
TotalSeconds : 2,0019911
TotalMilliseconds : 2001,9911
A partir de cette méthode j’ai fait une fonction afin de pouvoir tester du code de façon plus industriel et aussi, par exemple, faire plusieurs fois le même test afin d’avoir une moyenne, un minimun et un maximun.
Avant d’afficher la fonction, voici le résultat
#Array pour stocker les résultats
$test =@()
#les tests
$t1 = Measure-MyScript -ScriptBlock { Start-Sleep 1 } -name 'test1' -Repeat 2 -Unit s
$t2 = Measure-MyScript -ScriptBlock { Start-Sleep 3 } -name 'test2' -Repeat 2 -Unit s
$t3 = Measure-MyScript -ScriptBlock { Start-Sleep 5 } -name 'test3' -Repeat 3 -Unit s
#Stockage des résulats
$test += $t1
$test += $t2
$test += $t3
#Vue sur les résultats.
#Le ConvertTo-JSON est là uniquement pour afficher le résultat dans le terminal.
$test | ConvertTo-JSON
#Résultat affiché :
[
{
"test1": {
"Min": "1,0000809 Seconds",
"Max": "1,0001528 Seconds",
"Avg": "1,0001168 Seconds"
}
},
{
"test2": {
"Min": "2,9998739 Seconds",
"Max": "3,0003697 Seconds",
"Avg": "3,0001218 Seconds"
}
},
{
"test3": {
"Min": "5,0001505 Seconds",
"Max": "5,00086 Seconds",
"Avg": "5,0004759 Seconds"
}
}
]
Voici maintenant la fonction Measure-MyScript
, c’est un 1er jet, je viens juste de la faire pour cet article. N’hesitez pas à commenter si vous avez des améliorations à apporter, ou tout autres remarques..
function Measure-MyScript {
<#
.SYNOPSIS
Measure speed execution of a scriptblock
.DESCRIPTION
Measure speed execution of a scriptblock, use it to optimze your code.
.PARAMETER Name
Specifies the name of the test
.PARAMETER ScriptBlock
Specifies the name of your script block or put your code here.
.PARAMETER Repeat
Specifies the numbers of time you want the test run
.PARAMETER Unit
Specifies the unit for the result.
Accepted : d,h,m,s,ms
for Days,hours,minutes,seconds,milliseconds
.EXAMPLE
Measure-MyScript -Name 'Montest' -ScriptBlock { Start-Sleep 2 }
Will execute Sleep for 2 secondes and give you the result in milliseconds associed to 'Montest' name
.EXAMPLE
Measure-MyScript -Name 'Montest' -ScriptBlock { Start-Sleep 2 } -name 'mon test' -Unit 's'
Will execute Sleep for 2 secondes, and give you the result in seconds associed to 'Montest' name
.EXAMPLE
Measure-MyScript -Name 'Montest' -ScriptBlock { Start-Sleep 2 } -Repeat 5 -Unit 's'
Will execute Sleep for 2 secondes, 5 times, and give you the miniminal,maximun and average time in seconds
.NOTES
Christophe Kumor
https://christophekumor.github.io
#>
[CmdletBinding()]
param (
[string]$Name = "Test",
[Parameter(Mandatory = $true)]
[ScriptBlock]$ScriptBlock,
[ValidateScript({ if (@('d','h','m','s','ms') -contains $_) {$true} else {throw "$_ is not supported. Autorised : d,h,m,s,ms ->Days,hours,minutes,seconds,milliseconds"} })]
[String]$Unit = 'ms',
[int]$Repeat = 1
)
if (-not $PSBoundParameters['Name']) {
$Name = ('{0}{1}' -f $Name, $Repeat)
}
$timings = @()
do {
$sw = New-Object Diagnostics.Stopwatch
if ($PSBoundParameters['Verbose']) {
$sw.Start()
try {
&$ScriptBlock
}
catch {
return $_.Exception.Message
}
$sw.Stop()
}
else {
$sw.Start()
try {
$null = &$ScriptBlock
}
catch {
return $_.Exception.Message
}
$sw.Stop()
Write-Host "o." -NoNewLine
}
$timings += $sw.Elapsed
$Repeat--
}
while ($Repeat -gt 0)
$stats = $timings | Measure-Object -Average -Minimum -Maximum -Property Ticks
switch ($Unit)
{
d { $u = 'TotalDays';$msg='Days'; break}
h { $u = 'TotalHours';$msg='Hours'; break}
m { $u = 'TotalMinutes';$msg='Minutes'; break}
s { $u = 'TotalSeconds';$msg='Seconds'; break}
ms { $u = 'TotalMilliseconds';$msg='Milliseconds'; break}
default { $u = 'TotalMilliseconds';$msg='Milliseconds'}
}
if ($PSBoundParameters['Repeat'] -gt 1) {
$hash = @{
Avg = "{0} {1}" -f (New-Object System.TimeSpan $stats.Average).$u, $msg
Min = "{0} {1}" -f (New-Object System.TimeSpan $stats.Minimum).$u, $msg
Max = "{0} {1}" -f (New-Object System.TimeSpan $stats.Maximum).$u, $msg
}
$hash2=@{
$name = $hash
}
}
else {
$hash2=@{
$name = "{0} {1}" -f (New-Object System.TimeSpan $stats.Average).$u, $msg
}
}
return $hash2;
}
Conclusion
And Voilà ! (Oui je sais…, mais j’aime bien cette expression)
A vos chronomètres ! testez votre code et partagez vos trouvailles en termes d’optimisation.