C++ Socket Server-Impossible de saturer CPU

j'ai développé un mini serveur HTTP en C++, en utilisant boost::asio, et maintenant je l'ai testé avec plusieurs clients et j'ai été incapable de me rapprocher de saturer le CPU. Je teste sur une instance EC2 D'Amazon, et j'obtiens environ 50% d'utilisation d'un cpu, 20% d'un autre, et les deux autres sont inactifs (selon htop).

détails:

  • le serveur Active un thread par core
  • Des demandes
  • sont reçues, analysées, traitées et les réponses sont écrites
  • les requêtes sont pour des données, qui sont lues hors de la mémoire (en lecture seule pour ce test)
  • je "charge" le serveur en utilisant deux machines, chacune exécutant une application java, en exécutant 25 threads, en envoyant des requêtes
  • je vois environ 230 requêtes / sec débit (c'est application requêtes, qui sont composées de nombreuses requêtes HTTP)

alors, qu'est-ce que je devrais regarder pour améliorer ce résultat? Étant donné que le CPU est en grande partie inactif, j'aimerais tirer parti de cette capacité supplémentaire pour obtenir un débit plus élevé, disons 800 requêtes/seconde ou autre.

les Idées que j'ai eu:

  • les requêtes sont très petites, et souvent remplies en quelques ms, je pourrais modifier le client pour envoyer/composer des requêtes plus importantes (peut-être en utilisant la mise en lots)
  • je pourrais modifier le serveur HTTP pour utiliser le Sélectionnez le motif de conception, est-ce approprié ici?
  • j'ai pu faire quelques profilage pour essayer de comprendre ce que le goulot d'étranglement de l'est
25
demandé sur unixman83 2009-08-05 21:56:43

6 réponses

boost::asio n'est pas comme fil convivial que vous espérez - il y a un gros verrou autour de la epoll code boost/asio/detail/epoll_reactor.hpp ce qui signifie qu'un seul thread peut appeler dans l'appel syscall epoll du noyau à la fois. Et pour les très petites requêtes Cela fait toute la différence (ce qui signifie que vous ne verrez qu'une seule performance filetée).

notez qu'il s'agit d'une limitation de la façon dont boost::asio utilise les fonctionnalités du noyau Linux, pas nécessairement le Linux le noyau lui-même. L'epoll syscall prend en charge plusieurs threads lors de l'utilisation d'événements déclenchés par les bordures, mais il peut être très difficile de le faire correctement (sans verrouillage excessif).

BTW, j'ai fait un peu de travail dans ce domaine (combinant une boucle d'événement de bord entièrement multithreaded avec des fils/fibres programmés par l'utilisateur) et j'ai rendu un peu de code disponible sous le projet nginetd .

41
répondu cmeerw 2009-08-06 11:30:39

comme vous utilisez EC2, tous les paris sont éteints.

essayez - le avec du vrai matériel, et alors vous pourriez être en mesure de voir ce qui se passe. Essayer de faire des tests de performance dans les VMs est fondamentalement impossible.

Je n'ai pas encore trouvé ce que EC2 est utile pour, si quelqu'un le découvre, s'il vous plaît laissez-moi savoir.

12
répondu MarkR 2009-08-06 12:59:37

230 requêtes/s semble très faible pour un tel simple demandes asynchrones. En tant que tel, l'utilisation de plusieurs threads est probablement une optimisation prématurée - faites-le fonctionner correctement et accordé dans un seul thread, et voyez si vous avez encore besoin d'eux. Le fait de se débarrasser d'un verrouillage inutile peut accélérer les choses.

cet article contient des détails et des discussions sur les stratégies d'E/S pour la performance de type serveur web vers 2003. Quelqu'un a quelque chose de plus récent?

3
répondu soru 2009-08-06 12:51:26

de vos commentaires sur l'utilisation du réseau,

Vous ne semblez pas avoir beaucoup de mouvement réseau.

