【问题标题】:CLR multi-dimensioned array traversal performanceCLR 多维数组遍历性能
【发布时间】:2014-03-08 07:39:55
【问题描述】:

在我编写的软件的很多地方,都有shortfloat 的三维数组,通常有几百万个元素。数据最好从概念上理解为一个三维数组,因为它描述了空间中规则位置的值。

我在这里看到其他地方提到 .NET CLR 在遍历这些数组时并不是非常“高效”,例如,在计算新值和填充第二个大小和维度相同的数组时。如果这是真的,为什么会这样?

出于可读性的原因,我还没有确定使用锯齿状数组的想法,但如果这真的是答案,那么我愿意,但是:

为了解决这个问题,有人建议我将数据格式化为单维数组。例如,如果我的数组具有大小为mno 的维度,那么我将创建一个float[m*n*o] 而不是float[m,n,o],并编写我自己的索引器以获取正确的数组位置在遍历期间。

具体用例是在并行化遍历中,如:

Parallel.For(0,m)((x) => for(y=0,y<n,y++) { for(z=0,z<o,z++) doSomething(array(x,y,z)); });

在单索引的情况下,会发生Parallel.ForEach(myArray, (position) =&gt; doSomething(array(Position))) 之类的事情,而不是嵌套的for 循环。

所以,问题是,真的,这会比依赖内置的 CLR 数组索引更快吗?

编辑:根据一些计时测试,我在下面提供了自己的答案。代码已包含在内。

【问题讨论】:

  • 您必须对其进行分析才能确定,但​​我敢打赌,您的更多时间花在复制数据然后是索引查找上。
  • 您说复制 3D 数组很慢的来源是什么?
  • doSomething() 最好是一个实质性的方法,否则内存总线将挫败使用线程加速代码的尝试。在这种情况下,数组索引不再相关。你有几匹马,如果你想看看哪一匹跑得最快,那就跑吧。
  • @Servy:他几乎大喊这是道听途说;他想要确认。
  • 请记住 float[m,n,o] 就像 float[mno] 有大于 85k 的风险,最终会出现在大对象堆上,这可能导致到碎片化。如果您的数组大于 85kb 并且您创建/销毁它们很多,那么请考虑使用 float[m][n][o] 代替。

标签: c# algorithm multidimensional-array parallel-processing


【解决方案1】:

要考虑的一件大事是遍历的顺序。内存缓存是现代处理器性能的重要组成部分,缓存未命中可能(相对)昂贵。如果您跨一个“长”维度索引数组,导致跨越缓存边界,您可能会导致频繁的未命中作为索引的一部分。因此,索引的顺序很重要。这通常意味着您需要注意如何选择索引排序。

另外,在复制时,请考虑多重索引需要使用乘法/加法计算底层内存块的“真实”索引。但是,如果您只是复制所有元素,则可以简单地增加一个索引并访问每个元素,而无需额外的计算。

通过索引访问数组时还会发生各种条件检查(使 IndexOutOfRangeException 成为可能),当您通过多个索引访问时需要更多检查。我相信(尽管我不完全确定)抖动有时可以通过仅检查一次范围而不是每次索引操作来使用简单循环来优化单维数组访问。

【讨论】:

  • 我以行优先顺序索引,遍历第二个循环内的最后一个维度,然后是第一个。
  • @Rob Perkins,可能值得分析 3d 数组与 1d 数组。请注意,在第一种情况下,您还可以在以这种方式迭代 last-order-first 时保留一个并行索引器,并且每次迭代只需 i++;这将减少实际数组索引每次迭代的计算次数,同时如果需要,您仍然可以访问 x,y,z。
【解决方案2】:

我跑了一些时间,发现整体性能几乎没有区别:

我在下面使用了这段代码。在每种情况下,我得到的时间基本相同:

public partial class Form1 : Form
{
    int ArrayDim1 = 50;
    int ArrayDim23 = 500;
    int ParallelSplit = 50;
    int DoSomethingSize = 100;

    Double sqRoot = 0;

    Single[, ,] multidim = null;
    Single[] singleDim = null;
    Single[][][] jagged = null;

    ParallelOptions po = new ParallelOptions() { MaxDegreeOfParallelism = 36 };

    public Form1()
    {
        InitializeComponent();

        multidim = new Single[ArrayDim1, ArrayDim23, ArrayDim23];
        for (int x = 0; x < ArrayDim1; x++)
            for (int y = 0; y < ArrayDim23; y++)
                for (int z = 0; z < ArrayDim23; z++)
                    multidim[x, y, z] = 1;
        singleDim = new Single[ArrayDim1 * ArrayDim23 * ArrayDim23];
        for (int i = 0; i < singleDim.Length; i++)
            singleDim[i] = 1;

        jagged = new Single[ArrayDim1][][];

        for (int i = 0; i < ArrayDim1; i++)
        {
            jagged[i] = new Single[ArrayDim23][];
            for (int j = 0; j < ArrayDim23; j++)
            {
                jagged[i][j] = new Single[ArrayDim23];
            }
        }

    }

    private void btnGO_Click(object sender, EventArgs e)
    {
        int loopcount = 1;

        DateTime startTime = DateTime.Now;
        for (int i = 0; i < loopcount; i++)
        {
            TestMultiDimArray(multidim);
        }
        textBox1.Text = DateTime.Now.Subtract(startTime).TotalMilliseconds.ToString("#,###");

        startTime = DateTime.Now;
        for (int i = 0; i < loopcount; i++)
        {
            TestSingleArrayClean(singleDim);
        }
        textBox2.Text = DateTime.Now.Subtract(startTime).TotalMilliseconds.ToString("#,###");

        startTime = DateTime.Now;
        for (int i = 0; i < loopcount; i++)
        {
            TestJaggedArray(jagged);
        }
        textBox3.Text = DateTime.Now.Subtract(startTime).TotalMilliseconds.ToString("#,###");
    }

    public void TestJaggedArray(Single[][][] multi)
    {
        Parallel.For(0, ArrayDim1, po, x =>
        {
            for (int y = 0; y < ArrayDim23; y++)
            {
                for (int z = 0; z < ArrayDim23; z++)
                {
                    DoComplex();
                    multi[x][y][z] = Convert.ToSingle(Math.Sqrt(123412341));
                }
            }
        });
    }

    public void TestMultiDimArray(Single[, ,] multi)
    {

        Parallel.For(0, ArrayDim1, po, x =>
            {
                for (int y = 0; y < ArrayDim23; y++)
                {
                    for (int z = 0; z < ArrayDim23; z++)
                    {
                        DoComplex();
                        multi[x, y, z] = Convert.ToSingle(Math.Sqrt(123412341));
                    }
                }
            });
    }

    public void TestSingleArrayClean(Single[] single)
    {
        Parallel.For(0, single.Length, po, y =>
            {
                //System.Diagnostics.Debug.Print(y.ToString());
                DoComplex();
                single[y] = Convert.ToSingle(Math.Sqrt(123412341));
            });
    }

    public void DoComplex()
    {
        for (int i = 0; i < DoSomethingSize; i++)
        {
            sqRoot = Math.Log(101.101);
        }
    }
}

【讨论】:

    猜你喜欢
    • 2012-04-16
    • 2017-12-20
    • 2015-09-26
    • 1970-01-01
    • 2013-04-01
    相关资源
    最近更新 更多