2010-01-22 14 views
10

Während ich die intellektuelle Herausforderung mag, die sich aus dem Entwurf von Multicore-Systemen ergibt, stelle ich fest, dass die meisten von ihnen nur unnötige vorzeitige Optimierung waren.Ist eine gute Multithread-Design-vorzeitige Optimierung?

Aber normalerweise haben alle Systeme etwas Leistungsbedarf und das Refactoring später in Multithreading-sichere Operationen ist schwer oder sogar nur wirtschaftlich nicht möglich, da es sich um eine komplette Neuschreibung mit einem anderen Algorithmus handeln würde.

Wie können Sie die Balance zwischen Optimierung und Erledigung von Aufgaben halten?

+0

Diese ziemlich subjektiv, empfehlen CW. –

+0

Können Sie konkretere Beispiele machen? Die "allgemeine Aussage" ist meiner Meinung nach falsch. Ich meine natürlich, dass eine Serverkomponente, die gleichzeitig Clientanforderungen bedient, eine Art von "Multithread-Design" benötigt. – Alex

+0

@Alex: Wenn du Webserver meinst, dann muss ich sagen, dass ich Server-Komponenten fast nie eines guten Multithread-Designs benötige, weil die Datenbank normalerweise der einzige Punkt der Synchronisation ist und ich nie eine Web-Server-Komponente gesehen habe, die algorithmisch war komplex zum Beispiel im Vergleich zu einer Datenbank oder einem Compiler. – Lothar

Antwort

1

Ich glaube, Threading gehorcht auch die Gesetze der Optimierung.
Das heißt, verschwende deine Zeit nicht und mache schnelle Operationen parallel.
Wenden Sie stattdessen Threads auf Tasks an, deren Ausführung sehr lange dauert.

Natürlich, wenn Systeme beginnen, 1000 + Kerne zu haben, dann könnte diese Antwort veraltet sein und muss überarbeitet werden. Aber andererseits, wenn Sie "Dinge erledigen" wollen, dann werden Sie Ihr Produkt definitiv vorher versenden wollen.

+0

Ich stimme dem zu ... verwende Threading für Dinge wie I/O oder wirklich schwere Berechnungen. Sie möchten einen Hauptthread nicht blockieren, während er auf einem schweren I/O wartet. – Polaris878

+0

-1: "Wenn Systeme mit mehr als 1000 Kernen beginnen" und wenn das System mehr als einen Kern hat. "Verschwenden Sie keine Zeit und machen Sie schnelle Operationen parallel": Was bedeutet "schnell"? Warum ist Parallelismus Zeitverschwendung? Wie sonst würden Sie die heutigen Prozessoren nutzen, die als 8-Kerne und mehr sogar im Verbrauchermarkt kommen? – Alex

+0

Das Problem, das ich hier erwähne, ist die Existenz von Engpässen. Es gibt sicherlich Teile des Codes, deren Ausführung länger dauert als andere. Das sind die Teile, die wirklich wichtig sind zu optimieren, und das sind die Teile, die wahrscheinlich Paralellismus verwenden könnten. Wenn Sie die Parallelität verwenden könnten, um eine gewöhnliche, aber lange Operation 10x zu beschleunigen, dann sollten Sie das tun. Mit "Zeitverschwendung" meine ich wie "Verschwendung von Entwicklungszeit". Bei der letzten Überprüfung kamen die meisten Computer in 2 Kernen, sogar 4 Kerne sind relativ selten, 8 Kerne sind wirklich ungewöhnlich. – luiscubal

4

Einführung Threading verbessert nicht automatisch die Leistung.

+2

In der Tat. Wenn Sie sich die Geschichte von Webservern ansehen, war das Entfernen von Threading für multiplexed I/O einer der ersten großen Meilensteine ​​bei der Verbesserung der Leistung. – slebetman

+0

Hier ist ein Beweis dafür, worauf sich Slebetman bezogen hat: http://www.kegel.com/c10k.html – Polaris878

+0

