c# - servidor - Sockets.NET vs sockets C++ con alto rendimiento
socket receive c# (3)
Mi pregunta es resolver una discusión con mis compañeros de trabajo en C ++ contra C #.
Hemos implementado un servidor que recibe una gran cantidad de flujos UDP. Este servidor se desarrolló en C ++ utilizando sockets asincrónicos y E / S superpuestas usando los puertos de terminación. Usamos 5 puertos de terminación con 5 hilos. Este servidor puede manejar fácilmente un rendimiento de 500 Mbps en una red gigabit sin pérdida de paquetes / error (no empujamos nuestras pruebas más allá de 500 Mbps).
Hemos tratado de volver a implementar el mismo tipo de servidor en C # y no hemos podido alcanzar el mismo rendimiento entrante. Estamos utilizando la recepción asincrónica mediante el método ReceiveAsync
y un conjunto de SocketAsyncEventArgs
para evitar la sobrecarga de crear un nuevo objeto para cada llamada de recepción. Cada SAEventArgs
tiene un búfer establecido para que no necesitemos asignar memoria para cada recepción. El grupo es muy, muy grande, por lo que podemos poner en cola más de 100 solicitudes de recepción. Este servidor no puede manejar un rendimiento entrante de más de 240 Mbps. Por encima de ese límite, perdemos algunos paquetes en nuestras transmisiones UDP.
Mi pregunta es esta: ¿debería esperar el mismo rendimiento usando sockets C ++ y sockets C #? Mi opinión es que debería ser el mismo rendimiento si la memoria se gestiona correctamente en .NET.
Pregunta complementaria: ¿alguien sabría un buen artículo / referencia que explique cómo los zócalos .NET usan puertos de E / S de finalización bajo el capó?
¿Alguien sabría un buen artículo / referencia explicando cómo las tomas .NET usan puertos de E / S de finalización bajo el capó?
Sospecho que la única referencia sería la implementación (es decir, Reflector u otro decompilador de ensamblaje). Con eso, encontrará que todo IO asíncrono pasa por un puerto de finalización de IO con llamadas de respuesta que se procesan en el grupo de subprocesos de IO (que está separado del grupo de subprocesos normal).
use 5 puertos de terminación
Esperaría usar un solo puerto de terminación procesando todo el IO en un solo grupo de subprocesos con un subproceso por complementos de servicio del grupo (suponiendo que esté haciendo cualquier otro IO, incluido el disco, también asincrónicamente).
Múltiples puertos de finalización tendrían sentido si tiene alguna forma de priorización.
Mi pregunta es esta: ¿debería esperar el mismo rendimiento usando sockets C ++ y sockets C #?
Sí o no, según cuán estrechamente defina la parte "usar ... enchufes". En términos de las operaciones desde el inicio de la operación asincrónica hasta que la finalización se publique en el puerto de finalización, no esperaría una diferencia significativa (todo el procesamiento está en la API de Win32 o el kernel de Windows).
Sin embargo, la seguridad que proporciona el tiempo de ejecución de .NET agregará algo de sobrecarga. P.ej. se verificará la longitud del búfer, se validará a los delegados, etc. Si el límite de la aplicación es la CPU, es probable que esto haga la diferencia y, en el extremo, una pequeña diferencia puede sumarse fácilmente.
Además, la versión de .NET ocasionalmente hará una pausa para GC (.NET 4.5 realiza una recolección asincrónica, por lo que mejorará en el futuro). Existen técnicas para minimizar la acumulación de basura (por ejemplo, reutilizar objetos en lugar de crearlos, hacer uso de estructuras y evitar el boxeo).
Al final, si la versión de C ++ funciona y cumple con sus necesidades de rendimiento, ¿por qué puerto?
No puede hacer un puerto directo del código de C ++ a C # y esperar el mismo rendimiento. .NET hace mucho más que C ++ en lo que respecta a la gestión de memoria (GC) y se asegura de que su código sea seguro (controles de límites, etc.).
Asignaría un búfer grande para todas las operaciones de IO (por ejemplo 65535 x 500 = 32767500 bytes) y luego asignaría un fragmento a cada SocketAsyncEventArgs
(y para las operaciones de envío). La memoria es más barata que la CPU. Use un administrador de búfer / fábrica para proporcionar trozos para todas las conexiones y operaciones de E / S (patrón Flyweight). Microsoft hace esto en su ejemplo Async.
Los métodos Begin / End y Async utilizan puertos de terminación IO en segundo plano. Este último no necesita asignar objetos para cada operación que aumenta el rendimiento.
Supongo que no está viendo el mismo rendimiento porque .NET y C ++ en realidad están haciendo cosas diferentes. Es posible que su código C ++ no sea tan seguro o verifique los límites. Además, ¿simplemente está midiendo la capacidad de recibir los paquetes sin ningún procesamiento? ¿O su rendimiento incluye el tiempo de procesamiento de paquetes? Si es así, es posible que el código que haya escrito para procesar los paquetes no sea tan eficiente.
Sugiero usar un generador de perfiles para ver dónde se gasta más tiempo e intentar optimizarlo. El código de socket real debería ser bastante eficiente.