Go: net / http: le serveur HTTP / 1 a été 0,5 μs plus lent dans Go 1.8

Créé le 6 févr. 2017  ·  35Commentaires  ·  Source: golang/go

Veuillez répondre à ces questions avant de soumettre votre problème. Merci!

Quelle version de Go utilisez-vous ( go version )?

Allez 1.7.5, 1.8 rc3 et git

Quel système d'exploitation et quelle architecture de processeur utilisez-vous ( go env )?

Arch Linux 64 bits

Qu'est-ce que tu as fait?

go run https://gist.github.com/OneOfOne/4d7e13977886ddab825870bc3422a901
changer de terminal et exécuter wrk -c 20 -d 30s http://localhost:8081

Qu'espériez-vous voir?

Même débit que 1.7 ou plus rapide.

Qu'avez-vous vu à la place?

allez tip fd37b8ccf2

Running 30s test @ http://localhost:8081
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   195.30us  470.12us  16.30ms   95.11%
    Req/Sec    85.62k     6.00k   95.21k    80.83%
  5110713 requests in 30.01s, 721.35MB read
Requests/sec: 170322.67
Transfer/sec:     24.04MB

aller 1.8rc3 758a7281ab

Running 30s test @ http://localhost:8081
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   192.49us  451.74us  15.14ms   95.02%
    Req/Sec    85.91k     6.37k   97.60k    83.50%
  5130079 requests in 30.01s, 724.08MB read
Requests/sec: 170941.23
Transfer/sec:     24.13MB

aller 1.7.5

Running 30s test @ http://localhost:8081
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   210.16us  528.53us  14.78ms   94.13%
    Req/Sec    94.34k     4.31k  103.56k    73.83%
  5631803 requests in 30.01s, 794.89MB read
Requests/sec: 187673.03
Transfer/sec:     26.49MB
FrozenDueToAge NeedsInvestigation Performance help wanted

Commentaire le plus utile

Salut @bradfitz

Juste une petite question d'un étranger, alors s'il vous plaît soyez patient si j'ai raté quelque chose d'évident.
Pourquoi serait-il trop tard pour résoudre ce problème? N'est-ce pas là toute la raison de la libération des candidats? Pour trouver les derniers problèmes majeurs avant de publier la prochaine version majeure?

Sinon, renseignez-moi.

Aussi, merci d'avoir travaillé sur ce projet, je connais beaucoup de gens qui aiment cette langue et la communauté qui l'entoure. 🎉

Tous les 35 commentaires

Trop tard pour Go 1.8, mais nous pouvons examiner les performances pendant Go 1.9.

Quelqu'un veut-il enquêter sur la différence? Que dit un profil de processeur Go 1.7 vs Go 1.8?

Jeu mis à jour avec pprof: https://play.golang.org/p/GZ4zQOg1Wf

J'ai couru go tool pprof http://localhost:6060/debug/pprof/profile , changé les termes et exécuté wrk -c 20 -d 30s http://localhost:6060/ .

aller pourboire

(pprof) top
36600ms of 80000ms total (45.75%)
Dropped 297 nodes (cum <= 400ms)
Showing top 10 nodes out of 135 (cum >= 970ms)
      flat  flat%   sum%        cum   cum%
   26360ms 32.95% 32.95%    27340ms 34.17%  syscall.Syscall
    2280ms  2.85% 35.80%     5740ms  7.17%  runtime.mallocgc
    1310ms  1.64% 37.44%     1310ms  1.64%  runtime.heapBitsSetType
    1260ms  1.57% 39.01%     1260ms  1.57%  runtime._ExternalCode
    1030ms  1.29% 40.30%     7280ms  9.10%  net/http.(*chunkWriter).writeHeader
     970ms  1.21% 41.51%      970ms  1.21%  runtime.epollwait
     900ms  1.12% 42.64%      920ms  1.15%  runtime.mapiternext
     880ms  1.10% 43.74%      880ms  1.10%  runtime.usleep
     820ms  1.03% 44.76%     1490ms  1.86%  runtime.deferreturn
     790ms  0.99% 45.75%      970ms  1.21%  runtime.mapaccess1_faststr
