Go: net / http: servidor HTTP / 1 ficou 0,5 μs mais lento no Go 1.8

Criado em 6 fev. 2017  ·  35Comentários  ·  Fonte: golang/go

Responda a essas perguntas antes de enviar seu problema. Obrigado!

Qual versão do Go você está usando ( go version )?

Go 1.7.5, 1.8 rc3 e git

Qual sistema operacional e arquitetura de processador você está usando ( go env )?

Arch Linux 64 bits

O que você fez?

go run https://gist.github.com/OneOfOne/4d7e13977886ddab825870bc3422a901
trocar de terminais e executar wrk -c 20 -d 30s http://localhost:8081

O que você esperava ver?

Mesma taxa de transferência de 1,7 ou mais rápido.

O que você viu em vez disso?

go 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

go 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

vá 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

Comentários muito úteis

Oi @bradfitz

Apenas uma pergunta rápida de alguém de fora, então, por favor, tenha paciência se eu perdi algo óbvio.
Por que seria tarde demais para corrigir esse problema? Não é essa a razão para candidatos a lançamento? Para encontrar os principais problemas finais antes de lançar a próxima versão principal?

Se não, por favor, me eduque.

Além disso, obrigado por trabalhar neste projeto, conheço muitas pessoas que amam esse idioma e a comunidade ao seu redor. 🎉

Todos 35 comentários

Tarde demais para o Go 1.8, mas podemos analisar o desempenho durante o Go 1.9.

Alguém quer investigar a diferença? O que um perfil de CPU Go 1.7 vs Go 1.8 diz?

Jogo atualizado com pprof: https://play.golang.org/p/GZ4zQOg1Wf

Executei go tool pprof http://localhost:6060/debug/pprof/profile , troquei os termos e executei wrk -c 20 -d 30s http://localhost:6060/ .

vai dica

