C++ Socket Server – CPU kann nicht ausgelastet werden

Lesezeit: 9 Minuten

C Socket Server CPU kann nicht ausgelastet werden
Alex Schwarz

Ich habe einen Mini-HTTP-Server in C++ mit boost::asio entwickelt und teste ihn jetzt mit mehreren Clients und konnte die CPU nicht annähernd auslasten. Ich teste auf einer Amazon EC2-Instanz und erhalte etwa 50 % Auslastung einer CPU, 20 % einer anderen, und die verbleibenden zwei sind im Leerlauf (laut htop).

Einzelheiten:

  • Der Server startet einen Thread pro Kern
  • Anfragen werden empfangen, geparst, verarbeitet und Antworten geschrieben
  • Die Anforderungen beziehen sich auf Daten, die aus dem Speicher gelesen werden (schreibgeschützt für diesen Test).
  • Ich “lade” den Server mit zwei Maschinen, auf denen jeweils eine Java-Anwendung ausgeführt wird, die 25 Threads ausführt und Anforderungen sendet
  • Ich sehe einen Durchsatz von etwa 230 Anfragen/Sek Anwendung Anfragen, die sich aus vielen HTTP-Anfragen zusammensetzen)

Worauf sollte ich also achten, um dieses Ergebnis zu verbessern? Da die CPU größtenteils im Leerlauf ist, möchte ich diese zusätzliche Kapazität nutzen, um einen höheren Durchsatz zu erzielen, sagen wir 800 Anfragen/Sek. oder was auch immer.

Ideen, die ich hatte:

  • Die Anfragen sind sehr klein und werden oft in wenigen Millisekunden erfüllt. Ich könnte den Client so ändern, dass er größere Anfragen sendet/verfasst (möglicherweise mithilfe von Batching).
  • Ich könnte den HTTP-Server so ändern, dass er das Select-Entwurfsmuster verwendet, ist das hier angemessen?
  • Ich könnte einige Profile erstellen, um zu versuchen zu verstehen, was die Engpässe sind/sind

  • Kann man davon ausgehen, dass Sie einen 1-Gbit/s-Port auf dem Server haben? Was sind Ihre Anfrage- und Antwortgrößen (auf dem Draht)?

    – Nik

    5. August 09 um 18:26 Uhr

  • Wie hoch ist die Bandbreitenauslastung am Server-Netzwerkport (von dem ich annehme, dass er 1 Gbit / s beträgt)

    – Nik

    5. August 09 um 18:26 Uhr

  • Der Test läuft auf EC2, das meines Erachtens Gigabit verwendet. Bmon berichtet über eine TX-Rate von 3 MB (Megabit, glaube ich) und eine RX-Rate von 2,5 MB. Viele Anfragen/Antworten sind klein (bis zu 100 Bytes), aber einige Antworten sind bis zu 1 MB groß, Anfragen wahrscheinlich bis zu 0,25 MB

    – Alex Schwarz

    5. August 2009 um 18:30 Uhr

  • Was ist die Belastung Ihrer Kunden? Wenn Sie nur 1 Thread pro Kern haben und kein IO-Multiplexing (Select/Poll oder ähnliches) verwenden, erhalten Sie nicht viel Parallelität – und die Threads werden wahrscheinlich viel Zeit mit I/O verbringen.

    – Nr

    5. August 09 um 19:16 Uhr

  • Auf jedem Client-Rechner läuft ein Prozess mit 25 laufenden Threads

    – Alex Schwarz

    5. August 09 um 19:21 Uhr

boost::asio ist nicht so Thread-freundlich, wie Sie hoffen würden – es gibt eine große Sperre um den epoll-Code in boost/asio/detail/epoll_reactor.hpp, was bedeutet, dass jeweils nur ein Thread den epoll-Systemaufruf des Kernels aufrufen kann . Und bei sehr kleinen Anforderungen macht dies den Unterschied (was bedeutet, dass Sie nur ungefähr Single-Thread-Leistung sehen werden).

Beachten Sie, dass dies eine Einschränkung dafür ist, wie boost::asio die Linux-Kernel-Einrichtungen verwendet, nicht unbedingt den Linux-Kernel selbst. Der epoll-Systemaufruf unterstützt zwar mehrere Threads, wenn flankengetriggerte Ereignisse verwendet werden, aber es richtig hinzubekommen (ohne übermäßiges Sperren) kann ziemlich schwierig sein.