(pprof) top -cum
27.89s of 80s total (34.86%)
Dropped 297 nodes (cum <= 0.40s)
Showing top 10 nodes out of 135 (cum >= 23.44s)
      flat  flat%   sum%        cum   cum%
     0.01s 0.013% 0.013%     73.46s 91.83%  runtime.goexit
     0.55s  0.69%   0.7%     69.55s 86.94%  net/http.(*conn).serve
     0.30s  0.38%  1.07%     35.91s 44.89%  net/http.(*response).finishRequest
     0.15s  0.19%  1.26%     32.10s 40.12%  bufio.(*Writer).Flush
    26.36s 32.95% 34.21%     27.34s 34.17%  syscall.Syscall
     0.10s  0.12% 34.34%     24.56s 30.70%  net/http.checkConnErrorWriter.Write
         0     0% 34.34%     24.44s 30.55%  net.(*conn).Write
     0.23s  0.29% 34.62%     24.44s 30.55%  net.(*netFD).Write
     0.06s 0.075% 34.70%     23.50s 29.38%  syscall.Write
     0.13s  0.16% 34.86%     23.44s 29.30%  syscall.write

aller 1.7.5

(pprof) top
40520ms of 82240ms total (49.27%)
Dropped 281 nodes (cum <= 411.20ms)
Showing top 10 nodes out of 128 (cum >= 860ms)
      flat  flat%   sum%        cum   cum%
   29480ms 35.85% 35.85%    30920ms 37.60%  syscall.Syscall
    2550ms  3.10% 38.95%     5710ms  6.94%  runtime.mallocgc
    1560ms  1.90% 40.84%     1590ms  1.93%  runtime.heapBitsSetType
    1220ms  1.48% 42.33%     1220ms  1.48%  runtime.epollwait
    1050ms  1.28% 43.60%     2750ms  3.34%  runtime.mapassign1
    1050ms  1.28% 44.88%     1080ms  1.31%  runtime.mapiternext
    1000ms  1.22% 46.10%     7890ms  9.59%  net/http.(*chunkWriter).writeHeader
     880ms  1.07% 47.17%     2910ms  3.54%  net/http.DetectContentType
     870ms  1.06% 48.22%     1010ms  1.23%  runtime.mapaccess1_faststr
     860ms  1.05% 49.27%      860ms  1.05%  runtime.futex
(pprof) top -cum
31.67s of 82.24s total (38.51%)
Dropped 281 nodes (cum <= 0.41s)
Showing top 10 nodes out of 128 (cum >= 27.69s)
      flat  flat%   sum%        cum   cum%
         0     0%     0%     75.77s 92.13%  runtime.goexit
     0.44s  0.54%  0.54%     74.26s 90.30%  net/http.(*conn).serve
     0.27s  0.33%  0.86%     37.08s 45.09%  net/http.(*response).finishRequest
     0.18s  0.22%  1.08%     36.44s 44.31%  bufio.(*Writer).Flush
     0.25s   0.3%  1.39%     36.26s 44.09%  bufio.(*Writer).flush
    29.48s 35.85% 37.23%     30.92s 37.60%  syscall.Syscall
     0.12s  0.15% 37.38%     27.99s 34.03%  net/http.checkConnErrorWriter.Write
     0.69s  0.84% 38.22%     27.85s 33.86%  net/http.(*conn).readRequest
     0.08s 0.097% 38.31%     27.77s 33.77%  net.(*conn).Write
     0.16s  0.19% 38.51%     27.69s 33.67%  net.(*netFD).Write

Faites-moi savoir si vous voulez que je fasse quelque chose de spécifique.

Aïe, je vois la même chose avec un ralentissement d'environ 20% sur mon benchmark de serveur Web avec 1.8r3, et une taille binaire légèrement plus grande aussi

Je me rends compte qu'il est tard dans le cycle de publication, mais une régression de 20% sur http est une énorme régression pour beaucoup de gens.

Je suis prêt à aider au débogage si vous me dites ce qui est nécessaire @bradfitz.

Je suis prêt à aider au débogage si vous me dites ce qui est nécessaire @bradfitz.

L'étape 1 consiste à déboguer la raison pour laquelle il est devenu plus lent. Si vous trouvez des indices, faites-le moi savoir.

@OneOfOne comment cela fonctionne-t-il pour les applications autres que Hello World? Y voyez-vous des problèmes? Peut-être que 20% pour les applications Hello World sont acceptables jusqu'à la 1.9?

