C# 数组中的分段聚合
我有大量的基本值类型。该数组实际上是一维的,但在逻辑上表示二维字段。从左向右读取时,值需要变成(当前单元格的原始值)+(在左侧单元格中计算的结果)。显然,除了每行的第一个元素,它只是原始值 我已经有了一个实现来实现这一点,但是在整个阵列上完全是迭代的,对于大型(1M+元素)阵列来说速度非常慢 给定以下示例数组C# 数组中的分段聚合,c#,arrays,parallel-processing,C#,Arrays,Parallel Processing,我有大量的基本值类型。该数组实际上是一维的,但在逻辑上表示二维字段。从左向右读取时,值需要变成(当前单元格的原始值)+(在左侧单元格中计算的结果)。显然,除了每行的第一个元素,它只是原始值 我已经有了一个实现来实现这一点,但是在整个阵列上完全是迭代的,对于大型(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 以此类推,直到出现
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,但是索引的必要性使得它很难实现
是否有另一种方法来组织数据以加快处理速度
如果使用创新技术实现高效的GPU处理是可行的,这将是非常可取的,因为这是当前必须从显卡中提取并推回到显卡的纹理数据。嗯,我对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 |
尽管上述说法并不完全正确。对于内存缓存,行之间仍然存在隐藏的依赖关系
可能会有缓存垃圾。为了理解这个问题,您可以阅读“缓存错误共享”,并了解如何克服它。正如@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();
}
静态int[,]值=新int[,]{
{0, 0, 1, 0, 0},
{2, 0, 0, 0, 3},
{0, 4, 1, 1, 0},
{0, 1, 0, 4 ,1}};
静态void Main(字符串[]参数)
{
int rows=values.GetLength(0);
int columns=values.GetLength(1);
平行。对于(0,行,(行)=>
{
对于(变量列=1;列<列;列++)
{
值[行,列]+=值[行,列-1];
}
});
对于(变量行=0;行<行;行++)
{
对于(变量列=0;列<列;列++)
{
Write(“{0}”,值[行,列];
}
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);
}
static void Main(字符串[]args)
{
var值=新整数[1024*1024];
随机r=新随机();
对于(int i=0;i<1024;i++)
{
对于(int j=0;j<1024;j++)
{
值[i*1024+j]=r.Next(25);
}
}
int行=1024;
int列=1024;
秒表sw=Stopwatch.StartNew();
对于(int i=0;i<100;i++)
{
平行。对于(0,行,(行)=>
{
对于(变量列=1;列<列;列++)
{
值[(行*列)+列]+=值[(行*列)+列-1];
}
});
}
控制台写入线(软件运行时间);
}
但速度不如GPU快。要使用并行GPU处理,您必须在中重写它,或者看看如何将此并行程序移植到cudafy:正确的编码,并了解一下.NET如何知道这些东西也有帮助:-) 在这种情况下适用的一些经验法则:
publicstaticvoidmain(字符串[]args)
{
整数宽度=4096;
内部高度=4096;
int[]ar=新int[宽度*高度];
随机rnd=新随机(213);
对于(int i=0;iJIT会注意到这不会超出范围,因为[0
{
整数和=0;
对于(变量i=宽度*a+1;i<宽度*(a+1);i++)
{
ar[i]=(和+=ar[i]);
}
});
WriteLine(“这花费了{0:0.0000}”,sw.appead.TotalSeconds);
}
<>如果你真的需要性能,你可以编译它到C++,使用p/javek。即使你不使用GPU,我想SSE /AVX指令可能已经给你一个显著的性能提升,你不会用.NET/C语言来得到。我想指出的是,英特尔C++编译器可以自动矢量化你的代码——甚至到Xeon。PHI’s.不需要很多努力,这可能会给您的性能带来很好的提升。
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();
}
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);
}
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();
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();