slebetman, kannst du das verlinken? Ich bin jetzt neugierig. –

3

Wenn Sie irgendetwas Komplexes tun, das Multithread ist, denken Sie besser darüber nach/entwerfen Sie es gut vorher. Andernfalls wird Ihr Programm entweder ein komplettes Desaster oder wird perfekt funktionieren MOST der Zeit und verrückte Sachen den anderen Teil der Zeit. Es ist schwierig, mit Multithreading nachweislich etwas richtig zu gestalten, aber es ist extrem wichtig. Also nein, ich denke nicht, dass gutes Multithread-Design eine vorzeitige Optimierung ist.

1

Vielleicht ist das eine gute Sache ist die Gestaltung Systeme mit einigen Merkmalen so, wenn Sie Multithreading einführen wollen Sie könnte es tun anmutig.

Ich bin mir nicht sicher, was diese Eigenschaften sind, aber ein analoges Beispiel kommt mir in den Sinn: Skalierung. Wenn Sie für kleine Operationen entwerfen, die ein zustandsloses System ausführen kann, können Sie natürlicher skalieren.

Diese Art von Dingen scheint mir wichtig.

Wenn es für Multithreading entwickelt ... dann ist es wichtig, einen vorzeitigen Ansatz.

Wenn es einfach hat einige Eigenschaften zu gewährleisten, die Skalierung oder Multithreading in Zukunft ermöglichen: dann ist es nicht so wichtig :)

EDIT oops, lese ich es wieder: vorzeitige Optimierung?

Optimierung: Ich weiß nicht, es ist gut, bis Sie das System arbeiten (und ohne Laster aus dem Versuch, die Dinge zu optimieren). Mach ein sauberes Design, etwas, das am flexibelsten und am einfachsten ist. Als nächstes können Sie optimieren, wenn Sie sehen, was wirklich benötigt wird.

9

Wenn Sie die Pipeline und Map-Reduce Entwurfsmuster folgen, das sollte genug sein.
Zerlegen Sie Dinge, so dass Sie in einer OS-Ebene Multi-Processing-Pipeline ausführen können.

Sie können dann tatsächlich in einer tatsächlichen Pipeline ausgeführt werden. Keine zusätzliche Arbeit. OS handhabt alles. Große Beschleunigungsmöglichkeit.

Sie könnten auch zu Threads wechseln. Ein bisschen Arbeit. OS behandelt einige Teile davon, Thread-Bibliotheken behandeln den Rest. Da Sie jedoch zur Entwurfszeit "Prozess" dachten, haben Ihre Threads keine verwirrenden Probleme mit der Datenfreigabe. Großer Gewinn für ein wenig Nachdenken.

+0

Genau richtig - nicht "für mehrere Gewinde" entwerfen. Machen Sie eine Abstraktion auf hoher Ebene, wo Daten parallel verarbeitet werden können. –

2

Sie sagen, dass Tage der Codierung Stunden Design sparen können.

Nicht alle Probleme oder Frameworks sind multi-threadable. Die Bibliotheken, auf die Sie angewiesen sind, sind beispielsweise möglicherweise nicht Thread-sicher. Viele Prozesse sind natürlich sequenziell und können nicht in parallelisierbare Teile aufgeteilt werden.

Und multi-threaded/multi-verarbeitet ist nur eine Möglichkeit, zu parallelisieren. Sie können zum Beispiel auch asynchrones IO verwenden.

Meiner Erfahrung nach ist das asynchrone Wechseln aus einem einzigen Thread viel seriöser als ein Multi-Threading. Aber dann, die Programme, die ich schreibe, lösen andere Probleme, naja, so ziemlich alle anderen.

1