3 + 2.5 MiB/sec est autour du 50Mbps ball-park (comparé à votre port 1Gbps).

je dirais que vous avez l'un des deux problèmes suivants,

  1. charge de travail insuffisante (faible taux de demande de vos clients)
    • blocage dans le serveur (interféré réponse de la génération)

en regardant les notes de cmeerw et vos chiffres D'utilisation CPU

(ralenti à 50% + 20% + 0% + 0% )

il semble très probablement une limitation dans l'implémentation de votre serveur.

Je seconde cmeerw 's réponse (+1).

2
répondu nik 2009-08-06 11:46:17

ASIO est bon pour les tâches petites à moyennes, mais il n'est pas très bon à tirer parti de la puissance du système sous-jacent. Ni les appels de socket crus, ni même IOCP sur Windows, mais si vous êtes expérimenté, vous serez toujours meilleur que ASIO. De toute façon, Il ya beaucoup de frais généraux avec toutes ces méthodes, juste plus avec ASIO.

Pour ce qu'il vaut. en utilisant des appels de socket raw sur mon HTTP personnalisé, vous pouvez servir des requêtes dynamiques 800K par seconde avec un i7 de 4 core. C'est de servir les de RAM, qui est l'endroit où vous devez être pour ce niveau de performance. À ce niveau de performance, le gestionnaire de réseau et le système d'exploitation consomment environ 40% du CPU. En utilisant ASIO je peux obtenir environ 50 à 100K requêtes par seconde, sa performance est très variable et la plupart du temps liée dans mon application. Le billet de @cmeerw explique en grande partie pourquoi.

une façon d'améliorer les performances est de mettre en œuvre un proxy UDP. Intercepter les requêtes HTTP et ensuite les router via UDP vers votre backend UDP-HTTP serveur vous pouvez contourner beaucoup de TCP overhead dans les piles de système d'exploitation. Vous pouvez également avoir des extrémités avant qui pipe à travers sur UDP eux-mêmes, ce qui ne devrait pas être trop difficile à faire vous-même. Un avantage D'un proxy HTTP-UDP est qu'il vous permet d'utiliser n'importe quelle bonne interface sans modification, et vous pouvez les échanger à volonté sans aucun impact. Vous avez juste besoin de quelques serveurs supplémentaires pour l'implémenter. Cette modification sur mon exemple a réduit l'utilisation de L'OS CPU à 10%, ce qui a augmenté mes requêtes par deuxième à un peu plus d'un million sur ce simple backend. Et FWIW vous devriez toujours avoir une configuration frontend-backend pour n'importe quel site performant car les frontends peuvent mettre en cache des données sans ralentir les requêtes dynamiques backend plus importantes.

le futur semble écrire votre propre pilote qui implémente sa propre pile réseau afin que vous puissiez vous rapprocher le plus possible des requêtes et y implémenter votre propre protocole. Qui n'est probablement pas ce que la plupart des programmeurs veulent entendre comme c'est plus compliqué. Dans mon cas, je pourrais utiliser 40% de CPU de plus et passer à plus d'un million de requêtes dynamiques par seconde. La méthode UDP proxy peut vous rapprocher d'une performance optimale sans avoir besoin de le faire, mais vous aurez besoin de plus de serveurs - bien que si vous faites autant de requêtes par seconde, vous aurez généralement besoin de plusieurs cartes réseau et de plusieurs frontends pour gérer la bande passante de sorte qu'avoir un couple de proxies UDP légers dans Il n'y a pas que des problèmes.

J'espère qu'une partie de ceci peut vous être utile.

0
répondu Ryler Sturden 2016-02-24 01:40:20

combien de cas de io_service avez-vous? Boost asio a un exemple qui crée un io_service par CPU et les utilise à la manière de Rondrobin.

vous pouvez encore créer quatre threads et en assigner un par CPU, mais chaque thread peut Poller sur son propre io_service.

0
répondu clark.li 2016-06-19 12:14:09