45 votes

Sockets .NET contre sockets C++ à haute performance

Ma question vise à régler un différend avec mes collègues de travail sur le thème C++ vs C#.

Nous avons implémenté un serveur qui reçoit une grande quantité de flux UDP. Ce serveur a été développé en C++ en utilisant des sockets asynchrones et des E/S superposées en utilisant des ports de complétion. Nous utilisons 5 ports de complétion avec 5 threads. Ce serveur peut facilement gérer un débit de 500 Mbps sur un réseau gigabit sans aucune perte de paquets / erreur (nous n'avons pas poussé nos tests plus loin que 500 Mbps).

Nous avons essayé de réimplémenter le même type de serveur en C# et nous n'avons pas été en mesure d'atteindre le même débit entrant. Nous utilisons la réception asynchrone en utilisant ReceiveAsync et un pool de SocketAsyncEventArgs pour éviter la surcharge de créer un nouvel objet pour chaque appel de réception. Chaque SAEventArgs a un tampon qui lui est attribué, ce qui nous évite d'avoir à allouer de la mémoire pour chaque réception. Le pool est très, très grand et nous pouvons mettre en file d'attente plus de 100 demandes de réception. Ce serveur n'est pas en mesure de gérer un débit entrant de plus de 240 Mbps. Au-delà de cette limite, nous perdons certains paquets dans nos flux UDP.

Ma question est la suivante : dois-je m'attendre à obtenir les mêmes performances en utilisant les sockets C++ et les sockets C# ? Je pense que les performances devraient être les mêmes si la mémoire est gérée correctement en .NET.

Question secondaire : quelqu'un connaît-il un bon article/référence expliquant comment les sockets .NET utilisent les ports de complétion d'E/S sous le capot ?

8voto

Richard Points 54016

Quelqu'un connaît-il un bon article/référence expliquant comment les sockets .NET utilisent les ports de complétion d'E/S sous le capot ?

Je pense que la seule référence serait l'implémentation (par exemple Reflector ou un autre décompilateur d'assemblage). Avec cela, vous trouverez que tous Les entrées-sorties asynchrones passent par un port d'achèvement des entrées-sorties, les rappels étant traités dans le pool de threads d'entrées-sorties (qui est distinct du pool de threads normal).

utiliser 5 ports d'achèvement

Je m'attendrais à utiliser un port d'achèvement unique traitant toutes les entrées/sorties dans un pool unique de threads avec un thread par pool gérant les achèvements (en supposant que vous faites toutes les autres entrées/sorties, y compris le disque, de manière asynchrone également).

Les ports d'achèvement multiples auraient un sens si vous avez une forme de hiérarchisation des priorités.

Ma question est la suivante : dois-je m'attendre à obtenir les mêmes performances en utilisant les sockets C++ et les sockets C# ?

Oui ou non, selon la définition que vous donnez à la partie "utilisant ... des sockets". En termes d'opérations, depuis le début de l'opération asynchrone jusqu'à l'envoi de l'achèvement au port d'achèvement, je ne m'attends pas à une différence significative (tout le traitement se fait dans l'API Win32 ou le noyau Windows).

Cependant, la sécurité que fournit le runtime .NET ajoutera quelques frais généraux. Par exemple, la longueur des tampons sera vérifiée, les délégués validés, etc. Si la limite de l'application est le CPU, cela peut faire une différence, et à l'extrême, une petite différence peut facilement s'accumuler.

De plus, la version .NET s'arrête parfois pour la GC (.NET 4.5 fait de la collecte asynchrone, donc cela s'améliorera à l'avenir). Il existe des techniques pour minimiser l'accumulation de déchets (par exemple, réutiliser des objets plutôt que d'en créer, utiliser des structures tout en évitant les boîtes).

En fin de compte, si la version C++ fonctionne et répond à vos besoins de performance, pourquoi la porter ?

6voto

jgauffin Points 51913

Vous ne pouvez pas effectuer un portage direct du code de C++ à C# et espérer obtenir les mêmes performances. .NET fait beaucoup plus que C++ en ce qui concerne la gestion de la mémoire (GC) et la sécurité de votre code (vérifications des limites, etc.).

J'allouerais un grand tampon pour toutes les opérations d'entrée/sortie (par exemple 65535 x 500 = 32767500 octets), puis j'attribuerais un morceau à chaque opération d'entrée/sortie. SocketAsyncEventArgs (et pour les opérations d'envoi). La mémoire est moins chère que le CPU. Utilisez un gestionnaire/une fabrique de tampons pour fournir des morceaux pour toutes les connexions et opérations d'E/S (modèle Flyweight). Microsoft le fait dans son exemple Async.

Les méthodes Begin/End et Async utilisent toutes deux les ports de complétion IO en arrière-plan. Ces dernières n'ont pas besoin d'allouer des objets pour chaque opération, ce qui améliore les performances.

1voto

Erik Funkenbusch Points 53436

Je pense que vous n'obtenez pas les mêmes performances parce que .NET et C++ font en fait des choses différentes. Votre code C++ n'est peut-être pas aussi sûr, ou ne vérifie pas les limites. De plus, mesurez-vous simplement la capacité à recevoir les paquets sans aucun traitement ? Ou votre débit inclut-il le temps de traitement des paquets ? Si c'est le cas, alors le code que vous avez écrit pour traiter les paquets n'est peut-être pas aussi efficace.

Je vous suggère d'utiliser un profileur pour vérifier où le plus de temps est passé et d'essayer de l'optimiser. Le code socket actuel devrait être assez performant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X