Übrigens habe ich einige Arbeiten in diesem Bereich durchgeführt (Kombination einer vollständig multithreaded, kantengetriggerten Epoll-Ereignisschleife mit vom Benutzer geplanten Threads/Fasern) und einige Codes unter dem zur Verfügung gestellt nginetd Projekt.

  • (+1) cmeer Ich habe einen unbeantworteten Beitrag, der sich auf die Leistung von boost::asio im Allgemeinen unter Windows und Linux bezieht. Wenn Sie große Teile von Asio gelesen haben, kommen Sie bitte und beantworten Sie meinen Beitrag 😛

    – Hassan Syed

    14. Dezember 2009 um 15:31 Uhr

  • Ich war wirklich besorgt über diese globale Sperre. Es ist kein so großes Problem, wie es scheint. Der Engpass kann nur in Szenarien mit hohem Durchsatz auftreten. Wenn asio jedoch im epoll-Modus (Linux) läuft, versucht es präventiv zu schreiben oder zu lesen, wenn die async_* Anruf wird ausgegeben. In einem Szenario mit hohem Eingang ist die Buchse normalerweise bereit zum Lesen und Lassen async_read epoll komplett überspringen. Sie können sich keine bessere Netzwerkleistung wünschen.

    – geschickt_code

    21. Dezember 2009 um 20:06 Uhr

  • Ich glaube nicht, dass es so ist. Ja, es sieht so aus, als ob der epoll-Reaktor für die gesamte Dauer der Funktion run() eine bereichsbezogene Sperre hat, aber sie wird vorübergehend freigegeben (“lock.unlock();”), bevor epoll_wait aufgerufen wird, und erneut gesperrt, nachdem epoll_wait zurückgibt (“lock. sperren();”). Ich bin mir jedoch nicht sicher, warum dies auf diese Weise anstelle von zwei Zielfernrohrsperren gemacht wurde.

    – Alex B

    1. April 10 um 0:29 Uhr

  • Sie können die Sperre mit dem Makro BOOST_ASIO_DISABLE_THREADS entfernen. Wenn es nur einen Thread gibt, der den io_service verwendet, sollte dies sicher sein.

    – Josua Chia

    31. Oktober 15 um 3:08 Uhr

  • Ich bin verwirrt und würde wirklich gerne wissen, wie sich die globale Sperre auswirkt. Kann es tatsächlich eine Multithread-Anwendung zu einer Single-Thread-Anwendung degenerieren?

    – Russoue

    28. November 16 um 7:29 Uhr

Da Sie EC2 verwenden, sind alle Wetten ungültig.

Probieren Sie es mit echter Hardware aus, und dann können Sie vielleicht sehen, was passiert. Der Versuch, Leistungstests in VMs durchzuführen, ist im Grunde unmöglich.

Ich habe noch nicht herausgefunden, wofür EC2 nützlich ist, wenn jemand es herausfindet, lassen Sie es mich bitte wissen.

  • Dieses System wird in EC2 eingesetzt, daher wäre es meiner Meinung nach nicht hilfreich, die Leistung des Systems auf echter Hardware zu testen.

    – Alex Schwarz

    6. August 09 um 13:48 Uhr

  • Marks Argument ist gültig: Verwenden Sie für die Profilerstellung eine echte Maschine oder zumindest eine kontrolliertere Umgebung. Stellen Sie auf EC2 bereit, was Sie möchten, aber verstehen Sie, dass Sie in einem VM-Image ausgeführt werden und das bedeutet, dass Ihre “untätige” CPU möglicherweise nur darauf zurückzuführen ist, dass ein anderer Mandant auf der Box für eine Weile die gesamte CPU erhalten hat. Und das erschwert die Profilerstellung.

    – Jam

    18. November 09 um 6:46 Uhr

  • Da zu einem bestimmten Zeitpunkt mehrere hunderttausend EC2-Instanzen laufen (wie ich zuletzt gehört habe), denke ich, dass viele Leute wissen, wofür es nützlich ist. Sie sollten sich fragen, was sie wissen, was Sie nicht wissen.

    – Eloff

    20. Dezember 11 um 1:07 Uhr

  • +1 für den Hinweis auf Leistungstests in VMs ist unmöglich. Besonders für Netzwerkszenarien – Sie müssen mit physischen Boxen und physischen Switches testen und in der Lage sein, QoS zu überwachen. Sobald Sie fertig sind, können Sie auf EC2 pushen. Und wenn Sie dann Probleme mit der CPU-/RAM-Auslastung haben, können Sie sicher sein, dass EC2/Rackspace schuld ist.

    – quixver

    19. Februar 13 um 21:19 Uhr

Aus Ihren Kommentaren zur Netzwerkauslastung
Sie scheinen nicht viel Netzwerkbewegung zu haben.

3 + 2.5 MiB/sec ist um die 50Mbps Baseballstadion (im Vergleich zu Ihrem 1-Gbit/s-Port).

Ich würde sagen, Sie haben eines der folgenden zwei Probleme:

  1. Unzureichende Arbeitsbelastung (niedrige Anfragerate Ihrer Kunden)
    • Blockierung im Server (gestörte Antwortgenerierung)

Anschauen cmeerw‘s Notizen und Ihre CPU-Auslastungszahlen
(Leerlauf bei 50% + 20% + 0% + 0%)
Es scheint höchstwahrscheinlich eine Einschränkung in Ihrer Serverimplementierung zu sein.
Ich stimme zu cmeerwAntwort von (+1).

  • Er führt Tests auf Amazons EC2 Cloud Computing Cluster durch. Eine möglicherweise schlechte Leistung auf EC2 ist schwer auszuschließen.

    – unixman83

    20. Februar 12 um 7:23 Uhr