(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

vá 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

Deixe-me saber se você quiser que eu faça algo específico.

Ai, estou vendo a mesma coisa com cerca de 20% de lentidão no benchmark do meu servidor da web com 1.8r3 e um tamanho binário um pouco maior também

Sei que está atrasado no ciclo de lançamento, mas a regressão de 20% no http é uma regressão enorme para muitas pessoas.

Estou disposto a ajudar a depurar se você me disser o que é necessário @bradfitz.

Estou disposto a ajudar a depurar se você me disser o que é necessário @bradfitz.

A etapa 1 é depurar por que ficou mais lento. Se você encontrar alguma pista, me avise.

@OneOfOne como isso funciona para aplicativos não-hello world? Você vê algum problema aí? Talvez 20% para aplicativos Hello World seja aceitável até 1.9?

@bradfitz Acho que o recurso de Shutdown está causando desempenho inferior para o tipo de aplicativos / testes 'hello world'.

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

Olhe aqui .

Em 1,7 era apenas um loop de for .

Alguém pode confirmar minha suposição?

Obrigado,
kataras

Não consigo reproduzir os resultados do OP. Estou em um Mac e estou usando versões _ um pouco_ mais antigas do 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

Há uma pequena diferença, mas é menor que 20%, quase insignificante aqui.

@kataras , você tem evidências a favor dessa teoria?

Você mesmo pode confirmar: exclua essas linhas e meça novamente.

Eu ficaria surpreso, no entanto.

@kataras Parece que provavelmente é isso. Além do select, você tem uma aquisição mutex e um desbloqueio que é feito em um adiamento (que eu sei que recentemente foi acelerado, mas ainda é um pouco mais lento do que um desbloqueio direto.

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

Visto que o loop de aceitação é um caminho muito ativo, pode valer a pena mover a seleção de desligamento para uma goroutina separada e usar sync / atômico para sinalizar o desligamento no loop de aceitação.

EDITAR:
Acho que não é só isso, tentei apenas o tip sem o select e adiciona cerca de 7us (5% ~).

Eu entendo que é tarde demais para 1.8, mas por que não consertar isso em uma versão 1.8.1 futura?

Alguém mais foi capaz de reproduzir esses resultados?

-11,2% no Debian 8, amd64 (1.7.5 e 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 A implementação está correta, o que significa que o autor foi cuidadoso .. não é grande coisa bugs acontecem em software.

Dito isso, eu estava errado de qualquer maneira, olhei novamente agora há pouco e percebi que o select só acontece quando ocorre um erro durante a aceitação. O que é estranho é como meu programa fica mais lento de forma consistente quando o removo da aceitação. Talvez isso desqualifique aquele bloco de alguns passes de otimização ou inlining? Pode haver um problema em algum lugar causando lentidão, mas está em outro lugar e precisa de uma configuração diferente da minha.

@bradfitz Divisou isso

@codido , obrigado pela

Nunca fiz benchmarking (ou otimizações) depois dessa mudança.

Podemos analisá-lo para o Go 1.9.

Oi @bradfitz

Apenas uma pergunta rápida de alguém de fora, então, por favor, tenha paciência se eu perdi algo óbvio.
Por que seria tarde demais para corrigir esse problema? Não é essa a razão para candidatos a lançamento? Para encontrar os principais problemas finais antes de lançar a próxima versão principal?

Se não, por favor, me eduque.

Além disso, obrigado por trabalhar neste projeto, conheço muitas pessoas que amam esse idioma e a comunidade ao seu redor. 🎉

Por que as pessoas estão respondendo e votando como se esse fosse um problema importante? Essa mudança parece ter um impacto de desempenho no pior caso de aproximadamente 40 us por solicitação. Isso soa muito baixo para mim, há um cenário do mundo real onde isso importaria?

(editar: eu estava errado, é ~ 0,5us por solicitação, então ainda mais baixo)

Acho que pode ser necessário haver mais benchmarks em torno disso para ver o efeito em outras coisas além do hello world. Coisas como hello world colocam muita pressão de benchmark nas partes internas e, como tal, podem ampliar os efeitos da lentidão. Em um aplicativo do mundo real, há um carregamento de mais código executado, o que, em teoria, torna o efeito de coisas como essa muito menor por solicitação - ou seja, torna-se menos de uma% por solicitação, o que significa que o efeito é reduzido.
Apenas meus 2 centavos.
(Vou examinar precisamente isso mais tarde, se tiver tempo)

@reimertz Lembro-me de ter lido sobre o ciclo de lançamento aqui: https://github.com/golang/go/wiki/Go-Release-Cycle

Depois que um candidato a lançamento é emitido, apenas alterações na documentação e alterações para resolver bugs críticos devem ser feitas. Em geral, a barra para correções de bugs neste ponto é ainda um pouco mais alta do que a barra para correções de bugs em uma versão menor. Podemos preferir lançar um lançamento com uma falha conhecida, mas muito rara, do que lançar um lançamento com uma correção nova, mas não testada em produção.

Um dos critérios para a emissão de um candidato a lançamento é que o Google use essa versão do código para novas compilações de produção por padrão: se nós do Google não estamos dispostos a executá-lo para uso em produção, não deveríamos pedir a outros.

@kmlx obrigado pelo link! Eu só fui aqui: https://golang.org/doc/contribute.html e procurei por 'release' e não consegui encontrar nada. Foi mal.

Além disso, uau! Se o Google vai executar esta versão em seus servidores de produção e está tudo bem com um impacto de desempenho de pior caso de ~ 40us por solicitação (citando @tinco ), acho que o resto do mundo pode também. 🙂

É por isso que pedimos às pessoas que testem as versões beta. Para que, se acreditarem que encontraram um problema, possam reclamar o quanto antes.

go1.8beta1 foi lançado em 1 de dezembro de 2016 , há mais de 2 meses:

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

Citando o anúncio:

É importante encontrarmos bugs antes de emitir um candidato a lançamento.
O candidato a lançamento está previsto para a primeira semana de janeiro.
Sua ajuda no teste deste beta é inestimável.

Desculpe, olhei para os números errados. Portanto, no teste de go tip fez 5110713 solicitações em 30 segundos, ou seja, 5,87 us por solicitação. Go 1.7.5 fez 5631803 solicitações em 30 segundos, 5,33 us por solicitação. Então, quando você os compara, é uma diminuição de 11% no desempenho. Mas se você olhar de uma perspectiva absoluta, é um impacto de desempenho de apenas meio microssegundo por solicitação. Não consigo nem imaginar um serviço HTTP onde isso seja relevante.

@tinco eu concordo, é uma regressão muito pequena quando colocada em perspectiva. No entanto, ainda é muito importante descobrir por que ele regrediu. A menos que você queira uma situação como: # 6853 e Go 1.9 vem com outra redução de 11%.

Dito isso, não sei por que isso não pode ser corrigido com um lançamento de patch (em vez de um lançamento menor).

@tinco , quantos núcleos esse computador tem? multiplique 0,5us pelo número de núcleos.

Na quarta-feira, 8 de fevereiro de 2017 às 4:13 PM, Sokolov Yura [email protected]
escrevi:

quantos núcleos esse computador tem? multiplique 0,5us pelo número de núcleos.

Por que o número de núcleos tem a ver com isso.
cada solicitação é tratada apenas por um único núcleo.

@minux, os 5 milhões de pedidos são tratados em 30 segundos por N núcleos. Portanto, o tempo real gasto por solicitação em cada núcleo é 30 / 5M * N. N é provavelmente bastante pequeno, menos de 10 provavelmente, então não é realmente relevante.

Versões menores são para bugs críticos e inevitáveis. Seu programa tem 1% de chance de falhar aleatoriamente após um dia de execução é o tipo de bug que corrigimos em um lançamento pontual, com a correção mais simples, segura e trivial possível. Versões menores não são para consertar "seu programa roda 0,5us mais devagar para executar uma operação que provavelmente leva muito mais tempo do que o geral".

Eu acho que você está se referindo a lançamentos de patch ( 1.8.x ) vs menores ( 1.x.0 ).
IMO, todo o objetivo dos lançamentos de patch era lidar com regressões sem alterar recursos ou qualquer comportamento. Considerando que este não parece um grande problema, não vejo razão para não corrigi-lo em um 1.8.patch .

No projeto Go, nos referimos a 1.x como uma versão principal e 1.xy como uma versão secundária (ou às vezes pontual): Go 1.8 é uma versão principal; Go 1.8.2 é uma versão secundária ou pontual. Não faz sentido chamar o Go 1.8 de um lançamento menor.

A política de lançamento do projeto Go está documentada em https://golang.org/doc/devel/release.html#policy :

Cada versão principal do Go torna obsoleta e encerra o suporte para a anterior. Por exemplo, se Go 1.5 foi lançado, então é a versão atual e Go 1.4 e anteriores não são mais suportados. Corrigimos problemas críticos na versão atual conforme necessário, emitindo pequenas revisões (por exemplo, Go 1.5.1, Go 1.5.2 e assim por diante).

Go 1.5 é compatível com Go 1.4 com algumas ressalvas: seu código não deve depender de comportamento não documentado (por exemplo, a ordenação de elementos iguais escolhidos por sort.Sort), seu código deve usar literais de estrutura com chave para que não interromper se novos campos de estrutura forem adicionados e assim por diante .

Na medida do possível, o Go 1.5.1 é compatível com as versões anteriores do Go 1.5 sem quaisquer advertências: nosso objetivo é atualizar do Go 1.5 para o Go 1.5.1 para ser uma operação com segurança garantida, um não evento, como você diz " sem alterar recursos ou qualquer comportamento ".

Aceitar que cometer erros é uma parte inevitável da criação de software e que sempre que você fizer uma alteração, haverá o risco de introduzir um bug que não é detectado pelo teste antes do lançamento, a melhor maneira que conhecemos de reduzir esse risco é proibir mudanças não críticas em lançamentos pontuais.

Corrigir uma desaceleração de 0,5us que só aparece em microbenchmarks é uma mudança não crítica.

@tinco se não considerarmos esse problema de desempenho "chamado de menor" e toda vez que o ignorarmos,

@ rashidul0405 Este problema ainda está aberto; ninguém está ignorando isso. Eu estava explicando por que ele não merece uma correção apressada no Go 1.8 ou Go 1.8.1.

Eu aceitaria com prazer um Go 1% mais lento em vez de um Go 1% mais travado.

Vamos manter a discussão nas listas de e-mail e Twitter etc.

Por favor, apenas comente aqui se você estiver trabalhando neste problema.

Já que ninguém parece querer trabalhar nisso, vou fechá-lo. Não podemos perseguir todos os detalhes de desempenho possíveis, e este está ficando velho.

Esta página foi útil?
0 / 5 - 0 avaliações