Die Leistung von gleichzeitigem Code unter Verwendung von dispatch_group_async ist VIEL langsamer als bei einer Single-Thread-Version

Ich habe in letzter Zeit einige Experimente angestellt, um mit einer großen Anzahl von Zufallszahlen "normalverteilte" Glockenkurven zu erzeugen.

Der Ansatz ist einfach:

Erstellen Sie ein Array von Ganzzahlen und setzen Sie es auf Null. (Ich verwende 2001 Ganzzahlen)Berechnen Sie Indizes in diesem Array wiederholt und indizieren Sie diesen Eintrag im Array wie folgtSchleife entweder 999 oder 1000 mal. Bei jeder Iteration:Setze einen Array-Index mit dem Center-Wert (1000)Generiere eine Zufallszahl = + 1 / -1. und fügen Sie es dem Array-Index hinzuErhöhen Sie am Ende der Schleife den Wert am berechneten Array-Index.

Da zufällige Werte 0/1 ungefähr so häufig auftreten, bleibt der Endindexwert aus der obigen inneren Schleife tendenziell nahe am Mittelwert. Indexwerte, die viel größer / kleiner als der Startwert sind, werden immer ungewöhnlicher.

Nach einer großen Anzahl von Wiederholungen nehmen die Werte im Array die Form einer Normalverteilungsglockenkurve an. Die von mir verwendete Zufallsfunktion arc4random_uniform () mit hoher Qualität ist jedoch ziemlich langsam, und es sind VIELE Iterationen erforderlich, um eine glatte Kurve zu erzeugen.

Ich wollte 1.000.000.000 (eine Milliarde) Punkte zeichnen. Das Laufen auf dem Haupt-Thread dauert ungefähr 16 Stunden.

Ich habe beschlossen, den Berechnungscode neu zu schreiben, um dispatch_async zu verwenden, und ihn auf meinem 8-Core Mac Pro auszuführen.

Am Ende habe ich dispatch_group_async () verwendet, um 8 Blöcke zu senden, und dispatch_group_notify (), um das Programm zu benachrichtigen, wenn alle Blöcke die Verarbeitung beendet haben.

Der Einfachheit halber schreiben alle 8 Blöcke beim ersten Durchlauf in dasselbe Array von NSUInteger-Werten. Es besteht eine geringe Wahrscheinlichkeit, dass eine Race-Bedingung beim Lesen / Ändern eines Schreibvorgangs für einen der Array-Einträge auftritt. In diesem Fall geht jedoch lediglich ein Wert verloren. Ich hatte vor, dem Array-Inkrement später eine Sperre hinzuzufügen (oder sogar separate Arrays in jedem Block zu erstellen und sie anschließend zu summieren).

Wie auch immer, ich habe den Code überarbeitet, um dispatch_group_async () zu verwenden und 1/8 der Gesamtwerte in jedem Block zu berechnen. Dann habe ich meinen Code so eingestellt, dass er ausgeführt wird. Zu meiner völligen Verwirrung läuft der gleichzeitige Code, während er alle Kerne auf meinem Mac ausschöpftVIEL langsamer als der Singlethread-Code.

Wenn ich mit einem einzelnen Thread arbeite, erhalte ich ungefähr 17.800 Punkte pro Sekunde. Bei Ausführung mit dispatch_group_async sinkt die Leistung auf 665 Punkte / Sekunde oderungefähr 1/26 so schnell. Ich habe die Anzahl der von mir eingereichten Blöcke variiert - 2, 4 oder 8, das spielt keine Rolle. Leistung ist schrecklich. Ich habe auch versucht, einfach alle 8 Blöcke mit dispatch_async ohne dispatch_group zu senden. Das ist auch egal.

Derzeit ist keine Blockierung / Sperre aktiv: Alle Blöcke werden mit voller Geschwindigkeit ausgeführt. Ich bin völlig verblüfft, warum der gleichzeitige Code langsamer ausgeführt wird.

