Ich prüfe derzeit eine Multithread-Anwendung, die Multithread-fähig sein soll, um sie in der Lage zu sein, alle verfügbaren Kerne zu verwenden und theoretisch eine bessere / bessere Leistung zu liefern (besser ist der kommerzielle Ausdruck für besser: P)
Was sollte ich bei der Programmierung von Multithread-Anwendungen beachten?
Ich meine Dinge, die die Performance stark beeinflussen, vielleicht sogar bis zu dem Punkt, an dem Sie mit Multithreading überhaupt nichts mehr erreichen, aber viel an Komplexität verlieren. Was sind die großen roten Fahnen für Multithreading-Anwendungen?
Sollte ich anfangen, die Schlösser in Frage zu stellen und auf eine Lock-Free-Strategie zu schauen, oder gibt es andere wichtige Punkte, die ein Warnlicht aufleuchten sollten?
Bearbeiten: Die Art von Antworten, die ich möchte, sind ähnlich der Antwort von Janusz, ich möchte rote Warnungen im Code nachschlagen, ich weiß, dass die Anwendung nicht so gut funktioniert Ich sollte wissen, wo ich anfangen soll, was mich beunruhigen sollte und wo ich meine Anstrengungen machen sollte. Ich weiß, dass es eine allgemeine Frage ist, aber ich kann nicht das gesamte Programm posten und wenn ich einen Codeabschnitt wählen könnte, müsste ich nicht erst fragen.
Ich benutze Delphi 7, obwohl die Anwendung in .NET (c #) für das nächste Jahr portiert / remake wird, also würde ich eher Kommentare hören, die allgemein anwendbar sind, und wenn sie spezifisch sein müssen entweder eine dieser Sprachen
Eine Sache, die definitiv vermieden werden sollte, ist viel Schreibzugriff auf dieselben Cache-Zeilen von Threads.
Beispiel: Wenn Sie eine Zählervariable verwenden, um die Anzahl der von allen Threads verarbeiteten Elemente zu zählen, beeinträchtigt dies die Leistung, da die CPU-Cachezeilen synchronisiert werden müssen, wenn die andere CPU in die Variable schreibt.
Etwas, das Sie beim Sperren beachten sollten: Sperren Sie so kurz wie möglich. Zum Beispiel stattdessen:
%Vor%Mach das (wenn möglich):
%Vor% Natürlich funktioniert dieses Beispiel nur, wenn DoSomethingIfTrue()
und DoSomethingIfFalse()
keine Synchronisation erfordern, aber es veranschaulicht diesen Punkt: Sperren für eine so kurze Zeit wie möglich, während vielleicht nicht immer Ihre Leistung verbessern, wird die verbessern Sicherheit Ihres Codes, indem es die Oberfläche für Synchronisationsprobleme reduziert.
Und in bestimmten Fällen verbessert es die Leistung. Wenn Sie für längere Zeit gesperrt bleiben, bedeutet dies, dass andere Threads, die auf den Zugriff auf eine Ressource warten, länger warten werden.
Mehr Threads, dann gibt es Kerne, bedeutet normalerweise, dass das Programm nicht optimal funktioniert.
Also ist ein Programm, das normalerweise viele Threads erzeugt, nicht auf die beste Art und Weise entworfen. Ein gutes Beispiel für diese Vorgehensweise sind die klassischen Socket-Beispiele, bei denen jede eingehende Verbindung einen eigenen Thread für die Verbindung erhält. Es ist eine sehr nicht skalierbare Möglichkeit, Dinge zu tun. Je mehr Threads vorhanden sind, desto mehr Zeit muss das Betriebssystem für den Kontextwechsel zwischen Threads verwenden.
Sie sollten zuerst mit Amdahls Gesetz vertraut sein.
Wenn Sie Java verwenden, empfehle ich das Buch Java Concurrency in Practice ; Der größte Teil der Hilfe ist jedoch spezifisch für die Java-Sprache (Java 5 oder höher).
Im Allgemeinen wird durch das Reduzieren der Größe des gemeinsam genutzten Speichers das Ausmaß der möglichen Parallelität erhöht, und für die Leistung sollte dies eine wichtige Rolle spielen.
Threading mit GUI's ist eine andere Sache, auf die man achten sollte, aber es sieht so aus, als ob sie für dieses spezielle Problem nicht relevant ist.
Was die Leistung zunichte macht, ist, wenn sich zwei oder mehr Threads die gleichen Ressourcen teilen. Dies kann ein Objekt sein, das beide verwenden, oder eine Datei, die beide verwenden, ein Netzwerk, das beide verwenden, oder ein Prozessor, der beide verwendet. Sie können diese Abhängigkeiten für freigegebene Ressourcen nicht vermeiden, versuchen Sie jedoch nach Möglichkeit, die gemeinsame Nutzung von Ressourcen zu vermeiden.
Laufzeit-Profiler funktionieren möglicherweise nicht gut mit einer Multithread-Anwendung. Nichtsdestotrotz wird alles, was eine Singlethread-Anwendung verlangsamt, eine Multithread-Anwendung verlangsamen. Es kann eine Idee sein, Ihre Anwendung als Single-Thread-Anwendung auszuführen und einen Profiler zu verwenden, um herauszufinden, wo sich die Leistungs-Hotspots (Engpässe) befinden.
Wenn es als Multithread-Anwendung ausgeführt wird, können Sie mithilfe des Leistungsüberwachungstools des Systems feststellen, ob Sperren ein Problem darstellen. Unter der Annahme, dass Ihre Threads statt busy-wait gesperrt werden, ist das Sperren für 100% CPU kein Problem. Umgekehrt ist etwas, das wie 50% der gesamten CPU-Auslastung auf einem Dual-Prozessor-Rechner aussieht, ein Zeichen dafür, dass nur ein Thread läuft, und daher ist Ihr Sperren möglicherweise ein Problem, das mehr als einen Thread verhindert (wenn Sie die Anzahl der CPUs zählen Ihre Maschine, achten Sie auf Multi-Core- und Hyperthreading).
Sperren befinden sich nicht nur in Ihrem Code, sondern auch in den von Ihnen verwendeten APIs: z. der Heap-Manager (wann immer Sie Speicher reservieren und löschen), vielleicht in Ihrer Logger-Implementierung, vielleicht in einigen der O / S-APIs, etc.
Sollte ich anfangen, die Schlösser in Frage zu stellen und auf eine Lock-Free-Strategie zu achten?
Ich stelle immer die Schlösser in Frage, habe aber nie eine Lock-free-Strategie verwendet; Stattdessen ist es mein Ehrgeiz, Schlösser wo nötig zu verwenden, so dass es immer sicher ist, aber niemals blockiert wird, und um sicherzustellen, dass Schlösser für eine winzige Menge Zeit erworben werden (zB für nicht mehr als die Zeit, die man braucht, um zu schieben oder zu platzen Zeiger auf eine thread-sichere Warteschlange), so dass die maximale Zeit, die ein Thread blockiert werden kann, ist unbedeutend im Vergleich zu der Zeit, die es sinnvolle Arbeit verbringt.
Sie erwähnen nicht die Sprache, die Sie verwenden, daher werde ich eine allgemeine Aussage zum Sperren treffen. Das Sperren ist ziemlich teuer, insbesondere das naive Sperren, das in vielen Sprachen vorhanden ist. In vielen Fällen lesen Sie eine gemeinsame Variable (im Gegensatz zum Schreiben). Das Lesen ist threadsicher, solange es nicht gleichzeitig mit einem Schreibvorgang stattfindet. Sie müssen es jedoch immer noch sperren. Die naivste Form dieser Sperre besteht darin, das Lesen und das Schreiben als den gleichen Operationstyp zu behandeln und den Zugriff auf die gemeinsam genutzte Variable von anderen Lesevorgängen sowie Schreibvorgängen zu beschränken. Eine Lese- / Schreibsperre kann die Leistung erheblich verbessern. Ein Schriftsteller, unendliche Leser. Bei einer App, an der ich gearbeitet habe, habe ich eine Leistungsverbesserung von 35% gesehen, als ich zu diesem Konstrukt wechselte. Wenn Sie in .NET arbeiten, ist die korrekte Sperre der ReaderWriterLockSlim.
Ich empfehle, mehr Prozesse als mehrere Threads innerhalb desselben Prozesses auszuführen, wenn es sich um eine Serveranwendung handelt.
Der Vorteil der Aufteilung der Arbeit auf mehrere Prozesse auf einer Maschine besteht darin, dass die Anzahl der Server leicht erhöht werden kann, wenn mehr Leistung benötigt wird, als ein einzelner Server liefern kann.
Sie reduzieren auch die Risiken komplexer Multithread-Anwendungen, bei denen Deadlocks, Engpässe usw. die Gesamtleistung verringern.
Es gibt kommerzielle Frameworks, die die Entwicklung von Serversoftware vereinfachen, wenn es um Load Balancing und verteilte Warteschlangenverarbeitung geht. Die Entwicklung einer eigenen Load-Sharing-Infrastruktur ist jedoch nicht so kompliziert im Vergleich zu dem, was Sie allgemein in einer Multithread-Anwendung finden / p>
Ich benutze Delphi 7
Sie verwenden COM-Objekte dann explizit oder implizit; Wenn dies der Fall ist, haben COM-Objekte ihre eigenen Komplikationen und Einschränkungen beim Threading: Prozesse, Threads, und Wohnungen .
Sie sollten zuerst ein Tool zum Überwachen von Threads erhalten, die für Ihre Sprache, Ihr Framework und Ihre IDE spezifisch sind. Ihr eigener Logger könnte auch gut funktionieren (Resume Time, Sleep Time + Duration). Von dort aus können Sie nach leistungsschwachen Threads suchen, die nicht viel ausführen oder zu lange warten, bis etwas passiert. Vielleicht möchten Sie das Ereignis, auf das sie warten, so früh wie möglich ausführen.
Da Sie beide Kerne verwenden möchten, sollten Sie die Verwendung der Kerne mit einem Werkzeug überprüfen, das die Prozessorauslastung für beide Kerne nur für Ihre Anwendung grafisch darstellen kann, oder einfach sicherstellen, dass Ihr Computer so leer wie möglich ist.
>Außerdem sollten Sie Ihre Anwendung profilieren, nur um sicherzustellen, dass die in den Threads ausgeführten Aufgaben effizient sind, aber achten Sie auf vorzeitige Optimierung. Es ist nicht sinnvoll, das Multiprocessing zu optimieren, wenn die Threads selbst schlecht arbeiten.
Die Suche nach einer Lock-Free-Strategie kann sehr hilfreich sein, aber es ist nicht immer möglich, Ihre Anwendung auf eine Lock-Free-Art auszuführen.
Threads entsprechen nicht immer der Leistung.
In bestimmten Betriebssystemen ist es viel besser als in anderen, aber wenn Sie etwas schlafen lassen oder die Zeit bis zum Signalisieren aufgeben können ... oder nicht für fast alles einen neuen Prozess starten, vor dem Sie sich schützen Die Anwendung wird im Kontextwechsel verdeckt.
Tags und Links multithreading performance