Maison  >  Article  >  développement back-end  >  Aller : notes d'optimisation simples

Aller : notes d'optimisation simples

Go语言进阶学习
Go语言进阶学习avant
2023-07-21 13:04:42906parcourir
À l'ère du cloud computing, nous créons souvent des applications sans serveur (un modèle de développement cloud natif qui permet aux développeurs de créer et d'exécuter des applications sans gérer de serveurs). Lorsque nos projets adopteront ce modèle, le budget d’entretien des infrastructures sera en tête de liste. Si la charge sur notre service est faible, c'est pratiquement gratuit. Mais si quelque chose ne va pas, vous le paierez cher ! Lorsqu’il s’agit d’argent, vous êtes forcément amené à réagir d’une manière ou d’une autre.

Lorsque votre VPS exécute plusieurs applications de service, mais que l'une d'elles occupe parfois toutes les ressources, ce qui rend impossible l'accès au serveur via ssh. Vous passez à l’utilisation d’un cluster Kubernetes et définissez des limites pour toutes les applications. Nous avons ensuite vu certaines applications redémarrer alors que le OOM-killer résolvait le problème de « fuite » de mémoire.

Bien sûr, le MOO n'est pas toujours un problème de fuite, cela peut aussi être un dépassement de ressources. Les problèmes de fuite sont très probablement causés par des erreurs de programme. Le sujet dont nous parlons aujourd'hui est de savoir comment éviter cette situation.

Une consommation excessive de ressources nuit au portefeuille, ce qui signifie que nous devons prendre des mesures immédiates.

Ne pas optimiser prématurément

Parlons maintenant de l'optimisation. J'espère que vous comprendrez pourquoi nous ne devrions pas optimiser prématurément !

  • Premièrement, l'optimisation peut être un travail inutile. Parce que nous devrions d’abord étudier l’ensemble de l’application, et votre code ne sera probablement pas le goulot d’étranglement. Ce dont nous avons besoin, ce sont des résultats rapides, MVP (Minimum Viable Product, minimum viable product), et ensuite nous examinerons ses problèmes.
  • Deuxièmement, l'optimisation doit être basée sur une base. C’est-à-dire que chaque optimisation doit être basée sur un benchmark, et nous devons prouver quel profit elle nous apporte.
  • Troisièmement, l'optimisation peut apporter de la complexité. Ce que vous devez savoir, c'est que la plupart des optimisations rendent votre code moins lisible. Vous devez trouver cet équilibre.

Suggestions d'optimisation

Nous donnons maintenant quelques suggestions pratiques selon la classification standard des entités dans Go.

1. Tableaux et tranches

Allouez de la mémoire pour les tranches à l'avance

Essayez d'utiliser le troisième paramètre : <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #000000;background: rgba(14, 210, 247, 0.15);"><span style="font-size: 15px;">make([]T, 0, len)</span>make([]T, 0, len)

Si le nombre exact d'éléments n'est pas connu et la tranche est de courte durée Oui, vous pouvez allouer une taille plus grande pour garantir que la tranche ne s'agrandit pas pendant l'exécution.

N'oubliez pas d'utiliser copy

Essayez de ne pas utiliser append lors de la copie, par exemple lors de la fusion de deux tranches ou plus.

Itération correcte

Une tranche contenant de nombreux éléments ou de gros éléments, utilisez pour pour obtenir un seul élément. De cette façon, les duplications inutiles seront évitées.

Multiplexage de tranches

Si une opération est effectuée sur la tranche entrante et renvoie un résultat modifié, nous pouvons le renvoyer. Cela évite une nouvelle allocation de mémoire.

🎜
Ne laissez pas une partie inutilisée de la tranche

Si vous devez couper un petit morceau de la tranche et l'utiliser uniquement, la partie principale de la tranche sera également conservée. L'approche correcte consiste à utiliser une nouvelle copie de cette petite tranche et à lancer l'ancienne tranche au GC.

