Go: net / http: el servidor HTTP / 1 se volvió 0.5μs más lento en Go 1.8

Creado en 6 feb. 2017  ·  35Comentarios  ·  Fuente: golang/go

Responda estas preguntas antes de enviar su problema. ¡Gracias!

¿Qué versión de Go estás usando ( go version )?

Ir a 1.7.5, 1.8 rc3 y git

¿Qué sistema operativo y arquitectura de procesador está utilizando ( go env )?

Arch Linux de 64 bits

¿Qué hiciste?

go run https://gist.github.com/OneOfOne/4d7e13977886ddab825870bc3422a901
cambiar terminales y ejecutar wrk -c 20 -d 30s http://localhost:8081

¿Qué esperabas ver?

Mismo rendimiento que 1.7 o más rápido.

¿Qué viste en su lugar?

ir propina 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

ir 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

ir 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

Comentario más útil

Hola @bradfitz

Solo una pregunta rápida de un forastero, así que tengan paciencia conmigo si me perdí algo obvio.
¿Por qué sería demasiado tarde para solucionar este problema? ¿No es esta la única razón para liberar a los candidatos? ¿Para encontrar los problemas principales finales antes de lanzar la próxima versión principal?

Si no es así, infórmame.

Además, gracias por trabajar en este proyecto, conozco a mucha gente que ama este idioma y la comunidad que lo rodea. 🎉

Todos 35 comentarios

Demasiado tarde para Go 1.8, pero podemos analizar el rendimiento durante Go 1.9.

¿Alguien quiere investigar la diferencia? ¿Qué dice un perfil de CPU Go 1.7 vs Go 1.8?

Juego actualizado con pprof: https://play.golang.org/p/GZ4zQOg1Wf

Ejecuté go tool pprof http://localhost:6060/debug/pprof/profile , cambié de términos y ejecuté wrk -c 20 -d 30s http://localhost:6060/ .

ir propina

