我已经在这个问题上头痛了几个小时,但最终线程争用一直吞噬着并行循环带来的性能提升。
我正在尝试计算一个8位灰度千兆像素图像的直方图。读过《CUDA示例》一书的人可能知道这是什么(第9章)。
这种方法非常简单(导致非常紧密的循环)。它基本上只是:
显然是由于原子增量的性能影响。
无论我尝试什么(如范围分区器[http://msdn.microsoft.com/en-us/library/ff963547.aspx], 并发集合[http://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx]等),最终都归结为一个事实,即我将十亿个元素缩减为256个元素,并且在尝试访问我的直方图数组时总是陷入竞争条件。
我最后的尝试是使用像...这样的范围分区器。
我正在尝试计算一个8位灰度千兆像素图像的直方图。读过《CUDA示例》一书的人可能知道这是什么(第9章)。
这种方法非常简单(导致非常紧密的循环)。它基本上只是:
private static void CalculateHistogram(uint[] histo, byte[] buffer)
{
foreach (byte thisByte in buffer)
{
// increment the histogram at the position
// of the current array value
histo[thisByte]++;
}
}
其中缓冲区是一个包含1024^3个元素的数组。
在相对较新的Sandy Bridge-EX CPU上,使用单核心构建10亿个元素的直方图只需要1秒钟。
无论如何,我尝试通过将循环分布在所有核心之间来加速计算,结果得到了一个50倍慢的解决方案。
private static void CalculateHistrogramParallel(byte[] buffer, ref int[] histo)
{
// create a variable holding a reference to the histogram array
int[] histocopy = histo;
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
// loop through the buffer array in parallel
Parallel.ForEach(
buffer,
parallelOptions,
thisByte => Interlocked.Increment(ref histocopy[thisByte]));
}
显然是由于原子增量的性能影响。
无论我尝试什么(如范围分区器[http://msdn.microsoft.com/en-us/library/ff963547.aspx], 并发集合[http://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx]等),最终都归结为一个事实,即我将十亿个元素缩减为256个元素,并且在尝试访问我的直方图数组时总是陷入竞争条件。
我最后的尝试是使用像...这样的范围分区器。
var rangePartitioner = Partitioner.Create(0, buffer.Length);
Parallel.ForEach(rangePartitioner, parallelOptions, range =>
{
var temp = new int[256];
for (long i = range.Item1; i < range.Item2; i++)
{
temp[buffer[i]]++;
}
});
计算子直方图。但最终,我仍然有一个问题,那就是我必须合并所有这些子直方图,于是线程争用又出现了。
我不相信没有办法通过并行化来加速,即使这是一个非常紧密的循环。如果在GPU上可能,那么在CPU上也可能 - 在某种程度上。
除了放弃,还有什么可以尝试的?
我已经在stackoverflow和网络上搜索了很多,但这似乎是并行性的边缘案例。
histo
,然后在最后把它们全部加起来吗? - Andrew Morton