2009-08-05 12 views
24

Desarrollé un mini servidor HTTP en C++, usando boost :: asio, y ahora estoy cargando la prueba con múltiples clientes y no he podido acercarme a la saturación la CPU. Estoy probando en una instancia de Amazon EC2 y obteniendo aproximadamente el 50% de uso de una CPU, el 20% de otra, y las otras dos están inactivas (según htop).C++ Socket Server - No se pudo saturar la CPU

Detalles:

  • Los fuegos de servidor hasta un hilo por núcleo
  • solicitudes son recibidos, analizados, procesados, y las respuestas se escriben
  • Las peticiones son para los datos, que se lee de memoria (solo lectura para esta prueba)
  • Estoy 'cargando' el servidor usando dos máquinas, cada una ejecutando una aplicación java, ejecutando 25 hilos, enviando solicitudes
  • Veo unas 230 solicitudes/seg thro ughput (esto es solicitudes de aplicaciones, que se componen de muchas peticiones HTTP)

Por lo tanto, lo que debería mirar a mejorar este resultado? Dado que la CPU está prácticamente inactiva, me gustaría aprovechar esa capacidad adicional para obtener un mayor rendimiento, digamos 800 solicitudes/seg. O lo que sea.

ideas que he tenido:

  • Las peticiones son muy pequeños, ya menudo cumplen en pocos ms, podría modificar el cliente para enviar/componer solicitudes más grandes (tal vez usando procesamiento por lotes)
  • I podría modificar el servidor HTTP para usar el patrón Seleccionar diseño, ¿es apropiado aquí?
  • que podría hacer algunos perfiles para tratar de entender lo que son del cuello de botella/es
+0

¿Es justo asumir que tiene un puerto de 1 Gbps en el servidor? ¿Cuáles son sus tamaños de solicitud y respuesta (en el cable)? – nik

+2

¿Cuál es la utilización del ancho de banda en el puerto de red del servidor (el que supongo que es 1Gbps) – nik

+1

La prueba se está ejecutando en EC2, que creo que usa Gigabit. Bmon informa acerca de la velocidad de TX de 3MiB (megabits, creo), y la tasa de RIB de 2.5Mib. Muchos tamaños de solicitud/respuesta son pequeños (tan poco como 100 bytes), pero algunas respuestas son de hasta 1mb, las solicitudes probablemente hasta de .25mb –

Respuesta

40

impulso :: asio no es como el hilo de usar como es de esperar - hay un gran bloqueo alrededor del código epoll en alza /asio/detail/epoll_reactor.hpp, lo que significa que solo un hilo puede llamar al epoll syscall del núcleo a la vez. Y para solicitudes muy pequeñas, esto hace toda la diferencia (lo que significa que solo verá el rendimiento aproximado de un único subproceso).

Tenga en cuenta que esto es una limitación de cómo boost :: asio utiliza las funciones del kernel de Linux, no necesariamente el núcleo de Linux en sí. El epoll syscall admite varios subprocesos cuando se usan eventos desencadenados por flancos, pero hacerlo bien (sin un bloqueo excesivo) puede ser bastante complicado.

BTW, he estado haciendo un trabajo en esta área (combinando un bucle de eventos epoll totalmente multiproceso desencadenado por borde con hilos/fibras programadas por el usuario) e hice disponible algún código bajo el proyecto nginetd.

+0

Gracias por la información cmeerw, eso es algo interesante. –

+1

(+1) cmeer Tengo una publicación sin respuesta que relaciona el rendimiento de boost :: asio en general en Windows y Linux. Si ha leído grandes secciones de asio, por favor, venga y responda a mi publicación: P –

+3

Estaba realmente preocupado por este bloqueo global. No es un problema tan grande como parece. El cuello de la botella solo puede ocurrir en escenarios de alto rendimiento. Sin embargo, cuando asio se ejecuta en modo epoll (linux) intenta de forma preventiva escribir o leer cuando se emite la llamada 'async_ *'. En un escenario de entrada alto, el socket generalmente estará listo para leer, permitiendo que 'async_read' omita epoll por completo. No puede pedir un mejor rendimiento de la red que eso. –

2

De sus comentarios sobre la utilización de la red,
Parece que no tiene mucho movimiento en la red.

3 + 2.5 MiB/sec es alrededor del 50Mbps ball-park (en comparación con su puerto de 1 Gbps).

diría que está teniendo uno de los dos problemas siguientes,

  1. insuficiente carga de trabajo (petición de tasa baja de sus clientes)
    • bloqueo en el servidor (interferido generación de respuestas)

en cuanto a cmeerw 's notas y sus cifras de utilización de la CPU
(al ralentí en 50% + 20% + 0% + 0%)
parece ser una limitación en la implementación de su servidor.
I segundo cmeerw la respuesta (+1).

+1

Está ejecutando pruebas en el clúster de computación en la nube EC2 de Amazon. Es difícil descartar el posible mal rendimiento en EC2. – unixman83

3

230 solicitudes/seg. Parece muy bajo para tales solicitudes asincrónicas simples. Como tal, el uso de múltiples hilos probablemente sea una optimización prematura: haz que funcione correctamente y sintonizado en un solo hilo, y mira si aún los necesitas. Solo deshacerse del bloqueo innecesario puede hacer que las cosas se aceleren.

