2011-08-13 15 views
7

TL; DR: ¿Es posible que el rendimiento del reactor sea limitado? ¿Cómo lo diría? ¿Qué tan caro y escalable (a través de los hilos) es la implementación del io_service?Cómo evitar que el reactor ASIO Boost se convierta en un solo núcleo?

Tengo una aplicación farsamente masiva en paralelo, que se ejecuta en una máquina hyperthreaded-dual-quad-core-Xeon con toneladas de RAM y una rápida RAID SSD. Esto se desarrolla usando boost :: asio.

Esta aplicación acepta conexiones de alrededor de otras 1000 máquinas, lee datos, decodifica un protocolo simple y mezcla los datos en archivos mapeados mediante mmap(). La aplicación también preselecciona las páginas "futuras" de mmap usando madvise (WILLNEED), por lo que es poco probable que bloquee las fallas de página, pero para estar seguro, he intentado generar hasta 300 subprocesos.

Esto se ejecuta en Linux kernel 2.6.32-27-generic (Ubuntu Server x64 LTS 10.04). La versión de Gcc es 4.4.3 y la versión de boost :: asio es 1.40 (ambas son existencias de Ubuntu LTS).

Ejecutando vmstat, iostat y superior, veo que el rendimiento del disco (tanto en TPS como en volumen de datos) está en los dígitos únicos de%. Del mismo modo, la longitud de la cola del disco es siempre mucho menor que la cantidad de subprocesos, por lo que no creo que esté vinculado a E/S. Además, el RSS trepa pero luego se estabiliza en algunos conciertos (como se esperaba) y vmstat no muestra mensajes, por lo que me imagino que no tengo memoria. La CPU es constante al 0-1% de usuario, 6-7% al sistema y el resto está inactivo. ¡Pista! Un "núcleo" completo (recuerde hyper-threading) es 6.25% de la CPU.

Sé que el sistema se está quedando atrás, porque las máquinas del cliente bloquean el envío de TCP cuando hay más de 64kB pendientes, e informan el hecho; todos continúan informando sobre este hecho, y el rendimiento del sistema es mucho menor de lo deseado, previsto y teóricamente posible.

Supongo que estoy conteniendo un bloqueo de algún tipo. Utilizo un bloqueo de nivel de aplicación para proteger una tabla de búsqueda que puede estar mutada, así que clasifiqué esto en 256 bloqueos/tablas de nivel superior para romper esa dependencia. Sin embargo, eso no pareció ayudar en absoluto.

Todos los hilos pasan por una única instancia de io_service global. Ejecutar strace en la aplicación muestra que pasa la mayor parte del tiempo lidiando con llamadas futex, lo que imagino tiene que ver con la implementación basada en el evocado reactor io_service.

¿Es posible que el rendimiento del reactor sea limitado? ¿Cómo lo diría? ¿Qué tan caro y escalable (a través de los hilos) es la implementación del io_service?

EDITAR: Al principio no encontré este otro hilo porque usaba un conjunto de etiquetas que no se solapaban con las mías: -/Es bastante posible que mi problema sea el bloqueo excesivo utilizado en la implementación del boost :: asio reactor. Ver C++ Socket Server - Unable to saturate CPU Sin embargo, la pregunta sigue siendo: ¿Cómo puedo probar esto? ¿Y cómo puedo solucionarlo?

+1

¿Ha comparado el rendimiento con las versiones más nuevas de asio? Boost 1.40 es un poco viejo y hubo algunas mejoras agradables integradas [bastante recientemente] (http://think-async.com/Asio/LinuxPerformanceImprovements). –

+0

Estoy algo obligado a usar Ubuntu 10.04 LTS, que viene con boost 1.40. Tal vez pueda probar esto en un sistema más moderno, pero todavía tiene que implementarse en el stock 10.04. Creo que boost :: asio es solo encabezado, así que quizás esto pueda funcionar ... –

+1

Sam: Lo intenté con el último lanzamiento de Boost, que es 1.47.0. Todavía tiene el mismo problema: el rendimiento se rehúsa a exceder el de un solo núcleo de CPU (aunque todos los núcleos realmente están trabajando, casi todos bloqueados). –

Respuesta

2

La respuesta es, de hecho, que incluso el último boost :: asio solo llama al descriptor del archivo epoll desde un solo hilo, sin ingresar el kernel desde más de un hilo a la vez. Puedo entender por qué, porque la seguridad de subprocesos y la duración de los objetos es extremadamente precaria cuando se utilizan varios subprocesos para que cada uno pueda recibir notificaciones para el mismo descriptor de archivo. Cuando codifico esto yo mismo (usando pthreads), funciona y escala más allá de un solo núcleo. No usar boost :: asio en ese momento - es una lástima que una biblioteca bien diseñada y portátil tenga esta limitación.

+0

podría ampliarlo un poco, p. donde esto se puede ver en la fuente? Tengo curiosidad. :) – murrekatt

+0

@Jon podría ser que el diseño de ASIO ** ordena ** que 'io_service' sea * uno por subproceso *? ¿Y solo proporciona bloqueo como una conveniencia para que su programa no falle? – unixman83

+0

Todo lo contrario. La API de ASIO dice que el trabajo se compartirá entre todos los hilos que entren en el mismo io_service; ese es el objetivo de ASIO. El problema es realmente la vida útil de los objetos cuando las notificaciones por objeto se pueden reordenar. –

2

Creo que si utiliza múltiples objetos io_service (por ejemplo, para cada núcleo de la CPU), cada uno ejecutado por un único hilo, no tendrá este problema. Consulte el ejemplo 2 del servidor http en la página de impulso de ASIO.

He realizado varias pruebas comparativas con el ejemplo 2 del servidor y el ejemplo 3 del servidor y he encontrado que la implementación que mencioné funciona mejor.

+0

El problema es que cada objeto de E/S (temporizador, socket, etc.) tiene que ser despachado en el io_service que lo posee, AFAICT. La asignación estática de los sockets a los hilos no es algo que quiera hacer. –

0

En mi aplicación de subproceso único, me enteré por el perfil de que una gran parte de las instrucciones del procesador se gastó en el bloqueo y desbloqueo por el io_service :: poll(). Inhabilité las operaciones de bloqueo con la macro BOOST_ASIO_DISABLE_THREADS. También puede tener sentido para usted, dependiendo de su situación de enhebrado.

+0

Cuando deshabilita el soporte de subprocesos, solo puede ejecutarlo con un único subproceso, que obviamente no logrará el objetivo de no estar restringido a un solo núcleo. –

+0

¿Por qué no usar un io_service por hilo y seguir estando libre de bloqueo? ¿Es eso un caso de uso no válido? – Syncopated

+0

Porque eso frustra el propósito de escalar todo el trabajo en todos los hilos. Tenga en cuenta que los sockets y similares están vinculados a un io_service particular en el constructor. –

Cuestiones relacionadas