Ich würde nie in Erwägung ziehen, für Multithreading in einer Anwendung nur für spekulative Leistungsüberlegungen zu entwerfen. Das liegt daran, dass es mit ein paar Techniken, die für jede Anwendung gut sind, einfach ist, später eine Operation multi-threaded zu machen. Die techniues Ich denke an sind:

  • Hart konst Verträge
    • In C++ können Sie eine Methode als const markieren, was bedeutet, es nicht den Wert einer Instanzvariablen ändern. Sie können einen Eingabeparameter auch für eine Methode als const markieren, was bedeutet, dass nur konstante Methoden für diesen Parameter aufgerufen werden können. Mit diesen beiden Techniken (und indem Sie keine "Tricks" verwenden, um diese Compiler-Durchsetzung zu umgehen) können Sie die Operationen, die Multi-Threading-bewusst sein müssen, reduzieren.
  • Dependency Inversion
    • Dies ist eine allgemeine Technik, wo alle externen Objekten durch ein Objekt benötigt werden, bei Bau/Initialisierungszeit oder als Teil des Verfahrens Signatur für das jeweilige Verfahren an sie übergeben. Mit dieser Technik wird zu 100% klar, welche Objekte möglicherweise durch eine Operation geändert werden können (die nichtkonstanten Instanzvariablen plus die nichtkonstanten Parameter der Operation). Wenn Sie das wissen, kennen Sie den Umfang der nicht-funktionalen Aspekte von eine Operation und Sie können Mutexe usw. zu Objekten hinzufügen, die zwischen parallelen Operationen geteilt werden können. Sie können dann Ihre Parallelität so gestalten, dass sie korrekt und effizient ist.
  • Favor funktioneller über verfahren
    • Ironischerweise bedeutet dies, nicht optimiert vorzeitig. Machen Sie Wertobjekte unveränderlich. In C# beispielsweise sind Zeichenfolgen unveränderlich, was bedeutet, dass alle Operationen auf ihnen neue Instanzen von Zeichenfolgenobjekten zurückgeben, keine geänderten Instanzen der vorhandenen Zeichenfolge. Die einzigen Objekte, die nicht unveränderlich sein sollten, sind ungebundene Arrays oder Objekte, die ungebundene Arrays enthalten, wenn diese Arrays wahrscheinlich häufig geändert werden. Ich würde argumentieren, dass unveränderliche Objekte leichter zu verstehen sind. Vielen Programmierern wurden Verfahrenstechniken beigebracht, was uns etwas fremd ist, aber wenn man anfängt, in unveränderlichen Begriffen zu denken, gehen schreckliche Aspekte der Präzeordinationsprogram- mierung, wie die Reihenfolge der Operationsabhängigkeit und Nebenwirkungen weg. Diese Aspekte sind in der Multithread-Programmierung noch schrecklicher, so dass die Verwendung eines funktionalen Stils im Klassen-Design auf viele Arten hilft.Wenn Maschinen schneller wachsen, werden die höheren Kosten unveränderlicher Objekte leichter und einfacher zu rechtfertigen. Heute ist es ein Gleichgewicht.
1