@bradfitz Je pense que la fonctionnalité pour Shutdown entraîne une baisse des performances pour le type d'applications / tests «bonjour le monde».

            select {
            case <-srv.getDoneChan():
return ErrServerClosed
//....

Regardez ici .

À 1,7, c'était juste une boucle for .

Quelqu'un peut-il confirmer mon hypothèse?

Merci,
kataras

Je ne suis pas en mesure de reproduire les résultats du PO. Je suis sur un Mac et j'utilise des versions plus anciennes de Go.

$ wrk -c 20 -d 30s http://localhost:8081  # go 1.8 RC2
Running 30s test @ http://localhost:8081
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.87ms    6.99ms 151.45ms   99.38%
    Req/Sec    25.22k     2.34k   33.28k    66.94%
  1510655 requests in 30.10s, 213.22MB read
Requests/sec:  50188.34
Transfer/sec:      7.08MB

$ wrk -c 20 -d 30s http://localhost:8081  # go 1.7.4
Running 30s test @ http://localhost:8081
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   840.73us    6.85ms 151.46ms   99.41%
    Req/Sec    26.05k     3.67k   33.43k    60.96%
  1560770 requests in 30.10s, 220.29MB read
Requests/sec:  51853.50
Transfer/sec:      7.32MB

Il y a une légère différence mais elle est inférieure à 20%, presque négligeable ici.

@kataras , avez-vous des preuves en faveur de cette théorie?

Vous pouvez le confirmer vous-même: supprimez ces lignes et mesurez à nouveau.

Je serais surpris, cependant.

@kataras On dirait que c'est probablement ça. En plus de la sélection, vous avez une acquisition mutex et un déverrouillage qui se fait dans un différé (qui, je sais, a récemment été accéléré, mais qui sont toujours un peu plus lents qu'un déverrouillage direct.

func (s *Server) getDoneChan() <-chan struct{} {
    s.mu.Lock()
    defer s.mu.Unlock()
    return s.getDoneChanLocked()
}

Puisque la boucle d'acceptation est un chemin si chaud, il peut être intéressant de déplacer la sélection d'arrêt dans un goroutine séparé et d'utiliser sync / atomic pour signaler l'arrêt dans la boucle d'acceptation.

ÉDITER:
Je ne pense pas que ce soit juste ça, j'ai juste essayé la pointe sans sélectionner tous ensemble et cela ajoute environ 7us (5% ~).

Je comprends que c'est trop tard pour la 1.8, mais pourquoi ne pas peut-être résoudre ce problème dans une future version 1.8.1?

Quelqu'un d'autre a-t-il même pu reproduire ces résultats?

-11,2% sur Debian 8, amd64 (1.7.5 et rc3)

Running 30s test @ http://localhost:8081
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   457.65us    1.26ms  46.74ms   93.23%
    Req/Sec    65.08k     7.84k   99.65k    73.83%
  3891443 requests in 30.07s, 549.25MB read
Requests/sec: 129397.13
Transfer/sec:     18.26MB

Running 30s test @ http://localhost:8081
  2 threads and 20 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   350.93us    0.92ms  39.50ms   94.72%
    Req/Sec    57.70k     5.34k   77.31k    74.50%
  3447358 requests in 30.03s, 486.57MB read
Requests/sec: 114800.24
Transfer/sec:     16.20MB

@kataras L'implémentation est correcte, ce qui signifie que l'auteur a été prudent ... ce n'est pas si grave que des bugs se produisent dans les logiciels.

Cela dit, j'avais tort de toute façon, j'ai regardé à nouveau tout à l'heure et j'ai réalisé que la sélection ne se produit que lorsqu'une erreur se produit lors de l'acceptation. Ce qui est étrange, c'est comment cela a ralenti mon programme de manière constante lorsque je le supprime de l'acceptation. Peut-être que cela disqualifie ce bloc de certaines passes d'optimisation ou de l'intégration? Il peut y avoir un problème quelque part provoquant un ralentissement, mais il est ailleurs et nécessite une configuration différente de la mienne.

@bradfitz a brièvement divisé cela en deux et semble commettre faf882d1d427e8c8a9a1be00d8ddcab81d1e848e en est à l'origine au moins en partie. Je constate une baisse de performance d'environ 10% sur ce benchmark avec ce commit par rapport au commit qui le précède.

@codido , merci pour la bissectrice! Oui, et c'est le commit que je soupçonnerais aussi. Bon à voir la confirmation. Ce fut l'un des plus gros changements architecturaux du net / http.Server depuis un certain temps.

Je n'ai jamais fait de benchmarking (ou d'optimisation) après ce changement.

