如何尽可能快地初始化基本类型的多维数组?
我坚持使用多维数组。我的问题是表现。以下例程初始化大约100x100阵列。 500蜱。删除int.MaxValue初始化导致大约。 180个滴答仅用于循环。大约100个刻度来创建数组而不循环并且没有初始化为int.MaxValue。
我愿意接受有关如何优化数组的非默认初始化的建议。我的一个想法是在可用时使用较小的原始类型。例如,使用byte而不是int,可以节省100个滴答。我会对此感到满意,但我希望我不必更改原始数据类型。
public int[,] CreateArray(Size size) {
int[,] array = new int[size.Width, size.Height];
for (int x = 0; x < size.Width; x++) {
for (int y = 0; y < size.Height; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
使用以下内容减少到450个滴答:
public int[,] CreateArray1(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
for (int x = 0; x < iX; x++) {
for (int y = 0; y < iY; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
在一次性初始化2800个滴答之后下降到大约165个滴答。 (请参阅下面的答案。)如果我可以让stackalloc
使用多维数组,我应该能够获得相同的性能而无需初始化private static
数组。
private static bool _arrayInitialized5;
private static int[,] _array5;
public static int[,] CreateArray5(Size size) {
if (!_arrayInitialized5) {
int iX = size.Width;
int iY = size.Height;
_array5 = new int[iX, iY];
for (int x = 0; x < iX; x++) {
for (int y = 0; y < iY; y++) {
_array5[x, y] = int.MaxValue;
}
}
_arrayInitialized5 = true;
}
return (int[,])_array5.Clone();
}
在不使用上述“克隆技术”的情况下降至约165个滴答。 (请参阅下面的答案。)如果能算出CreateArray9
的回报,我相信我可以降低价格。
public unsafe static int[,] CreateArray8(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
fixed (int* pfixed = array) {
int count = array.Length;
for (int* p = pfixed; count-- > 0; p++)
*p = int.MaxValue;
}
return array;
}
我提供有关此问题的所有代码和说明作为答案。希望它能在将来节省一些人的时间。
在大对象堆(LOH)上分配的数组不是本讨论的一部分。所听到的性能改进仅适用于堆上分配的数组。
我使用stackalloc
来消除初始化数组到默认值的想法没有用,因为必须从方法中复制分配的堆栈内存。意思是,我必须创建另一个数组来保存结果。该数组将被初始化,从而无法使用stackalloc
。
如果CLR位于完全受信任的程序集中,CLR将只执行unsafe
代码。
需要变量来确定数组是否已初始化并存储已初始化的数组。初始化后,性能与不安全/固定方法相同。请参阅Dan Tao对可能解决方案的回答。
我吮吸百分比,但300%是我想的(500到165刻度)。
对于这个应用程序,我决定使用“克隆”方法。以下是具有性能样本的应用程序中使用的“精简”通用实现。
初始化:
Grid<int>
;通用克隆类初始化:4348,4336,4339,4654 Grid<bool>
;通用克隆类初始化:2692,2684,3916,2680 Grid<Color>
;通用克隆类初始化:3747,4630,2702,2708 使用:
Grid<int>
;通用克隆类:185,159,152,290 Grid<bool>
;通用克隆类:39,36,44,46 Grid<Color>
;通用克隆类:2229,2431,2460,2466
public class Grid<T> {
private T[,] _array;
private T _value;
private bool _initialized;
private int _x;
private int _y;
public Grid(Size size, T value, bool initialize) {
_x = size.Width;
_y = size.Height;
_value = value;
if (initialize) {
InitializeArray();
}
}
private void InitializeArray() {
int iX = _x;
int iY = _y;
_array = new T[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
_array[x, y] = _value;
}
}
_initialized = true;
}
public T[,] CreateArray() {
if (!_initialized) {
InitializeArray();
}
return (T[,])_array.Clone();
}
}
答案 0 :(得分:4)
关于Clone
方法的说明:我怀疑你能否在性能方面击败它。但是,考虑到在第一次初始化之后,它可能是一个突破性的变化,它忽略了Size
参数,并且在每次调用时只返回相同大小的数组。根据您的方案中实际上是否重要,您可以:
Dictionary<Size, int[,]>
(我相信 Size
将作为密钥正常运行 - 尚未经过测试)每次唯一{{}时预初始化数组请求1}}。这个开销我不确定。Size
想法。如果您最终不得不使用上述3,那么这里有一些边缘荒谬的建议:
<强> 1。在本地缓存您的Clone
和Width
属性,而不是在每次迭代时从Height
结构中访问它们。
Size
要在我的机器上创建一个1000x1000阵列,这导致平均执行时间约为120000个刻度,而大约为140000个刻度。
<强> 2。如果有的话,可以利用多个核心并并行初始化阵列。
static int[,] CreateArray(Size size) {
int w = size.Width;
int h = size.Height;
int[,] array = new int[w, h];
for (int x = 0; x < w; x++) {
for (int y = 0; y < h; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
这可能不是您场景中非常现实的建议,因为您似乎只创建了100x100阵列,在这种情况下,并行化的开销超过了性能提升。但是,为了创建一个1000x1000的数组,我发现这种方法平均将执行时间减少到大约70k(与我建议的第一次优化得到的~120k tick)相比。
此外,如果您以这种方式创建多个阵列,我强烈建议并行化 (即,如果您需要创建一千个阵列,请从两个线程),假设您有多个处理器为您完成工作。没有多处理器,别忘了;添加线程只会损害您的性能。
第3。使用static int[,] CreateArray(Size size) {
int w = size.Width;
int h = size.Height;
int[,] array = new int[w, h];
Action<int[,], int, int> fillFirstHalf = FillArray;
Action<int[,], int, int> fillSecondHalf = FillArray;
var firstResult = fillFirstHalf.BeginInvoke(array, 0, h / 2, null, null);
var secondResult = fillSecondHalf.BeginInvoke(array, h / 2, h, null, null);
fillFirstHalf.EndInvoke(firstResult);
fillSecondHalf.EndInvoke(secondResult);
return array;
}
static void FillArray(int[,] array, int ystart, int yend) {
int w = array.GetLength(0);
for (int x = 0; x < w; ++x) {
for (int y = ystart; y < yend; ++y) {
array[x, y] = int.MaxValue;
}
}
}
指针获得增强的性能。
现在这里一个有趣的发现:似乎.NET中的二维数组以可预测的方式分配*:基本上作为一维内存块,其中每个“行”从起始点偏移相当于所有先前行的长度的量。换句话说,可以使用指针访问10x2数组,就像20x1数组一样;可以访问10x10阵列,如100x1阵列等。
我不知道这是否是记录在案的行为。它可能是您不希望依赖的未指定的实现细节。无论哪种方式,都值得研究。
* 大多数其他.NET开发人员可能已经知道了这一点,我只是说明显而已,在这种情况下,我取消了我对这个“有趣”的评论。
在任何情况下,了解此信息都可以让您利用unsafe
上下文中的fixed
关键字获得显着的性能提升:
unsafe
为了初始化一个有意义的大小的数组,我甚至建议将上述方法(并行化)与这个方法结合起来 - 所以,在建议#2中保持相同的static int[,] CreateArray(Size size) {
int w = size.Width;
int h = size.Height;
int[,] array = new int[w, h];
unsafe {
fixed (int* ptr = array) {
for (int i = 0; i < w * h; ++i)
ptr[i] = int.MaxValue;
}
}
return array;
}
,然后重写CreateArray
为:
FillArray
在我发布这篇文章之前,你似乎已经想出了这个最后一部分,但我认为我主要是为了将static void FillArray(int[,] array, int ystart, int yend) {
int w = array.GetLength(0);
unsafe {
fixed (int* p = array) {
for (int i = w * ystart; i < w * yend; ++i)
p[i] = int.MaxValue;
}
}
}
与并行化相结合这一点而包含它。
关于unsafe
的说明:我想你可能正在用彩虹追逐彩虹尽头的妖精。根据{{3}}:
足够大小的内存块 包含
stackalloc
类型的expr
个元素 在堆栈上分配,而不是 堆;块的地址是 存储在指针type
中。这个记忆是 不受垃圾收集和影响 因此不必固定 (通过ptr
)。 的生命 内存块仅限于 它的生命周期 定义。(强调我的)
这让我相信你不能 返回一个对象,其数据存储在fixed
从函数分配的内存中,因为该内存只是分配给函数的生命周期。
答案 1 :(得分:3)
在C#unmanaged(unsafe)模式下创建数组,如shown here[code project]并手动初始化。
在C#托管模式下,在循环开始填充之前,数组首先将所有元素初始化为默认值。您可以在非托管模式下减少加倍。这样可以节省很多时间。
答案 2 :(得分:2)
添加static
和unsafe
可以减少Ticks的数量。以下是一些样本。
我尝试使用stackalloc
。我的想法是分配数组,因为它是unsafe
代码而不会被初始化。然后我会压缩数组,在我去的时候初始化int.MaxValue
,然后Clone
数组返回结果。但是,我对多维宣言感到困惑。
然后我记得在另一个项目中对数组使用Clone
。每个Array.Clone
都节省了几秒钟。基于这个想法,我创建了以下版本的CreateArray
例程,获得了很好的结果。
现在,我需要做的就是让stackalloc
使用多维数组。
CreateArray5;克隆: 157,172
private static bool _arrayInitialized5;
private static int[,] _array5;
public static int[,] CreateArray5(Size size) {
if (!_arrayInitialized5) {
int iX = size.Width;
int iY = size.Height;
_array5 = new int[iX, iY];
for (int x = 0; x < iX; x++) {
for (int y = 0; y < iY; y++) {
_array5[x, y] = int.MaxValue;
}
}
_arrayInitialized5 = true;
}
return (int[,])_array5.Clone();
}
int[,] actual;
int iHi = 10000 * 10 * 2;
//'absolute minimum times array will be created (200,000)
//'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)
Stopwatch o;
//'pre-initialize
o = Stopwatch.StartNew();
actual = CreateArray5(new Size(100, 100));
o.Stop();
Trace.WriteLine(o.ElapsedTicks, "CreateArray5; pre-initialize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray5(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray5; static unsafe clone");
答案 3 :(得分:1)
在这种情况下,这是未经测试的,但在类似的情况下也有效。 有时,交换数组遍历的顺序会因内存局部性而加快速度。
换句话说,而不是for(x) ... for(y)
代替for(y) ... for(x)
。
答案 4 :(得分:0)
您可以使用并行库来并行使用每一行来初始化它,它会更快。
但是,我认为这是有限制的,只有64个操作可以排队,但在这种情况下你可以在Parallel.For中排队0到63,64到127等..
public int[,] CreateArray(Size size) {
int[,] array = new int[size.Width, size.Height];
System.Threading.Paralle.For (0,size.Width,
x=>{
for (int y = 0; y < size.Height; y++) {
array[x, y] = int.MaxValue;
}
}
);
return array;
}
这包含在.NEt 4中,但是对于.NET 3.51,您可以从codeplex获得相同的库。
答案 5 :(得分:0)
我能够将得分降至约165.请参阅下面的CreateArray8
。
我从jdk提供的http://www.codeproject.com/KB/dotnet/arrays.aspx的CodeProject文章的“范围检查消除”部分得到了一个想法。 (@jdk,非常感谢。)我们的想法是通过使用指针并在一个循环中初始化每个元素来消除范围检查。我能够将得分降低到大约165.与克隆一样好,没有预先初始化和支持静态变量的延迟。 (见我的另一个答案。)
我敢打赌,如果我能算出CreateArray9
的回报,我可以减少一半。
[TestClass]
public class CreateArrayTest {
public static unsafe int[,] CreateArray3(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
for (int x = 0; x < iX; x++) {
for (int y = 0; y < iY; y++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
public unsafe static int[,] CreateArray7(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
array[x, y] = int.MaxValue;
}
}
return array;
}
public unsafe static int[,] CreateArray8(Size size) {
int iX = size.Width;
int iY = size.Height;
int[,] array = new int[iX, iY];
fixed (int* pfixed = array) {
int count = array.Length;
for (int* p = pfixed; count-- > 0; p++)
*p = int.MaxValue;
}
return array;
}
public unsafe static int[,] CreateArray9(Size size) {
int iX = size.Width;
int iY = size.Height;
void* array = stackalloc int[iX * iY];
int count = iX * iY;
for (int* p = (int*)array; count-- > 0; p++)
*p = int.MaxValue;
//return (int[,])array; //how to return?
return new int[1, 1];
}
[TestMethod()]
public void CreateArray_Test() {
int[,] actual;
int iHi = 10000 * 10 * 2;
//'absolute minimum times array will be created (200,000)
//'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)
Stopwatch o;
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray3(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray3; static unsafe");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray7(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray7; static unsafe for(y,x)");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = CreateArray8(new Size(100, 100)); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, "CreateArray8; static unsafe pointer single_for");
}
}
答案 6 :(得分:0)
使用“克隆”方法的类和通用。
Grid<int>
;通用克隆类初始化:5344,5334,5693,5272 Grid<int>
;通用克隆类:187,204,199,288 Grid<bool>
;通用克隆类初始化:3585,3537,3552,3569 Grid<bool>
;通用克隆类:37,44,36,43 Grid<Color>
;通用克隆类初始化:4139,3536,3503,3533 Grid<Color>
;通用克隆类:2737,3177,2414,2171 [TestClass]
public class CreateArrayTest {
public class MDArray {
private bool _initialized;
private int[,] _array;
private int _x;
private int _y;
private int _value;
public MDArray(Size size, int value, bool initialize) {
_x = size.Width;
_y = size.Height;
_value = value;
if (initialize) {
InitializeArray();
}
}
private void InitializeArray() {
int iX = _x;
int iY = _y;
_array = new int[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
_array[x, y] = _value;
}
}
_initialized = true;
}
public int[,] CreateArray() {
if (!_initialized) {
InitializeArray();
}
return (int[,])_array.Clone();
}
}
public class Grid<T> {
private T[,] _array;
private T _value;
private bool _initialized;
private int _x;
private int _y;
public Grid(Size size, T value, bool initialize) {
_x = size.Width;
_y = size.Height;
_value = value;
if (initialize) {
InitializeArray();
}
}
private void InitializeArray() {
int iX = _x;
int iY = _y;
_array = new T[iX, iY];
for (int y = 0; y < iY; y++) {
for (int x = 0; x < iX; x++) {
_array[x, y] = _value;
}
}
_initialized = true;
}
public T[,] CreateArray() {
if (!_initialized) {
InitializeArray();
}
return (T[,])_array.Clone();
}
}
[TestMethod()]
public void CreateArray_Test() {
int[,] actual;
int iHi = 10000 * 10 * 2; //'absolute minimum times array will be created (200,000)
// 'could be as high as 10000 * 10 * 20? * 50? (100,000,000?)
Stopwatch o;
o = Stopwatch.StartNew();
MDArray oMDArray = new MDArray(new Size(100, 100), int.MaxValue, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " MDArray; clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = oMDArray.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " MDArray; clone class");
o = Stopwatch.StartNew();
Grid<int> oIntMap = new Grid<int>(new Size(100, 100), int.MaxValue, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " Grid<int>; generic clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { actual = oIntMap.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " Grid<int>; generic clone class");
bool[,] fActual;
o = Stopwatch.StartNew();
Grid<bool> oBolMap = new Grid<bool>(new Size(100, 100), true, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " Grid<bool>; generic clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { fActual = oBolMap.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " Grid<bool>; generic clone class");
Color[,] oActual;
o = Stopwatch.StartNew();
Grid<Color> oColMap = new Grid<Color>(new Size(100, 100), Color.AliceBlue, true);
o.Stop();
Trace.WriteLine(o.ElapsedTicks, " Grid<Color>; generic clone class initalize");
o = Stopwatch.StartNew();
for (int i = 0; i < iHi; i++) { oActual = oColMap.CreateArray(); }
o.Stop();
Trace.WriteLine(o.ElapsedTicks / iHi, " Grid<Color>; generic clone class");
}
}