Themen existieren, um die Beschäftigung von mehreren Agenten einfacher zu programmieren zu machen.

  • Wenn die Agenten Benutzer sind, wie wenn Sie einen Thread pro Benutzer haben, erleichtern sie das Schreiben des Programms. Dies ist kein Leistungsproblem, es ist ein Problem der Leichtigkeit des Schreibens.

  • Wenn die Agenten I/O-Geräte sind, erleichtern sie das Schreiben eines Programms, das I/O parallel ausführt. Dies kann für die Leistung getan werden oder auch nicht.

  • Wenn die Agenten CPU-Kerne sind, machen sie es einfach, Programme zu schreiben, die mehrere Kerne parallel schalten. Das ist, wenn Threads mit der Leistung korrelieren.

  • Mit anderen Worten, wenn Sie Threads == Parallelität == Leistung denken, das trifft nur eine der Verwendungen von Threads.

    1

    Es gibt drei grundlegende Design-Optionen: Sync, Async oder Sync + Multi-Threading. Wählen Sie einen oder mehrere, wenn Sie verrückt sind.

    Ja, Sie müssen die akzeptablen Leistungserwartungen Ihrer Kunden während der Entwurfsphase Ihrer Anwendung verstehen, um die richtigen Entscheidungen treffen zu können. Für jedes nicht-triviale Projekt kann es ziemlich gefährlich und zeitraubend sein, High-Performance-Erwartungen als nachträglicher Einfall zu behandeln.

    Wenn Synchronisierung nicht erfüllt Kundenanforderungen:

    CPU begrenzte Systeme erfordern Auswahl von Multi-Thread/Prozess

    IO begrenzte Systeme (am häufigsten) können oft entweder Async oder MT gehen.

    Für E/A-Technologien wie State Threads können Sie Ihren Kuchen haben und es auch essen. (Synchronisierungsentwurf/w asynchrone Ausführung)

    0

    Was ist Ihre Weise, ein Gleichgewicht zwischen Optimierung und dem Erhalten von Sachen zu halten?

    Nehmen Sie sich die Implementierungsdetails leicht, aber entwerfen Sie Designs mit viel Raum zum Optimieren. Nun, das ist der schwierige Teil, aber es ist nicht so schwer wie es klingt, sobald Sie sich daran gewöhnen. Der allgemeine Grund, warum Leute sich in Flaschenhals-Designs gefangen finden, ist normalerweise, weil die Entwürfe zu granular sind.

    Als ein extremes Beispiel nehmen Sie eine Video-Processing-Anwendung, deren Design um eine abstrakte IPixel dreht. Die Abstraktion ermöglicht es der Software, Videos mit unterschiedlichen Pixelformaten problemlos zu verarbeiten und trotzdem einheitlichen Code zu schreiben, der mit allen Pixelformaten funktioniert.

    Eine solche Anwendung ist in Bezug auf die Leistung auf der zentralen Design-Ebene geschraubt, wird wahrscheinlich keine wettbewerbsfähige Leistung für die Bearbeitung, Codierung, Decodierung und Wiedergabe ohne eine epische architektonische Überarbeitung bieten. Und das ist, weil es sich dazu entschlossen hat, zu grob auf einer Ebene zu abstrahieren.

    Durch die Wahl, auf der Ebene eines Pixels zu abstrahieren, entsteht ein dynamischer Versandaufwand pro Pixel. Der analoge virtuelle Zeiger (oder was auch immer die Sprache verwendet), um Merkmale wie virtuellen Versand, Laufzeittyp-Information (Reflektion, etc.) usw. zuzulassen, ist wahrscheinlich oft größer als das gesamte Pixel selbst, wobei seine Speicherbenutzung und Cache-Fehler sequentiell verdoppelt oder verdreifacht werden verarbeiten. Außerdem, wenn Sie im Nachhinein Multithread-Bildverarbeitung in vielen Bereichen möchten, müssten Sie jeden einzelnen Platz, der mit einem IPixel gleichzeitig arbeitet, neu schreiben.

    In der Zwischenzeit hätte dies alles vermieden werden können, wenn die Software einfach ihre Abstraktionen auf einer gröberen Ebene wie IImage entworfen hätte und es vermieden hätte, einzelne Pixelobjekte dem Rest des Systems auszusetzen. Ein Bild ist effektiv eine Sammlung von Pixel (oft Millionen von Pixeln) und es kann Operationen, die viele Pixel auf einmal verarbeiten. Jetzt wird der Verarbeitungs- und Speicher-Overhead, der mit der Verarbeitung von Pixeln verbunden ist, für ein Bild mit einer Million Pixel auf 1/1.000.000 reduziert, zu welchem ​​Zeitpunkt es trivialisiert wird. Das lässt auch den Bildoperationen viel Spielraum, um zum Beispiel Pixel parallel zu verarbeiten und jetzt auf einer zentralen Ebene zu vektorisieren, ohne epische Mengen an Code neu zu schreiben, da der Client-Code nicht einzeln ein Pixel auf einmal bearbeitet, sondern stattdessen ganze Pixel anfordert Bildoperationen, die ausgeführt werden sollen.

    Während dies wie ein Kinderspiel mit Bildverarbeitung scheint, die inhärent ein sehr performance-kritischen Bereich ist, gibt es viel Raum, dies in anderen Domänen zu tun. Bei Ihrem klassischen Vererbungsbeispiel müssen Sie Dog nicht erben Mammal. Sie können Dogs erben Mammals.

    Zurück zu den Dingen, beginne ich mit einer datenorientierten Denkweise, nicht um die effizientesten cachefreundlichen, peinlich parallelen, thread-sicheren, SIMD-freundlichen Datendarstellungen und hochmodernen Datenstrukturen und Algorithmen zu erhalten beim ersten Versuch. Sonst könnte ich eine ganze Woche damit verbringen, Dinge mit VTune in der Hand abzustimmen, während Benchmarks immer schneller laufen (ich liebe das, aber es ist definitiv nicht produktiv, überall und im Voraus zu arbeiten). Ich habe nur genug darüber nachgedacht, um die geeignete Granularität zu bestimmen, die ich verwenden sollte, um Dinge zu entwerfen: "sollte ich das System von Dog oder abhängig machen?", so etwas. Und es erfordert nicht einmal so viel Nachdenken. Für OOP ist es so, "verarbeitet das System hunderttausend Hunde in jedem einzelnen Frame? Ja/nein?" Wenn "Ja", sollten Sie keine zentrale Schnittstelle Dog entwerfen und keine zentrale Schnittstelle IMammal entwerfen. Design Dogs erben IMammals, so wie wir die IPixel Schnittstelle in der analogen Bildverarbeitung Szenario oben vermeiden, wenn wir Millionen von Pixeln gleichzeitig verarbeiten werden.

    Die Größe der Daten sollte Ihnen auch einen Treffer geben. Wenn die Daten kleiner als 64 Byte oder weniger sind, besteht die Möglichkeit, dass sie keine Schnittstelle offenlegen, die Abhängigkeiten akkumuliert, es sei denn, sie ist definitiv nicht leistungskritisch. Stattdessen sollte es eine Sammlungsschnittstelle verfügbar machen, um mit vielen dieser Dinge gleichzeitig fertig zu werden. Unterdessen, wenn die Daten riesig sind, sagen wir 4 Kilobyte, dann ist es wahrscheinlich, dass es kaum helfen würde, eine Sammlungsschnittstelle zu enthüllen, und Sie könnten einfach eine skalare Schnittstelle entwerfen, die sich mit einem dieser Dinge gleichzeitig beschäftigt.

    Multithreading ist die gleiche Art von Sache. Sie möchten beispielsweise nicht zu detailliert auf eine Ebene zugreifen und möchten nicht, dass Ihre Zugriffsmuster weiterhin gemeinsame Ressourcen treffen. Für Thread-Sicherheit möchten Sie auch in der Lage sein, einen Codeabschnitt zu nehmen und leicht zu verstehen, auf welchen Status von welchem ​​Thread zugegriffen wird.Um dies zu erreichen, benötigen Sie ein gröberes Design, das eine homogenere Verarbeitung aufweist, so dass Sie die Speicherzugriffsmuster innerhalb der Implementierung des Designs selbst steuern und minimieren können, den Zugriff auf gemeinsam genutzte Ressourcen minimieren, Sperren auf zu granularer Ebene vermeiden oder Vermeiden Sie möglicherweise sogar eine völlige Sperrung. Solange Ihre Designs genug Raum zum Atmen lassen, können Sie im Nachhinein viel erreichen, aber der Schlüssel ist, dass Sie sich Platz zum Atmen lassen.

    Ein Teeny-Ding, auf das eine Schiffsladung verschiedener Dinge im gesamten System angewiesen ist, die nicht-homogene Verarbeitung machen, hinterlässt keinen solchen Raum. Dort könnte man das Szenario des analogen Rennwagens mit nur 10 Metern Straße nutzen. Eine heftige Sache, die eine Bootsladung von Teeny-Sachen verarbeitet, die sie homogen speichert, lässt endlosen Raum übrig, um später zu optimieren.

    Verwandte Themen