(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

ir 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

Avísame si quieres que haga algo específico.

Vaya, estoy viendo lo mismo con una ralentización de aproximadamente un 20% en el punto de referencia de mi servidor web con 1.8r3, y un tamaño binario ligeramente mayor también

Me doy cuenta de que es tarde en el ciclo de lanzamiento, pero la regresión del 20% en http es una gran regresión para mucha gente.

Estoy dispuesto a ayudar a depurar si me dice lo que se necesita @bradfitz.

Estoy dispuesto a ayudar a depurar si me dice lo que se necesita @bradfitz.

El paso 1 es depurar por qué se hizo más lento. Si encuentra alguna pista, hágamelo saber.

@OneOfOne ¿cómo funciona esto para aplicaciones que no son de Hello World? ¿Ves algún problema allí? ¿Quizás el 20% para las aplicaciones de Hello World sea aceptable hasta la 1.9?

@bradfitz Creo que la función de Shutdown está provocando un rendimiento más bajo para el tipo de aplicaciones / pruebas de 'hola mundo'.

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

Mira aquí .

En 1.7 era solo un bucle for .

¿Alguien puede confirmar mi suposición?

Gracias,
kataras

No puedo reproducir los resultados del OP. Estoy en una Mac y estoy usando _ligeramente_ versiones anteriores 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

Hay una ligera diferencia, pero es menor al 20%, casi insignificante aquí.

@kataras , ¿tienes evidencia a favor de esa teoría?

Puede confirmarlo usted mismo: elimine esas líneas y vuelva a medir.

Sin embargo, me sorprendería.

@kataras Parece que probablemente lo sea. Además de la selección, tiene una adquisición de mutex y un desbloqueo que se realiza en un aplazamiento (que sé que se aceleró recientemente, pero aún son un poco más lentos que un desbloqueo directo.

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

Dado que el ciclo de aceptación es una ruta tan activa, puede valer la pena mover la selección de apagado a una rutina de gor por separado y usar sincronización / atómico para señalar el cierre en el ciclo de aceptación.

EDITAR:
No creo que sea solo esto, solo probé la sugerencia sin seleccionar todos juntos y agrega aproximadamente 7us (5% ~).

Entiendo que es demasiado tarde para 1.8, pero ¿por qué no arreglarlo en una futura versión 1.8.1?

¿Alguien más ha podido reproducir estos resultados?

-11,2% en Debian 8, amd64 (1.7.5 y 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 La implementación es correcta, lo que significa que el autor fue cuidadoso ... no es que sucedan errores importantes en el software.

Dicho esto, estaba equivocado de todos modos, miré nuevamente y me di cuenta de que la selección solo ocurre cuando ocurre un error durante la aceptación. Lo extraño es cómo ralentizó mi programa constantemente cuando lo elimino de la aceptación. ¿Quizás descalifica a ese bloque de algunos pases de optimización o alineación? Puede haber un problema en algún lugar que cause una desaceleración, pero está en otro lugar y necesita una configuración diferente a la mía.

@bradfitz brevemente dividió esto, y parece que el commit faf882d1d427e8c8a9a1be00d8ddcab81d1e848e está causando al menos algo de esto. Veo un impacto de rendimiento de aproximadamente el 10% en este punto de referencia con este compromiso en comparación con el compromiso que lo precede.

@codido , gracias por el bisecto! Sí, y ese es el compromiso del que sospecharía también. Es bueno ver la confirmación. Ese fue uno de los mayores cambios arquitectónicos en net / http.Server en bastante tiempo.

Nunca realicé evaluaciones comparativas (u optimizaciones) después de ese cambio.

Podemos investigarlo para Go 1.9.

Hola @bradfitz

Solo una pregunta rápida de un forastero, así que tengan paciencia conmigo si me perdí algo obvio.
¿Por qué sería demasiado tarde para solucionar este problema? ¿No es esta la única razón para liberar a los candidatos? ¿Para encontrar los problemas principales finales antes de lanzar la próxima versión principal?

Si no es así, infórmame.

Además, gracias por trabajar en este proyecto, conozco a mucha gente que ama este idioma y la comunidad que lo rodea. 🎉

¿Por qué la gente responde y vota como si este fuera un problema importante? Este cambio parece tener un impacto en el rendimiento en el peor de los casos de ~ 40us por solicitud. Eso me suena muy bajo, ¿hay un escenario del mundo real en el que esto importe?

(editar: estaba equivocado, es ~ 0.5us por solicitud, así que aún más bajo)

Creo que podría necesitarse más puntos de referencia en torno a esto para ver el efecto en otras cosas además de hola mundo. Cosas como hola mundo ejercen una gran presión de referencia en los aspectos internos y, como tales, pueden magnificar los efectos de las ralentizaciones. En una aplicación del mundo real, hay una carga de más código que se ejecuta, lo que en teoría hace que el efecto de cosas como esta sea mucho más pequeño por solicitud, es decir, se reduce a un% por solicitud, lo que significa que el efecto se reduce.
Solo mis 2 centavos.
(Voy a ver precisamente esto más adelante si tengo tiempo)

@reimertz Recuerdo haber leído sobre el ciclo de lanzamiento aquí: https://github.com/golang/go/wiki/Go-Release-Cycle

Una vez que se emite una versión candidata, solo se deben realizar cambios en la documentación y los cambios para abordar los errores críticos. En general, la barra de corrección de errores en este punto es incluso ligeramente más alta que la barra de corrección de errores en una versión menor. Es posible que prefiramos emitir una versión con un bloqueo conocido pero muy poco común que emitir una versión con una solución nueva pero no probada en producción.

Uno de los criterios para emitir un candidato de lanzamiento es que Google use esa versión del código para nuevas compilaciones de producción de manera predeterminada: si en Google no estamos dispuestos a ejecutarlo para uso en producción, no deberíamos pedirle a otros que lo hagan.

@kmlx gracias por el enlace! Solo fui aquí: https://golang.org/doc/contribute.html y busqué 'lanzamiento' y no pude encontrar nada. Culpa mía.

Además, ¡guau! Si Google ejecuta esta versión en sus servidores de producción y está de acuerdo con un impacto de rendimiento en el peor de los casos de ~ 40us por solicitud (citando a @tinco ), supongo que el resto del mundo también puede

Es por eso que le pedimos a la gente que pruebe las versiones beta. De modo que si creen que han encontrado un problema, pueden quejarse temprano.

go1.8beta1 fue lanzado el 1 de diciembre de 2016 , hace más de 2 meses:

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

Citando el anuncio:

Es importante que encontremos errores antes de emitir una versión candidata.
La versión candidata está prevista para la primera semana de enero.
Su ayuda para probar esta versión beta es invaluable.

Lo siento, miré mal los números. Entonces, de la prueba de go tip hizo 5110713 solicitudes en 30 segundos, eso es 5.87us por solicitud. Go 1.7.5 hizo 5631803 solicitudes en 30 segundos, 5.33us por solicitud. Entonces, cuando los compara entre sí, es como una disminución del rendimiento del 11%. Pero si lo mira desde una perspectiva absoluta, es un impacto de rendimiento de solo medio microsegundo por solicitud. Ni siquiera puedo imaginar un servicio HTTP donde esto sea relevante.

@tinco Estoy de acuerdo, es una regresión muy pequeña cuando se pone en perspectiva. Sin embargo, sigue siendo muy importante averiguar por qué retrocedió. A menos que desee una situación como: # 6853 y Go 1.9 aparece con otra disminución del 11%.

Dicho esto, no estoy seguro de por qué esto no se pudo solucionar con una versión de parche (frente a una versión menor).

@tinco , ¿cuántos núcleos tiene esa computadora? multiplique 0.5us por el número de núcleos.

El miércoles 8 de febrero de 2017 a las 4:13 p.m., Sokolov Yura [email protected]
escribió:

¿cuántos núcleos tiene esa computadora? multiplique 0.5us por el número de núcleos.

¿Por qué la cantidad de núcleos tiene que ver con esto?
cada solicitud solo es manejada por un único núcleo.

@minux los 5 millones de solicitudes se manejan en 30 segundos por N núcleos. Entonces, el tiempo real invertido por solicitud en cada núcleo es 30/5 M * N. N probablemente sea bastante pequeño, menos de 10 probablemente, por lo que no es realmente relevante.

Las versiones menores son para errores críticos e inevitables. Su programa tiene un 1% de probabilidad de fallar aleatoriamente después de un día de ejecución, es el tipo de error que corregimos en una versión puntual, con la solución más simple, segura y trivial posible. Las versiones menores no son para corregir "su programa corre 0.5us más lento para ejecutar una operación que probablemente demore mucho más que eso en general".

Supongo que te refieres a los lanzamientos de parches ( 1.8.x ) frente a los menores ( 1.x.0 ).
En mi opinión, el objetivo de los lanzamientos de parches era abordar las regresiones sin cambiar las características ni ningún comportamiento. Si bien este no parece muy grande, no veo ninguna razón para no arreglarlo en un 1.8.patch .

En el proyecto Go, nos referimos a 1.x como una versión principal y 1.xy como una versión menor (oa veces puntual): Go 1.8 es una versión principal; Go 1.8.2 es una versión menor o puntual. No tiene sentido que llamemos a Go 1.8 una versión menor.

La política de lanzamiento del proyecto Go está documentada en https://golang.org/doc/devel/release.html#policy :

Cada versión principal de Go queda obsoleta y finaliza el soporte de la anterior. Por ejemplo, si se lanzó Go 1.5, entonces es la versión actual y Go 1.4 y versiones anteriores ya no son compatibles. Solucionamos los problemas críticos en la versión actual según sea necesario mediante la publicación de revisiones menores (por ejemplo, Go 1.5.1, Go 1.5.2, etc.).

Go 1.5 es compatible con versiones anteriores de Go 1.4 con algunas advertencias: su código no debe depender de un comportamiento indocumentado (por ejemplo, el orden de elementos iguales elegidos por sort.Sort), su código debe usar literales de estructura con clave para que no lo haga romper si se agregan nuevos campos de estructura, y así sucesivamente .

En la mayor medida posible, Go 1.5.1 es retrocompatible con Go 1.5 sin tales advertencias: nuestro objetivo es que la actualización de Go 1.5 a Go 1.5.1 sea una operación segura garantizada, un no evento, como usted dice sin cambiar características ni ningún comportamiento ".

Aceptar que cometer errores es una parte inevitable de la escritura de software y que cada vez que realiza un cambio existe el riesgo de que introduzca un error que no se detecta mediante la prueba antes del lanzamiento, la mejor manera que conocemos de reducir ese riesgo es no permitir cambios no críticos en lanzamientos puntuales.

Arreglar una desaceleración de 0.5us que solo aparece en microbenchmarks es un cambio no crítico.

@tinco si no consideramos este problema de rendimiento "supuestamente menor" y cada vez que lo ignoramos, finalmente obtendremos "un proceso más lento". En mi opinión, nadie quiere que esto suceda.

@ rashidul0405 Este problema aún está abierto; nadie lo está ignorando. Estaba explicando por qué no merece una solución apresurada en Go 1.8 o Go 1.8.1.

Con mucho gusto tomaría un Go 1% más lento en lugar de un Go 1% más rápido.

Mantengamos la discusión sobre las listas de correo y Twitter, etc.

Solo comente aquí si está trabajando en este problema.

Como nadie parece querer trabajar en esto, lo cerraré. No podemos perseguir todos los detalles de rendimiento posibles, y este está envejeciendo.

¿Fue útil esta página
0 / 5 - 0 calificaciones