Der Code ist jetzt ein wenig durcheinander, weil ich ihn überarbeitet habe, um entweder mit einem Thread oder gleichzeitig zu arbeiten, damit ich ihn testen kann.

Hier ist der Code, der die Berechnungen ausführt:

randCount = 2;
#define K_USE_ASYNC 1

#if K_USE_ASYNC
    dispatch_queue_t highQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    //dispatch_group_async

    dispatch_group_t aGroup = dispatch_group_create();
    int totalJobs = 8;
    for (int i = 0; i<totalJobs; i++)
    {
      dispatch_group_async(aGroup,
                           highQ,
                           ^{
                             [self calculateNArrayPoints: KNumberOfEntries /totalJobs
                                          usingRandCount: randCount];
                           });
    }


    dispatch_group_notify(aGroup,
                          dispatch_get_main_queue(),
                          allTasksDoneBlock
                          );
#else
    [self calculateNArrayPoints: KNumberOfEntries
                 usingRandCount: randCount];
    allTasksDoneBlock();
#endif

Und die gängige Berechnungsmethode, die sowohl von der Singlethread- als auch von der Concurrent-Version verwendet wird:

+ (void) calculateNArrayPoints: (NSInteger) pointCount usingRandCount: (int) randCount;
{
  int entry;
  int random_index;

  for (entry =0; entry<pointCount; entry++)
  {
    static int processed = 0;
    if (entry != 0 && entry%100000 == 0)
    {
      [self addTotTotalProcessed: processed];
      processed = 0;
    }

    //Start with a value of 1000 (center value)
    int value = 0;

    //For each entry, add +/- 1 to the value 1000 times.
    int limit  = KPinCount;
    if (randCount==2)
      if (arc4random_uniform(2) !=0)
        limit--;
    for (random_index = 0; random_index<limit; random_index++)
    {
      int random_value = arc4random_uniform(randCount);
      /*
       if 0, value--
       if 1, no change
       if 2, value++
       */
      if (random_value == 0)
        value--;
      else if (random_value == randCount-1)
        value++;
    }
    value += 1000;
    _bellCurveData[value] += 1;
    //printf("\n\nfinal value = %d\n", value);
    processed++;
  }
}

Dies ist ein schnelles und schmutziges Lernprojekt. Es läuft sowohl auf Mac als auch auf iOS und verwendet daher eine gemeinsame Dienstprogrammklasse. Die Utilities-Klasse ist nichts anderes als Klassenmethoden. Es wurde keine Instanz der Dienstprogrammmethode erstellt. Es hat eine peinliche Anzahl von globalen Variablen. Wenn ich am Ende irgendetwas Nützliches mit dem Code mache, werde ich ihn überarbeiten, um ein Dienstprogramm-Singleton zu erstellen und alle globalen Variablen in Instanzvariablen auf dem Singleton zu konvertieren.

Im Moment funktioniert es und der abscheuliche Gebrauch von Globalen hat keinen Einfluss auf das Ergebnis, also lasse ich es.

Der Code, der die "verarbeitete" Variable verwendet, ist nur eine Methode, um herauszufinden, wie viele Punkte berechnet wurden, wenn sie im gleichzeitigen Modus ausgeführt wurden. Ich habe diesen Code hinzugefügt, nachdem ich die schreckliche Leistung der gleichzeitigen Version entdeckt habe. Ich bin also zuversichtlich, dass dies keine Ursache für die Verlangsamung ist.

Ich bin hier ratlos. Ich habe eine ganze Menge gleichzeitigen Codes geschrieben, und diese Aufgabe ist eine "peinlich parallelmsgstr "" "Problem, also gibt es keinen Grund, warum es nicht auf allen verfügbaren Kernen mit voller Neigung laufen sollte.

Sieht jemand anderes etwas, das dies verursachen könnte, oder hat er andere Erkenntnisse zu bieten?

Antworten auf die Frage(3)

Ihre Antwort auf die Frage