问题描述
我正在用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。
要恢复比较的公平性,您可以:
- 在显式线程实现中也包括匿名lambda调用的开销:
Parallel.For
- 通过将
fft_worker
替换为Parallel.ForEach
+Partitioner
组合,减少隐式线程实现的粒度(换句话说,增加块状性):
for (int ii = start; ii < end; ii++)
{
((Action)(() =>
{
//...
tempFFT = IterativeFFT.FFT(temp,twiddles,wSamp);
//...
}))();
}
我还没有测试过,但是这两个建议都应该弥补或消除两种并行化技术在性能上的差距。