Pregunta

Desarrollé un mini servidor HTTP en C ++, usando boost :: asio, y ahora lo estoy probando con varios clientes y no he podido acercarme a saturar la CPU. Estoy probando en una instancia de Amazon EC2 y obtengo alrededor del 50% de uso de una CPU, 20% de otra, y las dos restantes están inactivas (según htop).

Detalles:

  • El servidor dispara un hilo por núcleo
  • Las solicitudes se reciben, analizan, procesan y las respuestas se escriben
  • Las solicitudes son para datos, que se leen de la memoria (solo lectura para esta prueba)
  • Estoy 'cargando' el servidor usando dos máquinas, cada una con una aplicación java, ejecutando 25 hilos, enviando solicitudes
  • Veo un rendimiento de 230 solicitudes / segundo (se trata de solicitudes de solicitud , que se componen de muchas solicitudes HTTP)

Entonces, ¿qué debo mirar para mejorar este resultado? Dado que la CPU está inactiva en su mayoría, me gustaría aprovechar esa capacidad adicional para obtener un mayor rendimiento, digamos 800 solicitudes / segundo o lo que sea.

Ideas que he tenido:

  • Las solicitudes son muy pequeñas, y a menudo se cumplen en unos pocos ms, podría modificar el cliente para enviar / redactar solicitudes más grandes (tal vez usando el procesamiento por lotes)
  • Podría modificar el servidor HTTP para usar el patrón de diseño Seleccionar, ¿es apropiado aquí?
  • Podría hacer algunos perfiles para tratar de entender cuáles son / son los cuellos de botella
¿Fue útil?

Solución

boost :: asio no es tan amigable para subprocesos como cabría esperar: hay un gran bloqueo alrededor del código epoll en boost / asio / detail / epoll_reactor.hpp, lo que significa que solo un subproceso puede llamar al epoll syscall del núcleo a la vez Y para solicitudes muy pequeñas, esto hace la diferencia (lo que significa que solo verá un rendimiento aproximado de un solo subproceso).

Tenga en cuenta que esta es una limitación de cómo boost :: asio usa las instalaciones del kernel de Linux, no necesariamente el kernel de Linux en sí. Epoll syscall admite varios subprocesos cuando se utilizan eventos activados por bordes, pero hacerlo bien (sin un bloqueo excesivo) puede ser bastante complicado.

Por cierto, he estado haciendo algo de trabajo en esta área (combinando un bucle de evento epoll activado por borde completamente multiproceso con hilos / fibras programadas por el usuario) e hice que algún código esté disponible bajo el proyecto nginetd .

Otros consejos

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

Pruébelo utilizando hardware real y luego podrá ver lo que está sucediendo. Intentar hacer pruebas de rendimiento en máquinas virtuales es básicamente imposible.

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

De sus comentarios sobre la utilización de la red,
No parece tener mucho movimiento en la red.

3 + 2.5 MiB / sec está alrededor del parque de pelota 50Mbps (en comparación con su puerto de 1Gbps).

Diría que tiene uno de los dos problemas siguientes,

  1. Carga de trabajo insuficiente (baja tasa de solicitud de sus clientes)
    • Bloqueo en el servidor (generación de respuesta interferida)

Observando las notas de cmeerw y las cifras de utilización de su CPU
(inactivo en 50% + 20% + 0% + 0% )
parece más probable una limitación en la implementación de su servidor.
Secundo la respuesta de cmeerw (+1).

230 solicitudes / seg. parecen muy bajas para solicitudes asíncronas tan simples. Como tal, el uso de varios subprocesos es probablemente una optimización prematura: haga que funcione correctamente y sintonice un solo subproceso, y vea si aún los necesita. Simplemente deshacerse del bloqueo innecesario puede acelerar las cosas.

Este artículo tiene algunos detalles y debate sobre las estrategias de E / S para el servidor web estilo estilo circa 2003. ¿Alguien tiene algo más reciente?

ASIO está bien para tareas pequeñas a medianas, pero no es muy bueno para aprovechar el poder del sistema subyacente. Tampoco las llamadas de socket sin procesar, 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 que más con ASIO.

Por lo que vale. el uso de llamadas de socket sin procesar en mi HTTP personalizado puede atender solicitudes dinámicas de 800K por segundo con un I7 de 4 núcleos. Está sirviendo desde 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. Usando ASIO puedo obtener alrededor de 50 a 100K solicitudes por segundo, su rendimiento es bastante variable y mayormente limitado en mi aplicación. La publicación de @cmeerw explica principalmente por qué.

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

El futuro parece estar escribiendo su propio controlador que implementa su propia pila de red para que pueda acercarse lo más posible a las solicitudes e implementar su propio protocolo allí. Lo que 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 a un rendimiento óptimo sin necesidad de hacerlo, sin embargo, necesitará más servidores, aunque si realiza tantas solicitudes por segundo, generalmente necesitará múltiples tarjetas de red y múltiples interfaces para manejar el ancho de banda. un par de proxies UDP livianos allí no es gran cosa.

Espero que algo de esto pueda serle útil.

¿Cuántas instancias de io_service tienes? Boost asio tiene un ejemplo que crea un io_service por CPU y úselos de la manera de RoundRobin.

Todavía puede crear cuatro subprocesos y asignar uno por CPU, pero cada subproceso puede sondear en su propio io_service.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top