2. Épissage correct des chaînes

Si l'épissage des chaînes peut être effectué en une seule instruction, utilisez <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #000000;background: rgba(14, 210, 247, 0.15);"><span style="font-size: 15px;">+</span> 操作符。如果需要在循环中执行此操作,使用 <span style="font-size: 15px;">string.Builder</span>,并使用它的 <span style="font-size: 15px;">Grow</span> 方法预先指定 <span style="font-size: 15px;">Builder</span> 的大小,减少内存分配次数。

转换优化

string 和 []byte 在底层结构上非常相近,有时这两种类型之间可以通过强转换来避免内存分配。

字符串驻留

可以池化字符串,从而帮助编译器只存储一次相同的字符串。

避免分配

我们可以使用 map(级联)而不是复合键,我们可以使用字节切片。尽量不使用 <span style="font-size: 15px;">fmt</span>+ opérateur. Si vous devez le faire en boucle, utilisez

🎜string.Builder🎜🎜 et utilisez son 🎜🎜Grow🎜🎜 la méthode est pré-spécifiée🎜🎜Builder🎜🎜 taille pour réduire le nombre d'allocations de mémoire. 🎜🎜🎜Optimisation de la conversion🎜🎜🎜string et []byte sont très similaires dans la structure sous-jacente, et parfois une conversion forte peut être utilisée entre ces deux types pour éviter l'allocation de mémoire. 🎜🎜🎜String résident🎜🎜🎜 peut regrouper des chaînes, aidant ainsi le compilateur à stocker la même chaîne une seule fois. 🎜🎜🎜Éviter l'allocation🎜🎜🎜Nous pouvons utiliser map(cascade) au lieu de clés composites, nous pouvons utiliser des tranches d'octets. Essayez de ne pas utiliser 🎜🎜 fmt 🎜🎜 package, car toutes ses méthodes utilisent la réflexion. 🎜🎜

3. Structure

Évitez de copier de grandes structures

Nous comprenons que les petites structures ne comportent pas plus de 4 champs et pas plus d'une taille de mot machine.

Quelques scénarios de copie typiques

  • Projeté sur l'interface
  • Réception et envoi de canaux
  • Remplacement d'éléments dans la carte
  • Ajouter des éléments aux tranches
  • Itération (plage)
Évitez d'accéder aux champs de structure via des pointeurs

Le déréférencement coûte cher et nous devrions le faire le moins possible, surtout dans les boucles. Il perd également la possibilité d'utiliser des registres rapides.

Manipulation des petites structures

Ce travail est optimisé par l'éditeur, ce qui veut dire qu'il est bon marché.

Utilisez l'alignement pour réduire la taille de la structure

Nous pouvons réduire la taille de la structure elle-même en alignant la structure (en les disposant dans le bon ordre en fonction de la taille des champs).

4. Fonctions

Utilisez des fonctions en ligne ou inlinez-les vous-même

Essayez d'écrire de petites fonctions qui peuvent être intégrées par le compilateur, ce sera plus rapide, encore plus rapide que d'intégrer vous-même le code dans la fonction. Cela est particulièrement vrai pour les chemins chauds.

Lesquels ne seront pas en ligne

  • récupération
  • bloc de sélection
  • déclaration de type
  • defer
  • goroutine
  • pour-gamme
raisonnable Choisissez judicieusement les paramètres de fonction

Essayez d'utiliser de petits paramètres car leur réplication sera optimisée. Essayez de maintenir la réplication et la croissance de la pile équilibrées sur la charge du GC. Évitez un grand nombre de paramètres et laissez votre programme utiliser des registres rapides (leur nombre est limité).

Valeurs de retour nommées

Cela semble être plus efficace que de déclarer ces variables dans le corps de la fonction.

Enregistrez les résultats intermédiaires

Aidez le compilateur à optimiser votre code, enregistrez les résultats intermédiaires, et il y aura ensuite plus d'options pour optimiser votre code.

