Article original : How to Write Benchmark Tests for Your Golang Functions
Salut les Gophers 👋
Laissez-moi commencer par vous poser une question : Comment testeriez-vous la performance d'un morceau de code ou d'une fonction en Go ? Eh bien, vous pourriez utiliser des tests de benchmark.
Dans ce tutoriel, je vais vous montrer comment utiliser un outil de benchmarking génial qui est intégré au package testing de Golang.
C'est parti.
Que sont les tests de benchmark ?
En Go, les tests de benchmark sont utilisés pour mesurer la performance (vitesse et utilisation de la mémoire) des fonctions ou des blocs de code. Ces tests font partie du Framework de test de Go et sont écrits dans les mêmes fichiers que les tests unitaires, mais ils sont spécifiquement destinés à l'analyse de performance.
Exemple de cas d'utilisation : la suite de Fibonacci
Pour cet exemple, j'utiliserai la classique suite de Fibonacci, qui est déterminée par :
si (x < 2)
F(0) = 1
F(2) = 2
sinon
F(x) = F(x-1) + F(x-2)
En pratique, la suite est :
1, 1, 2, 3, 5, 8, 13, etc.
Cette suite est importante car elle apparaît dans diverses parties des mathématiques et de la nature également, comme illustré ci-dessous :

Il existe plusieurs façons d'implémenter ce code, et j'en choisirai deux pour nos tests de benchmark : les méthodes récursive et itérative. L'objectif principal des fonctions est de fournir une position et de retourner le nombre de Fibonacci à cette position.
Méthode récursive
// main.go
func fibRecursive(n uint) uint {
if n <= 2 {
return 1
}
return fibRecursive(n-1) + fibRecursive(n-2)
}
La fonction ci-dessus est une implémentation récursive du calcul de la suite de Fibonacci. Je vais maintenant la détailler étape par étape pour vous, en tant que débutant en Go.
Voici votre fonction pour calculer les nombres de Fibonacci :
func fibRecursive(n uint) uint {
if n <= 2 {
return 1
}
return fibRecursive(n-1) + fibRecursive(n-2)
}
1. Fonction :
func fibRecursive(n uint) uint
func: Ce mot-clé définit une fonction en Go.fibRecursive: C'est le nom de la fonction. Elle est appeléefibRecursivecar elle calcule les nombres de Fibonacci par récursion.n uint: La fonction prend un seul argument,n, qui est de typeuint(un entier non signé). Cela représente la position dans la suite de Fibonacci que nous voulons calculer.uint: La fonction retourne unuint(entier non signé) car les nombres de Fibonacci sont des entiers non négatifs.
2. Étape de base :
if n <= 2 {
return 1
}
L'instruction
ifvérifie sinest inférieur ou égal à 2.Dans la suite de Fibonacci, le 1er et le 2e nombres sont tous deux 1. Donc, si
nest 1 ou 2, la fonction retourne 1.C'est ce qu'on appelle l'étape de base, et elle empêche la récursion de descendre à l'infini.
3. Étape récursive :
return fibRecursive(n-1) + fibRecursive(n-2)
Si
nest supérieur à 2, la fonction s'appelle elle-même deux fois :fibRecursive(n-1): Calcule le nombre de Fibonacci pour la position juste avantn.fibRecursive(n-2): Calcule le nombre de Fibonacci pour deux positions avantn.
La fonction additionne ensuite ces deux résultats, car chaque nombre de Fibonacci est la somme des deux nombres précédents.
Pour plus de théorie sur la récursion, consultez ces articles.
Méthode itérative
// main.go
func fibIterative(position uint) uint {
slc := make([]uint, position)
slc[0] = 1
slc[1] = 1
if position <= 2 {
return 1
}
var result, i uint
for i = 2; i < position; i++ {
result = slc[i-1] + slc[i-2]
slc[i] = result
}
return result
}
Ce code implémente une approche itérative pour calculer la suite de Fibonacci en Go, ce qui est différent de l'approche récursive. Voici le détail de son fonctionnement :
1. Fonction :
func fibIterative(position uint) uint
func: Ce mot-clé déclare une fonction en Go.fibIterative: Le nom de la fonction suggère qu'elle calcule les nombres de Fibonacci par itération (une boucle).position uint: La fonction prend un argument,position, qui est un entier non signé (uint). Cela représente la position de la suite de Fibonacci que vous souhaitez calculer.uint: La fonction retourne un entier non signé (uint), qui sera le nombre de Fibonacci à la position spécifiée.
2. Création d'un Slice (structure de type tableau) :
slc := make([]uint, position)
slcest un slice (un tableau dynamique en Go) créé avec une longueur égale àposition. Ce slice stockera les nombres de Fibonacci à chaque index.
3. Valeurs initiales pour la suite de Fibonacci :
slc[0] = 1
slc[1] = 1
- Les deux premiers nombres de Fibonacci sont
1, donc les deux premières positions du slice (slc[0]etslc[1]) sont définies sur1.
4. Retour anticipé pour les petites positions :
if position <= 2 {
return 1
}
- Si l'entrée
positionest1ou2, la fonction retourne directement1, car les deux premiers nombres de Fibonacci sont toujours1.
5. Boucle itérative :
var result, i uint
for i = 2; i < position; i++ {
result = slc[i-1] + slc[i-2]
slc[i] = result
}
La boucle commence à
i = 2et s'exécute jusqu'à ce qu'elle atteigne laposition.À chaque itération, le nombre de Fibonacci à l'index
iest calculé comme la somme des deux nombres de Fibonacci précédents (slc[i-1]etslc[i-2]).Le résultat est stocké à la fois dans
resultet dans le sliceslc[i]pour les calculs futurs.
6. Retour du résultat :
return result
- Une fois la boucle terminée, la variable
resultcontient le nombre de Fibonacci à la position souhaitée, et la fonction le retourne.
C'est une approche plus efficace pour calculer les nombres de Fibonacci par rapport à la récursion, surtout quand la position est grande, car elle ne répète pas de calculs inutiles, ce que nous allons prouver en utilisant des tests de benchmark. Prouvons-le.
Comment exécuter les tests de benchmark
Maintenant, pour les tests de benchmark, écrivons du code de test. D'abord, vous devrez créer un fichier main_test.go. À l'intérieur, en utilisant la documentation de Golang sur les tests de benchmark, vous pouvez créer les fonctions à tester comme suit :
// main_test.go
// Benchmark pour la fonction itérative
func BenchmarkFibIterative(b *testing.B) {
for i := 0; i < b.N; i++ {
fibIterative(uint(10))
}
}
// Benchmark pour la fonction récursive
func BenchmarkFibRecursive(b *testing.B) {
for i := 0; i < b.N; i++ {
fibRecursive(uint(10))
}
}
Exécutons le test pour la position 10 puis augmentons de manière appropriée. Pour exécuter les tests de benchmark, lancez simplement la commande go test -bench=NomDeLaFonction.
Si vous voulez en savoir plus sur cette commande, regardez ici. Vérifions la fonction pour la position 10 :
func BenchmarkFibIterative(b *testing.B) {
for i := 0; i < b.N; i++ {
fibIterative(uint(10))
}
}
go test -bench=BenchmarkFibIterative
Results:
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkFibIterative-8 27715262 42.86 ns/op
PASS
ok playground 2.617s
Analysons à l'aide de cette image :