Nous pouvons l'examiner pour Go 1.9.

Salut @bradfitz

Juste une petite question d'un étranger, alors s'il vous plaît soyez patient si j'ai raté quelque chose d'évident.
Pourquoi serait-il trop tard pour résoudre ce problème? N'est-ce pas là toute la raison de la libération des candidats? Pour trouver les derniers problèmes majeurs avant de publier la prochaine version majeure?

Sinon, renseignez-moi.

Aussi, merci d'avoir travaillé sur ce projet, je connais beaucoup de gens qui aiment cette langue et la communauté qui l'entoure. 🎉

Pourquoi les gens répondent-ils et votent-ils comme s'il s'agissait d'un problème majeur? Ce changement semble avoir un impact sur les performances dans le pire des cas d'environ 40us par demande. Cela me semble très bas, y a-t-il un scénario dans le monde réel où cela aurait de l'importance?

(modifier: je me suis trompé, c'est ~ 0,5us par demande, donc encore plus bas)

Je pense qu'il faudra peut-être plus de repères à ce sujet pour voir l'effet sur des choses autres que bonjour le monde. Des choses comme hello world exercent une forte pression de référence sur les composants internes et peuvent en tant que telles amplifier les effets des ralentissements. Dans une application du monde réel, il y a une charge de bateau plus de code qui s'exécute, ce qui en théorie rend l'effet de choses comme celle-ci beaucoup plus petit par requête - c'est-à-dire qu'il devient moins d'un% par requête, ce qui signifie que l'effet est réduit.
Juste mes 2 cents.
(Je vais regarder précisément cela plus tard si j'ai le temps)

@reimertz Je me souviens avoir lu sur le cycle de publication ici: https://github.com/golang/go/wiki/Go-Release-Cycle

Une fois qu'une version candidate est publiée, seuls les changements de documentation et les changements pour résoudre les bogues critiques doivent être effectués. En général, la barre pour les corrections de bogues à ce stade est même légèrement plus élevée que la barre pour les corrections de bogues dans une version mineure. Nous préférons peut-être publier une version avec un crash connu mais très rare plutôt qu'une version avec un nouveau correctif mais non testé en production.

L'un des critères pour émettre une version candidate est que Google utilise cette version du code pour les nouvelles versions de production par défaut: si chez Google, nous ne sommes pas disposés à l'exécuter pour une utilisation en production, nous ne devrions pas demander aux autres de le faire.

@kmlx merci pour le lien! Je suis seulement allé ici: https://golang.org/doc/contribute.html et j'ai recherché 'release' et je n'ai rien trouvé. Ma faute.

Aussi, wow! Si Google exécute cette version sur ses serveurs de production et accepte un impact sur les performances dans le pire des cas d'environ 40us par requête (en citant @tinco ), je suppose que le reste du monde le peut aussi. 🙂

C'est pourquoi nous demandons aux gens de tester les versions bêta. Ainsi, s'ils croient avoir trouvé un problème, ils peuvent se plaindre tôt.

go1.8beta1 est sorti le 1 décembre 2016 , il y a plus de 2 mois:

https://groups.google.com/d/topic/golang-nuts/QYuo0fai6YE/discussion

Citant l'annonce:

Il est important de trouver des bogues avant de publier une version candidate.
La version candidate est prévue pour la première semaine de janvier.
Votre aide pour tester cette version bêta est inestimable.

Désolé, j'ai mal regardé les chiffres. Donc, d' après le test de go tip fait 5110713 requêtes en 30 secondes, soit 5,87us par requête. Go 1.7.5 a fait 5631803 requêtes en 30 secondes, 5,33us par requête. Donc, lorsque vous les comparez les uns aux autres, c'est comme une baisse de performance de 11%. Mais si vous regardez les choses d'un point de vue absolu, cela représente une performance de seulement une demi-microseconde par requête. Je ne peux même pas imaginer un service HTTP où cela serait pertinent.

@tinco Je suis d'accord, c'est une régression très mineure quand on la met en perspective. Cependant, il est toujours très important de comprendre pourquoi il a régressé. À moins que vous ne souhaitiez une situation comme: # 6853 et Go 1.9 se présente avec une autre diminution de 11%.

Cela étant dit, je ne sais pas pourquoi cela n'a pas pu être corrigé avec une version de correctif (par rapport à une version mineure).

@tinco , combien de cœurs cet ordinateur a-t-il? multipliez 0,5us par le nombre de cœurs.

Le mercredi 8 février 2017 à 16:13, Sokolov Yura [email protected]
a écrit:

combien de cœurs cet ordinateur a-t-il? multipliez 0,5us par le nombre de cœurs.

Pourquoi le nombre de cœurs a-t-il à voir avec cela?
chaque requête n'est traitée que par un seul cœur.

@minux les 5 millions de requêtes sont traitées en 30 secondes par N cœurs. Ainsi, le temps réel passé par requête sur chaque cœur est de 30 / 5M * N.N est probablement assez petit, moins de 10 probablement donc ce n'est pas vraiment pertinent.

Les versions mineures concernent des bogues critiques et inévitables. Votre programme a 1% de chances de planter au hasard après une journée d'exécution, c'est le genre de bogue que nous corrigeons dans une version ponctuelle, avec le correctif le plus simple, le plus sûr et le plus trivial possible. Les versions mineures ne sont pas destinées à corriger "votre programme s'exécute 0,5us plus lentement pour exécuter une opération qui prend probablement beaucoup plus de temps de toute façon".

Je suppose que vous faites référence aux versions de correctifs ( 1.8.x ) vs mineures ( 1.x.0 ).
OMI, le but des versions de correctifs était d'aborder les régressions sans modifier les fonctionnalités ni aucun comportement. Alors que celui-ci ne ressemble pas à un gros, je ne vois aucune raison de ne pas le réparer dans un 1.8.patch .

Dans le projet Go, nous nous référons à 1.x comme une version majeure et 1.xy comme une version mineure (ou parfois ponctuelle): Go 1.8 est une version majeure; Go 1.8.2 est une version mineure ou ponctuelle. Cela n'a aucun sens pour nous d'appeler Go 1.8 une version mineure.

La politique de publication du projet Go est documentée à l' adresse https://golang.org/doc/devel/release.html#policy :

Chaque version majeure de Go devient obsolète et met fin à la prise en charge de la précédente. Par exemple, si Go 1.5 a été publié, il s'agit de la version actuelle et Go 1.4 et les versions antérieures ne sont plus prises en charge. Nous corrigeons les problèmes critiques dans la version actuelle selon les besoins en publiant des révisions mineures (par exemple, Go 1.5.1, Go 1.5.2, etc.).

Go 1.5 est rétrocompatible avec Go 1.4 avec quelques mises en garde: votre code ne doit pas dépendre d'un comportement non documenté (par exemple, l'ordre des éléments égaux choisis par sort.Sort), votre code doit utiliser des littéraux de struct clé, donc il ne le fait pas break si de nouveaux champs struct sont ajoutés, et ainsi de suite .

Dans toute la mesure du possible, Go 1.5.1 est rétrocompatible avec Go 1.5 sans aucune mise en garde: nous visons la mise à jour de Go 1.5 vers Go 1.5.1 pour être une opération de sécurité garantie, un non-événement, comme vous dites " sans modifier les fonctionnalités ni aucun comportement ».

Accepter que faire des erreurs est une partie inévitable de l'écriture d'un logiciel et que chaque fois que vous apportez un changement, il y a un risque qu'il introduise un bogue qui n'est pas détecté par les tests avant la sortie, la meilleure façon que nous connaissons de réduire ce risque est de refuser changements non critiques dans les versions ponctuelles.

La correction d'un ralentissement de 0,5us qui n'apparaît que dans les microbenchmarks est un changement non critique.

@tinco si nous ne considérons pas ce problème de performance "soi-disant mineur" et chaque fois que nous l'ignorons, nous obtiendrons finalement "un coup plus lent". OMI, personne ne veut que cela se produise.

@ rashidul0405 Ce problème est toujours ouvert; personne ne l'ignore. J'expliquais pourquoi cela ne méritait pas une solution précipitée dans Go 1.8 ou Go 1.8.1.

Je prendrais volontiers un Go 1% plus lent au lieu d'un Go 1% plus crash.

Continuons la discussion sur les listes de diffusion et Twitter, etc.

Veuillez ne commenter ici que si vous travaillez sur ce problème.

Puisque personne ne semble vouloir travailler là-dessus, je vais le fermer. Nous ne pouvons pas rechercher tous les détails de performance possibles, et celui-ci vieillit.

Cette page vous a été utile?
0 / 5 - 0 notes