Implizites Threading vs. explizite Threading-Leistung [geschlossen]

Oct 09 2020

Ich parallelisiere eine Anwendung in C # und teste den Leistungsunterschied zwischen der Verwendung von implizitem Threading und explizitem Threading. Beide Techniken verwenden die System.ThreadingBibliothek, und das implizite Threading wird durch die Verwendung einer Parallel.ForSchleife charakterisiert, während das explizite Threading das Erstellen, Starten und Verbinden von Threads sowie das Berechnen von Blockgrößen, das Aufrufen der Worker-Funktion usw. umfasst.

Ich habe festgestellt, dass ich eine bessere Geschwindigkeit gegenüber der ursprünglichen sequentiellen Version des Programms erreiche, indem ich explizites Threading (nach 1,2 Versuchen etwa 1,2-mal schneller) auf acht Kernen verwende. Ich verstehe die zugrunde liegenden Unterschiede zwischen diesen beiden Techniken, bin mir jedoch nicht sicher, warum die explizite Version schneller zu sein scheint. Ich dachte, dass die implizite Version möglicherweise schneller sein würde, da Aufgaben automatisch geplant würden, im Gegensatz zur manuellen Erstellung von Aufgaben und Threads. Würde es einen Grund geben (abgesehen von vielleicht einem Fehler in meinen Ergebnissen), dass die explizite Version schneller wäre?

Als Referenz ist unten eine zusammengefasste Version des relevanten Codes zu sehen.

float[][] stft_implicit(Complex[] x, int wSamp)
{
    //...
    Parallel.For(0, size, new ParallelOptions { MaxDegreeOfParallelism = MainWindow.NUM_THREADS }, ii =>
    {
        Complex[] tempFFT = IterativeFFT.FFT(all_temps[ii], twiddles, wSamp);
        fft_results[ii] = tempFFT;
    });
    //...
}

float[][] stft_explicit(Complex[] x, int wSamp)
{
    //...
    length = (int)(2 * Math.Floor((double)N / (double)wSamp) - 1);
    chunk_size = (length + MainWindow.NUM_THREADS - 1) / MainWindow.NUM_THREADS;

    Thread[] threads = new Thread[MainWindow.NUM_THREADS];

    for (int i = 0; i < MainWindow.NUM_THREADS; i++)
    {
        threads[i] = new Thread(fft_worker);
        threads[i].Start(i);
    }

    for (int i = 0; i < MainWindow.NUM_THREADS; i++)
    {
        threads[i].Join();
    }
    //...
}

public void fft_worker(object thread_id)
{
    int ID = (int)thread_id;
    Complex[] temp = new Complex[wSamp];
    Complex[] tempFFT = new Complex[wSamp];
    int start = ID * chunk_size;
    int end = Math.Min(start + chunk_size, length);

    for (int ii = start; ii < end; ii++)
    {
        //...
        tempFFT = IterativeFFT.FFT(temp, twiddles, wSamp);
        //...
    }
}

Antworten

TheodorZoulias Oct 09 2020 at 09:22

Ich denke, dass der Vergleich für die nicht fair ist Parallel.For, da er für jedes Element des verarbeiteten Arrays ein anonymes Lambda aufrufen muss, während die explizite Threading-Implementierung einen einzelnen Methodenaufruf pro Thread (die fft_workerMethode) beinhaltet. Was dies noch wichtiger macht, ist, dass anonyme Lambdas vom C # -Compiler nicht eingebunden werden können.

Um die Fairness des Vergleichs wiederherzustellen, können Sie entweder:

  1. Nehmen Sie den Overhead eines anonymen Lambda-Aufrufs auch in die explizite Threading-Implementierung auf:
for (int ii = start; ii < end; ii++)
{
    ((Action)(() =>
    {
        //...
        tempFFT = IterativeFFT.FFT(temp, twiddles, wSamp);
        //...
    }))();
}
  1. Reduzieren Sie die Granularität (oder mit anderen Worten die Klobigkeit) der impliziten Threading-Implementierung, indem Sie die Parallel.Fordurch die Parallel.ForEach+ Partitioner-Kombination ersetzen :
Parallel.ForEach(Partitioner.Create(0, size), range =>
{
    for (int ii = range.Item1; ii < range.Item2; ii++)
    {
        Complex[] tempFFT = IterativeFFT.FFT(all_temps[ii], twiddles, wSamp);
        fft_results[ii] = tempFFT;
    }
});

Ich habe es nicht getestet, aber beide Vorschläge sollten die Lücke in der Leistung der beiden Parallelisierungstechniken schließen oder schließen.