La performance est un élément clé dans tout; nous (les humains) n’aimons pas attendre et perdre notre temps. Par conséquent, les solutions rapides, comme le supposent la plupart des gestionnaires, sont parfois meilleures que les solutions lentes, mais avec une bonne ingénierie et une bonne conception. Mais aujourd’hui, on ne parle pas de gestion mais sur les performances du code. Nous avons une petite bibliothèque de formatage de texte qui nous permet de formater du texte à l’aide d’un modèle de manière pratique à notre vue. Non seulement ces fonctions de formatage font quoi fmt.Sprintf
le fait de manière plus pratique, mais fournit également des fonctionnalités supplémentaires. Les versions précédentes de notre module manquaient de performances pour fmt.Sprintf
mais depuis 1.0.1, nous sommes meilleurs. Et aujourd’hui, nous allons vous dire comment faire fonctionner n’importe quel code golang plus rapidement.
Paramètres et valeurs de retour
Il existe deux options pour passer l’un ou l’autre des arguments et/ou obtenir le résultat de la fonction – par pointeur et par valeur ; considérez l’exemple suivant :
func getItemAsStr(item *interface{}) string // 1st variant
func getItemAsStr(item interface{}) string // 2nd variant
func getItemAsStr(item *interface{}) *string // 3rd variant
func getItemAsStr(item interface{}) *string // 4th variant
Selon nos tests de performance, nous avons pu conclure ce qui suit :
- Nous avons une petite augmentation des performances lorsque nous passons des arguments par pointeur car nous nous sommes débarrassés de la copie d’arguments.
- Nous avons une diminution des performances lorsque nous renvoyons un pointeur vers la variable locale de la fonction.
Par conséquent, la variante la plus optimale est la première variante.
Cordes
Les chaînes sont immuables comme dans de nombreux autres langages de programmation ; par conséquent, la concaténation de chaînes à l’aide de l’opérateur + est une mauvaise idée, c’est-à-dire qu’un code comme celui-ci est très lent :
var result string = ""
for _, arg := range args { result += getItemAsStr(&arg)
}
Chaque « + » crée un nouvel objet chaîne ; par conséquent, l’utilisation de la mémoire augmente et les performances diminuent considérablement en raison du temps consacré aux allocations à de nouvelles variables.
Il existe une meilleure solution pour la chaîne Concat – utilisez strings.Builder, mais pour de meilleures performances, vous devez d’abord agrandir le tampon (en utilisant Grow
fonction) pour l’empêcher de certaines/toutes les réallocations, mais si la taille du tampon est très grande, il y aura une pénalité pour les performances car l’allocation de mémoire initiale sera lente ; par conséquent, vous devez choisir judicieusement la taille du tampon initial, c’est-à-dire :
var formattedStr = &strings.Builder{}
formattedStr.Grow(templateLen + 22*len(args))
Cycles
Il existe deux manières de parcourir la collection :
for _, arg := range args { // do some staff here }
- Utilisation traditionnelle
for
avec trois expressions :
for i := start; i < templateLen; i++ { // do some staff here }
La deuxième variante est plus performante. Mais il y a des avantages supplémentaires à l’utilisation for
trois expressions :
- Réduire le nombre d’itérations ; plus le code d’itération est plus lent ; par conséquent, si vous pouviez affecter la valeur de votre boucle, faites-le. Ceci ne peut être réalisé avec
range
; - Définissez la valeur initiale de l’itération beaucoup plus élevée si cela est possible, c’est-à-dire dans notre cas :
start := strings.Index(template, "{") if start < 0 { return template }
formattedStr.WriteString(template[:start]) for i := start; i < templateLen; i++ { // iterate over i }
Conclusion
En utilisant des techniques aussi simples, nous avons fait en sorte que notre code s’exécute 1,5 fois plus vite qu’il ne l’était, et maintenant il fonctionne même plus vite que fmt.Sprintf, voir nos mesures de performances :