我有大量的原始值类型。该阵列实际上是一维的,但在逻辑上代表一个二维场。当您从左向右阅读时,值需要变为(当前单元格的原始值)+(在左侧单元格中计算的结果)。显然除了每行的第一个元素外,它只是原始值。
我已经有了一个实现它的实现,但是在整个数组上完全迭代,对于大型(1M +元素)数组来说非常慢。
给出以下示例数组
0 0 1 0 0
2 0 0 0 3
0 4 1 1 0
0 1 0 4 1
变为
0 0 1 1 1
2 2 2 2 5
0 4 5 6 6
0 1 1 5 6
依此类推,直至有问题的尺寸(1024x1024)
需要更新阵列(理想情况下),但必要时可以使用另一个阵列。内存占用在这里不是问题,但性能至关重要,因为这些阵列有数百万个元素,每秒必须处理数百次。
单个单元格计算似乎不可并行化,因为它们依赖于从左侧开始的值,因此GPU加速似乎是不可能的。我已经调查了PLINQ,但索引的必要条件使得它很难实现。
是否有另一种方法来构建数据以加快处理速度?
如果使用创新的teqnique进行高效的GPU处理是可行的,那么这将是非常可取的,因为这是当前必须从视频卡中拉出并推回到视频卡的纹理数据。
答案 0 :(得分:3)
正确的编码和对.NET如何知道东西的一些见解也有帮助: - )
在这种情况下适用的一些经验法则:
使用这些规则,您可以按如下方式制作一个小测试用例。请注意,我已将赌注提高到4Kx4K,因为1K的速度非常快,你无法测量它: - )
public static void Main(string[] args)
{
int width = 4096;
int height = 4096;
int[] ar = new int[width * height];
Random rnd = new Random(213);
for (int i = 0; i < ar.Length; ++i)
{
ar[i] = rnd.Next(0, 120);
}
// (5)...
for (int j = 0; j < 10; ++j)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = 0;
for (int i = 0; i < ar.Length; ++i) // (3) sequential access
{
if ((i % width) == 0)
{
sum = 0;
}
// (1) --> the JIT will notice this won't go out of bounds because [0<=i<ar.Length]
// (5) --> '+=' is an expression generating a 'dup'; this creates less IL.
ar[i] = (sum += ar[i]);
}
Console.WriteLine("This took {0:0.0000}s", sw.Elapsed.TotalSeconds);
}
Console.ReadLine();
}
其中一次迭代大约需要0.0174秒,因为这是你描述的最坏情况的16倍,我想你的性能问题已经解决了。
如果你真的想要平行它以使它更快,我想这是可能的,即使你将松开JIT中的一些优化(具体来说:(1))。但是,如果您拥有像大多数人一样的多核系统,那么这些好处可能会超重:
for (int j = 0; j < 10; ++j)
{
Stopwatch sw = Stopwatch.StartNew();
Parallel.For(0, height, (a) =>
{
int sum = 0;
for (var i = width * a + 1; i < width * (a + 1); i++)
{
ar[i] = (sum += ar[i]);
}
});
Console.WriteLine("This took {0:0.0000}s", sw.Elapsed.TotalSeconds);
}
如果你确实需要性能,可以将其编译为C ++并使用P / Invoke。即使您不使用GPU,我认为SSE / AVX指令可能已经为您提供了.NET / C#无法获得的显着性能提升。另外我想指出的是,英特尔C ++编译器可以自动对代码进行矢量化 - 甚至是Xeon PHI。如果不付出太多努力,这可能会给你带来很好的性能提升。
答案 1 :(得分:2)
嗯,我对GPU知之甚少,但我认为没有理由不能并行化,因为依赖只是从左到右。
行之间没有依赖关系。
0 0 1 0 0 - process on core1 |
2 0 0 0 3 - process on core1 |
-------------------------------
0 4 1 1 0 - process on core2 |
0 1 0 4 1 - process on core2 |
虽然上述说法并不完全正确。在内存缓存方面,行之间仍存在隐藏的依赖关系。
可能存在缓存垃圾。您可以阅读“缓存虚假共享”,以了解问题,并了解如何克服这一问题。
答案 2 :(得分:0)
正如@Chris Eelmaa告诉你的那样,可以按行执行并行执行。使用Parallel.For可以像这样重写:
static int[,] values = new int[,]{
{0, 0, 1, 0, 0},
{2, 0, 0, 0, 3},
{0, 4, 1, 1, 0},
{0, 1, 0, 4 ,1}};
static void Main(string[] args)
{
int rows=values.GetLength(0);
int columns=values.GetLength(1);
Parallel.For(0, rows, (row) =>
{
for (var column = 1; column < columns; column++)
{
values[row, column] += values[row, column - 1];
}
});
for (var row = 0; row < rows; row++)
{
for (var column = 0; column < columns; column++)
{
Console.Write("{0} ", values[row, column]);
}
Console.WriteLine();
}
所以,正如你的问题所述,你有一个一维数组,代码会更快一点:
static void Main(string[] args)
{
var values = new int[1024 * 1024];
Random r = new Random();
for (int i = 0; i < 1024; i++)
{
for (int j = 0; j < 1024; j++)
{
values[i * 1024 + j] = r.Next(25);
}
}
int rows = 1024;
int columns = 1024;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Parallel.For(0, rows, (row) =>
{
for (var column = 1; column < columns; column++)
{
values[(row * columns) + column] += values[(row * columns) + column - 1];
}
});
}
Console.WriteLine(sw.Elapsed);
}
但不如GPU那么快。要使用并行GPU处理,您必须在C++ AMP中重写它,或者查看如何将此并行端口移植到cudafy:http://w8isms.blogspot.com.es/2012/09/cudafy-me-part-3-of-4.html
答案 3 :(得分:0)
您也可以将数组存储为锯齿状数组,内存布局也是一样的。所以,而不是,
int[] texture;
你有,
int[][] texture;
将行操作隔离为
private static Task ProcessRow(int[] row)
{
var v = row[0];
for (var i = 1; i < row.Length; i++)
{
v = row[i] += v;
}
return Task.FromResult(true);
}
然后你可以写一个函数,
Task.WhenAll(texture.Select(ProcessRow)).Wait();
如果你想保留一维数组,可以使用类似的方法,只需更改ProcessRow
。
private static Task ProcessRow(int[] texture, int start, int limit)
{
var v = texture[start];
for (var i = start + 1; i < limit; i++)
{
v = texture[i] += v;
}
return Task.FromResult(true);
}
然后一次,
var rowSize = 1024;
var rows =
Enumerable.Range(0, texture.Length / rowSize)
.Select(i => Tuple.Create(i * rowSize, (i * rowSize) + rowSize))
.ToArray();
然后在每个周期。
Task.WhenAll(rows.Select(t => ProcessRow(texture, t.Item1, t.Item2)).Wait();
无论哪种方式,每一行都是并行处理的。