Utilisez defer avec précaution

Essayez de ne pas utiliser defer, ou du moins ne l'utilisez pas en boucle.

Aide au chemin chaud

Évitez d'allouer de la mémoire dans le chemin chaud, en particulier les objets de courte durée. Créez les branches les plus courantes (si, switch)

5. Map

Allocation de mémoire à l'avance

Identique à la tranche, lors de l'initialisation de la carte, spécifiez sa taille.

Utiliser des structures vides comme valeurs

struct{} n'est rien (ne prend pas de mémoire), il est donc très avantageux de l'utiliser lors du passage de signaux par exemple.

Effacer la carte

la carte ne peut que croître, pas rétrécir. Lorsque nous devons réinitialiser la carte, supprimer tous ses éléments ne nous aidera pas.

Essayez de ne pas utiliser de pointeurs dans les clés et les valeurs

Si la carte ne contient pas de pointeurs, alors le GC ne perdra pas de temps précieux dessus. Les chaînes utilisent également des pointeurs, vous devez donc utiliser des tableaux d'octets au lieu de chaînes comme clés.

Réduire le nombre de modifications

De même, nous ne voulons pas utiliser de pointeurs, mais nous pouvons utiliser une combinaison de carte et de tranche, en stockant les clés dans la carte et les valeurs dans la tranche. De cette façon, nous pouvons modifier la valeur sans restrictions.

6. Interface

Calculer l'allocation de mémoire

N'oubliez pas que lorsque vous souhaitez attribuer une valeur à une interface, vous devez d'abord la copier quelque part, puis y coller le pointeur. La clé est de copier. Il s'avère que le coût du boxing et du déballage de l'interface sera à peu près le même que celui de l'allocation de la taille de la structure.

Choisir le type optimal

Dans certains cas, il n'y a pas d'allocation lors du boxing et du unboxing des interfaces. Par exemple, des valeurs petites ou booléennes pour les variables et les constantes, des structures avec un champ simple, des pointeurs (y compris carte, canal, fonction)

Évitez l'allocation de mémoire

Comme ailleurs, essayez d'éviter les allocations inutiles. Par exemple attribuer une interface à une autre au lieu de boxer deux fois.

À utiliser uniquement en cas de besoin

Évitez d'utiliser des interfaces dans les paramètres de fonction fréquemment appelés et renvoyez les résultats. Nous n'avons pas besoin d'opérations de déballage supplémentaires. Réduisez la fréquence d’utilisation des appels de méthode d’interface car cela empêche l’inline.

7. Pointeurs, canaux, vérifications de limites

Évitez les déréférencements inutiles

Surtout dans les boucles car cela s'avère trop coûteux. Le déréférencement est quelque chose que nous ne voulons pas faire à nos propres frais.

L'utilisation des canaux est inefficace

La synchronisation des canaux est plus lente que les autres méthodes primitives de synchronisation. De plus, plus il y a de cas sélectionnés, plus notre programme sera lent. Cependant, select, case et default ont été optimisés.

Évitez les contrôles de limites inutiles

Cela coûte également cher et nous devrions l'éviter. Par exemple, vérifiez (obtenez) l’index de tranche maximum une seule fois, et non plusieurs fois. Il est préférable d'essayer d'opter pour l'option extrême maintenant.

Résumé

Tout au long de cet article, nous avons vu certaines des mêmes règles d'optimisation.

Aidez le compilateur à prendre la bonne décision et il vous remerciera. Allouez de la mémoire au moment de la compilation, utilisez des résultats intermédiaires et essayez de garder votre code lisible.

Je réitère que pour une optimisation implicite, les benchmarks sont obligatoires. Si quelque chose qui a fonctionné hier ne fonctionnera pas demain car le compilateur change trop rapidement entre les versions, et vice versa.

N'oubliez pas d'utiliser les outils d'analyse et de suivi intégrés de Go.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer