隐式线程与显式线程性能[关闭]

Posted

技术标签:

【中文标题】隐式线程与显式线程性能[关闭]【英文标题】:Implicit threading vs explicit threading performance [closed] 【发布时间】:2020-10-09 00:43:22 【问题描述】:

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

我发现通过在 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);
        //...
    

【问题讨论】:

既然你知道你的方法之间的区别,我怀疑你的基准测试错误,但仍然有很小的机会 - 显示基准测试代码(特别是你预热线程池的部分)会消除它愚蠢和冒犯的担忧。 however, I am not sure why the explicit version seems to be faster. 很可能因为你忘了打电话给docs.microsoft.com/en-us/dotnet/api/…。如果该数字是 NUM_THREADS,那么来自线程池的线程将比直接创建它们“更慢”可用(因为线程池在启动新线程方面是保守的)。 I thought that perhaps the implicit version would be faster as tasks would be scheduled automatically, as opposed to manual task and thread creation. 自动并不一定意味着更快。它通常意味着“更好的整体资源分配”——这可能意味着更慢,但更好的重用(例如)。 当您提供“相关代码的摘要版本”时,您不允许我们选择运行您的代码并自行测试。您应该提供minimal reproducible example,包括源数据和基准测试结果摘要。然后,我们可以验证我们得到了相同的结果——可能是环境问题影响了你的结果,但不是我们的——然后我们可以重构代码以确保你实际测量的是你认为你正在测量的东西,或者这样我们可以展示表现良好的替代方案。 感谢大家的回复。由于总体项目的范围很大,最小的可重复答案是不可行的,我只是从理论和教育的角度对隐式线程和显式线程的行为进行了回答,而不是特定于技术方面的示例参考代码。我会接受@Theodor Zoulias 的回答,并且很高兴由于缺乏明确性而关闭了这个问题。 【参考方案1】:

我认为Parallel.For 的比较是不公平的,因为它必须为已处理数组的每个元素调用一个匿名 lambda,而显式线程实现涉及每个线程的单个方法调用(fft_worker方法)。更重要的是 C# 编译器的匿名 lambdas cannot be inlined。

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

    在显式线程实现中也包括匿名 lambda 调用的开销:
for (int ii = start; ii < end; ii++)

    ((Action)(() =>
    
        //...
        tempFFT = IterativeFFT.FFT(temp, twiddles, wSamp);
        //...
    ))();

    通过将Parallel.For 替换为Parallel.ForEach+Partitioner 组合来降低隐式线程实现的粒度(或者换句话说,增加块度):
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;
    
);

我还没有测试过,但是这两个建议应该可以缩小或消除两种并行化技术的性能差距。

【讨论】:

如果 OP 提供了一个 minimal reproducible example 那就太棒了,否则这都是猜想。 @Enigmativity 希望 OP 会尝试这些建议并报告结果。否则,是的,这个答案在一定程度上仍然是一个猜想。

以上是关于隐式线程与显式线程性能[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程——ReentrantLock源码阅读

隐式与显式接口实现[重复]

js 的隐式转换与显式转换

Objective-C self->_ivar 访问与显式与隐式 self->

为啥我不能将接口与显式运算符一起使用? [复制]

C语言 显式 隐式是啥意思