隐式线程与显式线程性能

问题描述

我正在用C#并行处理一个应用程序,并且正在测试使用隐式线程与显式线程之间的性能差异。两种技术都利用System.Threading库,隐式线程的特征是使用Parallel.For循环,而显式线程涉及创建,启动和连接线程,同时还计算块大小,调用worker函数等。

我发现,通过在八个内核上利用显式线程(在50次试用后,速度提高了约1.2倍),我可以更快地提高程序的原始顺序版本。我了解这两种技术之间的根本差异,但是,我不确定为什么显式版本似乎更快。我认为隐式版本可能会更快,因为任务将自动调度,这与手动任务和线程创建相反。 (除了我的结果可能有错误之外)是否有理由使用显式版本会更快?

作为参考,下面是相关代码摘要版本。

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,wSamp);
        //...
    }
}

解决方法

我认为比较对于Promise { <pending> } 是不公平的,因为它必须为已处理数组的每个元素调用匿名lambda,而显式线程实现涉及每个线程一个方法调用({ {1}}方法)。使得这一点更加重要的是C#编译器的匿名Lambda cannot be inlined

要恢复比较的公平性,您可以:

  1. 在显式线程实现中也包括匿名lambda调用的开销:
Parallel.For
  1. 通过将fft_worker替换为Parallel.ForEach + Partitioner组合,减少隐式线程实现的粒度(换句话说,增加块状性):
for (int ii = start; ii < end; ii++)
{
    ((Action)(() =>
    {
        //...
        tempFFT = IterativeFFT.FFT(temp,twiddles,wSamp);
        //...
    }))();
}

我还没有测试过,但是这两个建议都应该弥补或消除两种并行化技术在性能上的差距。