C# 隐式线程与显式线程性能

C# 隐式线程与显式线程性能,c#,multithreading,performance,parallel-processing,task-parallel-library,C#,Multithreading,Performance,Parallel Processing,Task Parallel Library,我正在用C#并行化一个应用程序,并测试使用隐式线程和显式线程的性能差异。这两种技术都利用了系统线程库,隐式线程的特点是使用并行.For循环,而显式线程涉及创建、启动和连接线程,同时还计算块大小、调用worker函数等 我发现,通过在8个内核上使用显式线程(50次试验后大约快1.2倍),我比程序的原始顺序版本实现了更好的加速。我理解这两种技术之间的潜在差异,但是,我不确定为什么显式版本看起来更快。我认为隐式版本可能会更快,因为任务将自动调度,而不是手动创建任务和线程。是否有一个原因(也许除了我的结

我正在用C#并行化一个应用程序,并测试使用隐式线程和显式线程的性能差异。这两种技术都利用了
系统线程
库,隐式线程的特点是使用
并行.For
循环,而显式线程涉及创建、启动和连接线程,同时还计算块大小、调用worker函数等

我发现,通过在8个内核上使用显式线程(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, twiddles, wSamp);
        //...
    }
}
float[]stft\u隐式(复数[]x,整数wSamp)
{
//...
Parallel.For(0,size,新的ParallelOptions{MaxDegreeOfParallelism=MainWindow.NUM_THREADS},ii=>
{
复数[]tempft=IterativeFFT.FFT(所有_temp[ii],旋转,wSamp);
fft_结果[ii]=tempft;
});
//...
}
浮点[][]stft_显式(复数[]x,整数wSamp)
{
//...
长度=(整数)(2*数学楼层((双)N/(双)wSamp)-1);
chunk\u size=(length+MainWindow.NUM\u THREADS-1)/MainWindow.NUM\u THREADS;
线程[]线程=新线程[MainWindow.NUM_threads];
对于(inti=0;i
我认为这种比较对于并行的
是不公平的。对于
,因为它必须为已处理数组的每个元素调用匿名lambda,而显式线程实现涉及每个线程调用一个方法(fft\u worker方法)。更重要的是C#编译器提供了匿名lambda

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

  • 在显式线程实现中也包括匿名lambda调用的开销:

  • 我还没有对它进行测试,但这两个建议都应该缩小或消除这两种并行化技术在性能上的差距。

    既然您知道这两种方法之间的差异,我怀疑您的基准测试是错误的,但显示基准测试代码的可能性仍然很小(特别是在您预热线程池的部分)将消除这种愚蠢和冒犯性的顾虑。
    但是,我不确定为什么显式版本看起来更快。
    很可能是因为您忘记调用了。如果该数字是
    NUM\u THREADS
    ,那么线程池中的线程将“更慢”可用与直接创建它们相比(因为线程池在旋转新线程方面是保守的)。
    我认为隐式版本可能会更快,因为任务将自动调度,而不是手动任务和线程创建。
    自动并不一定意味着更快。它通常意味着“更好的总体资源分配”-这可能意味着更慢,但更好的重用(例如)"您不允许我们选择自己运行代码和测试。您应该提供一份测试报告(包括源数据)和基准测试结果摘要。然后,我们可以验证我们是否获得了相同的结果—可能有环境问题影响您的结果,但不影响我们的结果—然后我们可以将代码重构为确保您正在实际测量您认为您正在测量的内容,或者我们可以展示性能良好的替代方案。感谢所有人的回答。由于总体项目的范围很大,一个最小的可重复答案是不可行的,我只是想从理论和教育的角度在beh上找到一个答案通常隐式线程与显式线程的比较,不特定于示例参考代码的技术方面。我将接受@Theodor Zoulias的答案,并且很高兴由于缺乏清晰性而结束了这个问题。OP提供了一个新的解决方案,这将是非常棒的,否则这都是猜测。@Enigmativity希望OP会尝试这些建议和报告的结果。否则,是的,这个答案将仍然是一个猜测的程度。
    for (int ii = start; ii < end; ii++)
    {
        ((Action)(() =>
        {
            //...
            tempFFT = IterativeFFT.FFT(temp, twiddles, wSamp);
            //...
        }))();
    }
    
    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;
        }
    });