Selon l'image, nous avons 8 cœurs pour les tests, et pas de limite de temps (il s'exécutera jusqu'à la fin). Il a fallu 27_715_262 itérations et 1,651 secondes pour terminer la tâche.
func BenchmarkFibRecursive(b *testing.B) {
for i := 0; i < b.N; i++ {
fibRecursive(uint(10))
}
}
go test -bench=BenchmarkFibRecursive
Results:
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkFibRecursive-8 6644950 174.3 ns/op
PASS
ok playground 1.819s
En utilisant la même image pour analyser le résultat, dans ce cas, il a fallu 6_644_950 itérations et 1,819 secondes pour terminer la tâche, nous avons :
| Fonction Fibonacci | Position | Itérations | Temps d'exécution (s) |
| Itérative | 10 | 27_715_262 | 1.651 |
| Récursive | 10 | 6_644_950 | 1.819 |
Les résultats du benchmark montrent que l'approche itérative est nettement plus efficace que l'approche récursive pour calculer la suite de Fibonacci.
Pour la position 10, la fonction itérative a effectué environ 27,7 millions d'itérations en 1,651 secondes, tandis que la fonction récursive n'a réussi que 6,6 millions d'itérations en 1,819 secondes. La méthode itérative a surpassé la méthode récursive tant en termes d'itérations que de temps, soulignant son efficacité.
Pour prouver cela encore plus loin, essayons avec la position 40 (4 fois la valeur précédente) :
// Résultats pour la fonction itérative
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkFibIterative-8 9904401 114.5 ns/op
PASS
ok playground 1.741s
// Résultats pour la fonction récursive
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkFibRecursive-8 4 324133575 ns/op
PASS
ok playground 3.782s
| Fonction Fibonacci | Position | Itérations | Temps d'exécution (s) |
| Itérative | 40 | 9_904_401 | 1.741 |
| Récursive | 40 | 4 | 3.782 |
Les résultats du benchmark soulignent à nouveau clairement la différence d'efficacité entre les approches itérative et récursive pour le calcul de Fibonacci.
La fonction itérative a terminé environ 9,9 millions d'itérations avec un temps d'exécution moyen de 114,5 nanosecondes par opération, finissant le benchmark en 1,741 secondes. En revanche, la fonction récursive n'a effectué que 4 itérations avec un temps d'exécution moyen de 324 133 575 nanosecondes par opération (plus de 324 millisecondes par appel), prenant 3,782 secondes pour finir.
Ces résultats démontrent que l'approche récursive est beaucoup moins efficace en raison des appels de fonction répétés et des recalculs, ce qui rend la méthode itérative largement supérieure tant en vitesse qu'en utilisation des ressources, surtout à mesure que la taille de l'entrée augmente.
Juste par curiosité, j'ai essayé la position 60 et cela a littéralement fait planter le test :
// Résultats pour la fonction itérative
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkFibIterative-8 7100899 160.9 ns/op
// Résultats pour la fonction récursive
SIGQUIT: quit
PC=0x7ff81935f08e m=0 sigcode=0
goroutine 0 gp=0x3bf1800 m=0 mp=0x3bf26a0 [idle]:
runtime.pthread_cond_wait(0x3bf2be0, 0x3bf2ba0)
...
Conclusion
Si votre code de production s'exécute lentement ou est imprévisiblement lent, vous pouvez utiliser cette technique, combinée avec pprof ou d'autres outils du package testing intégré, pour identifier et tester les endroits où votre code est peu performant et travailler sur la façon de l'optimiser.
Rappelez-vous : un code qui est beau à regarder n'est pas nécessairement le plus performant.
Référence
Devoirs
Cet article explique pourquoi pour certains petits nombres, la stratégie récursive est meilleure. Pouvez-vous trouver un meilleur moyen d'améliorer la fonction récursive ? (Astuce : utilisez la programmation dynamique).