Question

J'ai développé un mini serveur HTTP en C ++, en utilisant boost :: asio, et maintenant je le teste en charge avec plusieurs clients et je ne suis pas parvenu à saturer le processeur. Je teste sur une instance Amazon EC2 et j'utilise environ 50% d'un processeur, 20% d'un autre et les deux autres sont inactifs (selon htop).

Détails:

  • Le serveur déclenche un thread par cœur
  • Les demandes sont reçues, analysées, traitées et les réponses sont écrites
  • Les demandes concernent des données lues en mémoire (lecture seule pour ce test)
  • Je "charge" le serveur à l'aide de deux machines, chacune exécutant une application Java, exécutant 25 threads et envoyant des demandes
  • Je vois environ 230 requêtes par seconde (il s'agit de requêtes d'application, composées de nombreuses requêtes HTTP)
  • .

Alors, que dois-je regarder pour améliorer ce résultat? Étant donné que le processeur est généralement inactif, j'aimerais tirer parti de cette capacité supplémentaire pour obtenir un débit plus élevé, par exemple 800 demandes / s ou peu importe.

Idées que j'ai eues:

  • Les requêtes sont très petites et souvent remplies en quelques ms, je peux modifier le client pour envoyer / composer des requêtes plus volumineuses (peut-être en utilisant le traitement par lots)
  • Je pourrais modifier le serveur HTTP pour utiliser le modèle de conception Select, est-ce approprié ici?
  • Je pourrais faire du profilage pour essayer de comprendre ce que sont / sont les goulots d'étranglement
Était-ce utile?

La solution

boost :: asio n’est pas aussi convivial que vous le souhaiteriez - il existe un gros verrou autour du code epoll dans boost / asio / detail / epoll_reactor.hpp, ce qui signifie qu’un seul thread peut appeler le appel système epoll du noyau. à la fois. Et pour de très petites requêtes, cela fait toute la différence (cela signifie que vous ne verrez que des performances à un seul thread).

Notez que c’est une limitation de la façon dont boost :: asio utilise les installations du noyau Linux, pas nécessairement le noyau Linux lui-même. Epoll syscall prend en charge plusieurs threads lorsqu’il utilise des événements déclenchés par un front, mais bien faire les choses (sans verrouillage excessif) peut être assez délicat.

BTW, j’ai effectué quelques travaux dans ce domaine (combinaison d’une boucle d’événement epoll entièrement multithread déclenchée par un bord avec des fils / fibres programmés par l’utilisateur) et mis du code à disposition sous nginetd projet.

Autres conseils

Comme vous utilisez EC2, tous les paris sont désactivés.

Essayez-le avec du matériel réel et vous pourrez alors voir ce qui se passe. Essayer de tester les performances des machines virtuelles est fondamentalement impossible.

Je n'ai pas encore compris en quoi l'EC2 est utile. Si quelqu'un le découvre, merci de me le faire savoir.

D'après vos commentaires sur l'utilisation du réseau,
Vous ne semblez pas avoir beaucoup de mouvement de réseau.

3 + 2,5 Mio / s correspond au parc de billes 50Mbps (par rapport à votre port 1 Gbps).

Je dirais que vous rencontrez l'un des deux problèmes suivants,

  1. Charge de travail insuffisante (faible taux de demandes de vos clients)
    • Blocage sur le serveur (génération de réponses perturbées)

En regardant les notes de cmeerw et les chiffres d'utilisation de votre CPU
(inactif à 50% + 20% + 0% + 0% )
cela semble très probablement une limitation de la mise en œuvre de votre serveur.
J'appuie la réponse de cmeerw (+1).

230 requêtes / s semble très faible pour de telles requêtes asynchrones simples. En tant que tel, utiliser plusieurs threads est probablement une optimisation prématurée: faites-le fonctionner correctement et accordez-le dans un seul thread, et voyez si vous en avez toujours besoin. Le simple fait de se débarrasser des verrouillages inutiles peut accélérer les choses.

Cet article contient des détails et une discussion sur les stratégies d'E / S du serveur Web. performance de style vers 2003. Quelqu'un at-il quelque chose de plus récent?

ASIO convient aux tâches petites à moyennes, mais il n’est pas très efficace pour tirer parti de la puissance du système sous-jacent. Ni les appels de socket bruts, ni même IOCP sous Windows, mais si vous êtes expérimenté, vous serez toujours meilleur que l'ASIO. Quoi qu'il en soit, il y a beaucoup de frais généraux avec toutes ces méthodes, mais davantage avec ASIO.

Pour ce que cela vaut. utiliser des appels de socket bruts sur mon HTTP personnalisé peut traiter 800 000 requêtes dynamiques par seconde avec un processeur I7 à 4 cœurs. Il sert à partir de la RAM, ce qui est nécessaire pour atteindre ce niveau de performance. À ce niveau de performance, le pilote réseau et le système d'exploitation consomment environ 40% de la CPU. En utilisant ASIO, je peux obtenir environ 50 à 100 000 requêtes par seconde. Ses performances sont très variables et principalement liées dans mon application. Le message de @cmeerw explique principalement pourquoi.

Un moyen d'améliorer les performances consiste à implémenter un proxy UDP. En interceptant les demandes HTTP, puis en les acheminant via UDP vers votre serveur UDP-HTTP principal, vous pouvez éviter beaucoup de temps système TCP dans les piles du système d'exploitation. Vous pouvez également avoir des extrémités avant qui passent directement par UDP, 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 quel bon front-end sans modification et que vous pouvez les échanger à volonté sans aucun impact. Vous avez juste besoin de quelques serveurs supplémentaires pour l'implémenter. Cette modification de mon exemple a réduit l'utilisation du processeur du système d'exploitation à 10%, ce qui a porté mes demandes par seconde à un peu plus d'un million sur ce serveur unique. Et FWIW Vous devriez toujours avoir une configuration d’avant-garde pour tout site performant car les interfaces peuvent mettre en cache des données sans ralentir le backend de requêtes dynamiques le plus important.

L'avenir semble être en train d'écrire votre propre pilote qui implémentera sa propre pile réseau afin que vous puissiez vous rapprocher le plus possible des demandes et y implémenter votre propre protocole. Ce qui n'est probablement pas ce que la plupart des programmeurs veulent entendre, car c'est plus compliqué. Dans mon cas, je serais en mesure d'utiliser 40% de CPU en plus et de passer à plus d'un million de requêtes dynamiques par seconde. La méthode du proxy UDP peut vous permettre d’atteindre des performances optimales sans avoir à le faire. Toutefois, vous aurez besoin de plus de serveurs. Toutefois, si vous effectuez autant de demandes par seconde, vous aurez généralement besoin de plusieurs cartes réseau et de plusieurs interfaces pour gérer la bande passante. un couple de mandataires UDP légers n’est pas si grave.

J'espère que cela vous sera utile.

Combien d'instances de io_service avez-vous? Boost asio a un exemple qui crée un io_service par CPU et utilisez-les à la manière de RoundRobin.

Vous pouvez toujours créer quatre threads et en affecter un par CPU, mais chaque thread peut interroger son propre io_service.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top