This article tiene algunos detalles y discusiones sobre las estrategias de E/S para el rendimiento del servidor web al estilo de 2003. ¿Alguien tiene algo más reciente?

+0

Tenga en cuenta que las 230 solicitudes/seg son 'solicitudes de aplicaciones' que se componen de muchas solicitudes HTTP en realidad. –

+0

No hay mucho bloqueo para deshacerse, ninguno en mi código, pero como cmeerw señala boost :: asio hace un bloqueo interno. El servidor HTTP hace un trabajo puramente dependiente de la CPU, por lo que no usar los núcleos adicionales sería una pérdida costosa. –

+2

Si el objetivo es simplemente saturar la CPU, haga el trabajo en una secuencia y los otros tres calculen PI o algo así. Tener múltiples subprocesos de nivel de usuario no hará que sea más fácil o más rápido para el sistema operativo y el hardware de IO leer y escribir paquetes de red. Los hilos y núcleos son para el trabajo computacional, si no está haciendo ninguno, no es posible que gane nada, y arriesgar la contención con cualquier otra cosa que el sistema esté haciendo. – soru

12

Como está utilizando EC2, todas las apuestas están desactivadas.

Pruébela con hardware real, y entonces es posible que pueda ver lo que está sucediendo. Tratar de hacer pruebas de rendimiento en máquinas virtuales es básicamente imposible.

Todavía no he averiguado para qué sirve EC2, si alguien se entera, hágamelo saber.

+0

Este sistema se implementará en EC2, por lo que probar el rendimiento del sistema en hardware real no sería útil, no creo. –

+6

El punto de Mark es válido: para crear perfiles utilice una máquina real, o al menos un entorno más controlado. Implemente en EC2 todo lo que quiera, pero comprenda que está ejecutando una imagen de máquina virtual y eso significa que su CPU "inactiva" podría ser porque algún otro inquilino en la caja obtuvo toda la CPU por un tiempo. Y eso hace que los perfiles sean difíciles. – janm

+2

(+1) odia los votos abajo informados –

0

ASIO es adecuado para tareas pequeñas y medianas, pero no es muy bueno para aprovechar la potencia del sistema subyacente. Tampoco lo son las llamadas de socket raw, o incluso IOCP en Windows, pero si tiene experiencia, siempre será mejor que ASIO. De cualquier manera, hay muchos gastos generales con todos esos métodos, solo más con ASIO.

Por lo que vale la pena. El uso de llamadas de socket sin procesar en mi HTTP personalizado puede atender solicitudes dinámicas de 800 K por segundo con un I7 de 4 núcleos. Está sirviendo desde la RAM, que es donde debe estar para ese nivel de rendimiento. En este nivel de rendimiento, el controlador de red y el sistema operativo consumen aproximadamente el 40% de la CPU. Utilizando ASIO puedo obtener alrededor de 50 a 100K solicitudes por segundo, su rendimiento es bastante variable y está principalmente vinculado en mi aplicación. La publicación de @cmeerw explica en gran parte por qué.

Una forma de mejorar el rendimiento es mediante la implementación de un proxy UDP. Al interceptar solicitudes HTTP y luego enrutarlas a través de UDP a su servidor UDP-HTTP de fondo, puede omitir una gran cantidad de TCP en las pilas del sistema operativo. También puede tener interfaces que se conectan en UDP, lo cual no debería ser demasiado difícil. Una ventaja de un proxy HTTP-UDP es que le permite usar cualquier buena interfaz sin modificaciones, y puede cambiarlas a voluntad sin ningún impacto. Solo necesitas un par de servidores más para implementarlo. Esta modificación en mi ejemplo redujo el uso de la CPU del sistema operativo al 10%, lo que aumentó mis solicitudes por segundo a poco más de un millón en ese backend único. Y FWIW Siempre debe tener una configuración frontend-backend para cualquier sitio de rendimiento porque las interfaces pueden almacenar en caché los datos sin ralentizar el back-end de las solicitudes dinámicas más importantes.

El futuro parece ser escribir su propio controlador que implementa su propia pila de red para que pueda acercarse lo más posible a las solicitudes e implementar allí su propio protocolo.Lo cual probablemente no es lo que la mayoría de los programadores quieren escuchar, ya que es más complicado. En mi caso, podría usar un 40% más de CPU y pasar a más de 1 millón de solicitudes dinámicas por segundo. El método de proxy UDP puede acercarlo al rendimiento óptimo sin necesidad de hacerlo, sin embargo, necesitará más servidores, aunque si está haciendo muchas solicitudes por segundo, generalmente necesitará múltiples tarjetas de red y múltiples interfaces para manejar el ancho de banda de modo que tenga un par de proxies UDP livianos no son un gran problema.

Espero que algo de esto te pueda ser útil.

+1

cuidado para mostrar un ejemplo o proyecto de trabajo? Sin esto, esto es tan útil como una conversación irrelevante. No trato de degradarlo, pero aquí se necesita un código concreto. –

0

¿Cuántas instancias de io_service tiene? Boost asio tiene un example que crea un io_service por CPU y los usa a la manera de RoundRobin.

Aún puede crear cuatro hilos y asignar uno por CPU, pero cada hilo puede sondear en su propio io_service.

Cuestiones relacionadas