230 Anfragen/Sek. scheinen für solch einfache asynchrone Anfragen sehr niedrig zu sein. Daher ist die Verwendung mehrerer Threads wahrscheinlich eine verfrühte Optimierung – sorgen Sie dafür, dass es in einem einzigen Thread richtig funktioniert und optimiert wird, und prüfen Sie, ob Sie sie noch benötigen. Nur die Beseitigung unnötiger Sperren kann die Dinge beschleunigen.

Dieser Beitrag hat einige Details und Diskussionen zu E/A-Strategien für die Leistung im Webserver-Stil um 2003. Hat jemand etwas Neueres?

1642882210 301 C Socket Server CPU kann nicht ausgelastet werden
Ryler Sturden

ASIO eignet sich gut für kleine bis mittlere Aufgaben, aber es ist nicht sehr gut darin, die Leistung des zugrunde liegenden Systems zu nutzen. Weder Raw-Socket-Aufrufe noch IOCP unter Windows, aber wenn Sie erfahren sind, werden Sie immer besser als ASIO sein. In jedem Fall gibt es bei all diesen Methoden eine Menge Overhead, nur mehr bei ASIO.

Für was es wert ist. Die Verwendung von Raw-Socket-Aufrufen auf meinem benutzerdefinierten HTTP kann 800.000 dynamische Anforderungen pro Sekunde mit einem 4-Kern-I7 bedienen. Es wird aus dem RAM bedient, wo Sie für dieses Leistungsniveau sein müssen. Bei diesem Leistungsniveau verbrauchen der Netzwerktreiber und das Betriebssystem etwa 40 % der CPU. Mit ASIO kann ich etwa 50 bis 100.000 Anfragen pro Sekunde erhalten, die Leistung ist ziemlich variabel und hauptsächlich in meiner App gebunden. Der Beitrag von @cmeerw erklärt meistens warum.

Eine Möglichkeit, die Leistung zu verbessern, besteht darin, einen UDP-Proxy zu implementieren. Indem Sie HTTP-Anforderungen abfangen und sie dann über UDP an Ihren Backend-UDP-HTTP-Server weiterleiten, können Sie eine Menge TCP-Overhead in den Betriebssystemstapeln umgehen. Sie können auch Frontends haben, die UDP selbst durchleiten, was selbst nicht allzu schwierig sein sollte. Ein Vorteil eines HTTP-UDP-Proxys besteht darin, dass Sie jedes gute Frontend ohne Änderungen verwenden und nach Belieben ohne Auswirkungen austauschen können. Sie brauchen nur ein paar Server mehr, um es zu implementieren. Diese Änderung an meinem Beispiel senkte die CPU-Auslastung des Betriebssystems auf 10 %, was meine Anfragen pro Sekunde auf etwas mehr als eine Million an diesem einzelnen Backend erhöhte. Und FWIW Sie sollten immer ein Frontend-Backend-Setup für jede performante Site haben, da die Frontends Daten zwischenspeichern können, ohne das wichtigere Backend für dynamische Anforderungen zu verlangsamen.

Die Zukunft scheint darin zu bestehen, einen eigenen Treiber zu schreiben, der einen eigenen Netzwerkstapel implementiert, damit Sie so nah wie möglich an die Anforderungen herankommen und dort Ihr eigenes Protokoll implementieren können. Was die meisten Programmierer wahrscheinlich nicht hören wollen, da es komplizierter ist. In meinem Fall wäre ich in der Lage, 40 % mehr CPU zu verbrauchen und auf über 1 Million dynamische Anfragen pro Sekunde zu kommen. Die UDP-Proxy-Methode kann Sie der optimalen Leistung nahe bringen, ohne dies tun zu müssen, aber Sie benötigen mehr Server – wenn Sie jedoch so viele Anfragen pro Sekunde ausführen, benötigen Sie normalerweise mehrere Netzwerkkarten und mehrere Frontends, um die Bandbreite zu bewältigen Ein paar leichtgewichtige UDP-Proxys darin sind keine so große Sache.

Hoffe, einiges davon kann für Sie nützlich sein.

  • Möchten Sie ein Beispiel oder ein funktionierendes Projekt zeigen? Ohne sie ist dies so hilfreich wie irrelevantes Gerede. Ich versuche nicht, Sie zu erniedrigen, aber hier wird ein konkreter Code benötigt.

    – Abhinav Gauniyal

    19. April 17 um 16:26 Uhr

C Socket Server CPU kann nicht ausgelastet werden
clark.li

Wie viele Instanzen von io_service haben Sie? Boost asio hat eine Beispiel die einen io_service pro CPU erstellen und diese in der Art von RoundRobin verwenden.

Sie können immer noch vier Threads erstellen und einen pro CPU zuweisen, aber jeder Thread kann seinen eigenen io_service abfragen.

  • Möchten Sie ein Beispiel oder ein funktionierendes Projekt zeigen? Ohne sie ist dies so hilfreich wie irrelevantes Gerede. Ich versuche nicht, Sie zu erniedrigen, aber hier wird ein konkreter Code benötigt.

    – Abhinav Gauniyal

    19. April 17 um 16:26 Uhr

.

594410cookie-checkC++ Socket Server – CPU kann nicht ausgelastet werden

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy