【发布时间】:2014-07-18 09:17:01
【问题描述】:
我在这个问题上已经花了好几个小时了,但我总是以线程争用而结束并行化循环的任何性能改进。
我正在尝试计算 8 位灰度千兆像素图像的直方图。读过《CUDA by Example》一书的人可能知道这是从哪里来的(第 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]++;
}
}
其中 buffer 是一个包含 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] 等等),归根结底是我将 10 亿个元素减少到 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并在最后将它们全部加起来? -
我用霍夫变换做过类似的事情。我使用了单独的累加器并在最后将它们合并,给了我很大的提升。最后合并 4/8 个小数组不应该是一个瓶颈。我从来没有亲自使用过
Parallel,所以对此不太了解,但如果你没有从中得到提升,它似乎在做一些奇怪的事情。 -
@lightxx 考虑每个并行循环的启动成本,创建一个任务,分配一些 L1 / L2 缓存,分配它认为需要的内容,引用内存等。这可能会变得非常繁重和如此紧密的循环会导致减速。您可以考虑使用动态分区msdn.microsoft.com/en-us/library/dd997416.aspx,通常albahari.com/threading/part5.aspx#_PLINQ 会加快速度。
-
首先考虑加速坏代码。 histo[thisByte]++;很慢 - 在此处使用指针和不安全的代码。应该会显着提升。
-
实际上我不会将避免不安全代码称为“坏代码”。无论如何,即使我们设法加快单核版本,这不是这里的问题。
标签: c# multithreading performance parallel-processing parallel.foreach