阵列加速

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
您好,我有一个程序很忙于访问数组,所以我一直在尝试优化速度。这对于整个程序要进行大量矩阵操作的高度迭代非常重要。我希望对下面的代码提出任何批评,或者希望进一步提出建议以进一步提高它的速度。

当前的运行时间(在释放模式下)分别以ms为单位,相对时间差相对恒定,此刻,选项4看起来可能是最好的,这表示速度比选项2显着提高,而选项2是当前的方法节目。在我更改整个数组之前,是否有任何明显的改进或更好的方法,或者下面的代码有任何问题,这些问题可能会带来奇怪的结果?

Ms1 128
Ms2 29
Ms3 228
Ms4 7

阵列访问速度测试:
 [TestMethod]
        [MethodImpl(MethodImplOptions.NoInlining)]
        public unsafe void TestDoubleDoubleArrayWithPts()
        {

            double[,] Array = new double[10000,1000];
            var watch = Stopwatch.StartNew();
            int UpperBound0 = Array.GetUpperBound(0);
            int UpperBound1 = Array.GetUpperBound(1);

            watch.Restart();
            for (int i = 0; i < Array.GetUpperBound(0); i++)
                for (int y = 0; y < Array.GetUpperBound(1); y++)
                    Array[i,y] = 10D * 10D;

            var elapsedMs1 = watch.ElapsedMilliseconds;

            watch.Restart();
            for (int i = 0; i < UpperBound0; i++)
                for (int y = 0; y < UpperBound1; y++)
                    Array[i, y] = 10D * 10D;

            var elapsedMs2 = watch.ElapsedMilliseconds;

            watch.Restart();
            fixed (double* ptr = Array)
            {
                for (int i = 0; i < Array.GetUpperBound(0); i++)
                    for (int y = 0; y < Array.GetUpperBound(1); y++)
                        *(ptr + i * Array.GetUpperBound(1) + y) = 10D * 10D;    
            }

            var elapsedMs3 = watch.ElapsedMilliseconds;

            watch.Restart();
            fixed (double* ptr = Array)
            {
                for (int i = 0; i < UpperBound0; i++)
                    for (int y = 0; y < UpperBound1; y++)
                        *(ptr + i*UpperBound1 + y) = 10D * 10D;      
            }

            var elapsedMs4 = watch.ElapsedMilliseconds;

            System.Windows.Forms.MessageBox.Show(elapsedMs1.ToString() + " " + elapsedMs2.ToString() + " " + elapsedMs3.ToString() + " " + elapsedMs4.ToString());
       
        }
 
Solution
是的,您的权利,现在您提起它时看起来很奇怪,当我测试它时,结果看起来还不错,这里晚了,所以明天要检查一下。如果结果实际上是正确的,也许可以简化程序,则最终矩阵应该围绕对角线对称,这在数学上可能有些奇怪。可能只是错了。实际上,应该能够将sqrt调用减少一半,因为它是对称的,但是我太讨厌让它今天工作了。需要重新思考。

parralell可能很有趣!

对于纯塔模拟,我无法减少对该函数的调用次数,但对于流和常规Flash计算,一定可以。

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
多维数组(与锯齿状数组相反)在内存中连续布置。这就是为什么第36-41行的指针代码起作用的原因。跨度仅代表连续的内存范围。

36-41行可以改写为:
C#:
fixed (double* ptr = Array)
{
    double * dst = ptr;
    for (int i = 0; i < UpperBound0; i++)
        for (int y = 0; y < UpperBound1; y++)
            *dst++ = 10D * 10D;
}
以显示数组元素的连续性质。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
我不熟悉Spans
我在其他线程中也提到过Spans,您还问过如何优化矩阵访问:
 

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
我在其他线程中也提到过Spans,您还问过如何优化矩阵访问:
谢谢,是的,当时它可能还没有完全向我注册,这有点使我难以接受新的编程技巧,并且往往只花大量的代码。这是一个“业余时间”项目,因此付费工作(化学工程/热力学)经常打断我的思路。

实际上,其中一些技巧使C#的运行速度给我留下了深刻的印象,我的程序性能还没有达到商用程序性能(例如,可能是用C语言编写的东西),但是对于特定的应用程序却可以达到目标。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
同样,正如我在其他主题中指出的那样,在开始进行微优化之前,请始终选择尝试找到更有效的算法。超越Newton-Raphson方法,使用更有效的方法。

出于好奇,您是否使用模拟退火作为优化方法?这就是为什么您必须对听起来像模拟的东西进行多次迭代吗?为什么单纯形优化方法无法解决您的问题空间?

如果还需要进行矩阵乘法,并且探查器将其标识为瓶颈,请记下我在另一线程中关于创建矩阵的两个版本的评论:标准中的一个 行专业 布局,以及经过转换的版本(实际上是专栏专业)。这将加快处理速度,因为内存访问将是连续的,并有助于利用处理器的内存缓存以及有关将哪些内存带入缓存的预测器。

微观优化可能会加快处理速度,但是由于代码变得更难解析和理解,因此它会减慢开发人员的速度。有了更多的复杂性,虫子就更容易潜入。而且有了更多的复杂性,就很难对所有活动的部件有一个清晰的印象。如果没有大的了解,就很难知道可以在哪里进行其他优化或可以在哪里进行重构。
 

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
同样,正如我在其他主题中指出的那样,在开始进行微优化之前,请始终选择尝试找到更有效的算法。超越Newton-Raphson方法,使用更有效的方法。

出于好奇,您是否使用模拟退火作为优化方法?这就是为什么您必须对听起来像模拟的东西进行多次迭代吗?为什么单纯形优化方法无法解决您的问题空间?

如果还需要进行矩阵乘法,并且探查器将其标识为瓶颈,请记下我在另一线程中关于创建矩阵的两个版本的评论:标准中的一个 行专业 布局,以及经过转换的版本(实际上是专栏专业)。这将加快处理速度,因为内存访问将是连续的,并有助于利用处理器的内存缓存以及有关将哪些内存带入缓存的预测器。

微观优化可能会加快处理速度,但是由于代码变得更难解析和理解,因此它会减慢开发人员的速度。有了更多的复杂性,虫子就更容易潜入。而且有了更多的复杂性,就很难对所有活动的部件有一个清晰的印象。如果没有大的了解,就很难知道可以在哪里进行其他优化或可以在哪里进行重构。

是的,您对它的蒸馏塔模拟是正确的。矩阵进入各个方面,主要的工业标准方法之一是“内而外”方法,其中内部问题通过简化的热力学解决,而外部问题通过更新内部简化的方程式进行迭代。有矩阵可以解决每个组件的组件平衡问题(因此,对于一个原油塔,大约100个矩阵),可以通过矩阵求逆来求解直角。有一个包含梯度的雅可比矩阵(通过遍历100个组成矩阵*变量的数量进行评估,对于大型复杂塔楼,最大可能为100)。然后通过典型的newtown raphson类型迭代来解决该jacobian,直到满足所有约束为止。目前,我在每一步都将jacobian反转,而不是使用Sherman-Morrison更新公式。

所有这一切中最慢的步骤实际上是热力学,尽管它是由内而外方法的外循环,而不是成分矩阵或雅可比矩阵求逆和更新。 (这可能会随着我尝试更大的问题而改变)。

有一个小函数消耗了整个运行时间的大约20%左右(塔解决时间大约为300ms到1s,这会随着更大的问题而增长)。这个小函数隐藏在热力学中,不仅在整个仿真中使用在塔的问题。因此,这就是我目前针对的改进目标。

好的,所以300毫秒对于解决一座塔来说听起来并不多(我最近从专家那里听说,1980年代的口头禅是“一小时一座塔”),但是如果有多个塔的模拟并且有回收利用,那仍然很重要在流程图中从一个塔到另一个塔再返回。


Slow Step:
        static public double CalcAmix(Components cc, double[] XY, double[] ai, double[,] kijm, out double[,] Aij, out double[] sumAi)
        {
           // Debug.Print(": CalcAmix");
            //
            // T is in units of Kelvin; Tc in Kelvin; Pc in Pa
            //
            int n = cc.components.Count;
            double amix = 0;
            Aij = new double[n,n];
            sumAi = new double[n];

            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    if (i == j)
                        Aij[i, j] = ai[i];
                    else if (i > j)
                        Aij[i, j] = Aij[j, i];
                    else
                        Aij[i, j] = Math.Sqrt(ai[i] * ai[j]) * (1 - kijm[i, j]);
                }
            }

            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                    sumAi[i] += XY[j] * Aij[i, j];
               
                amix += sumAi[i] * XY[i];
            }

            return amix;
        }
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
是的,我记得,我父亲曾经说过"a tower an hour",但后来他常常这么高兴地说。这是因为他从60年代开始就是在炼油厂工作的化学工程师,因此将模拟工作降低至一个小时并保持较高的保真度是他们在大型机上进行隔夜模拟的一个重要改进。我还记得他有很多乐趣,能够用Lotus 1-2-3和1980年代的PC克隆来解决优化问题,以及将数学协处理器添加到计算中的时差是多少。
 

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
是的,我记得,我父亲曾经说过"a tower an hour",但后来他常常这么高兴地说。这是因为他从60年代开始就是一名化学工程师,因此将模拟工作降低至一个小时并保持高保真度是他们在大型机上隔夜运行时的粗略改进。我还记得他有很多乐趣,能够用Lotus 1-2-3和1980年代的PC克隆来解决优化问题,以及将数学协处理器添加到计算中的时差是多少。

它已经取得了长足的发展,如今,模拟已被视为一个简单的计算器,几乎总是需要即时准确的答案(这是不现实的)。

我很感兴趣,也许牛顿·拉夫森可能有更好的替代品,但并没有考虑太多。 NR可能会在没有很好的初步猜测的情况下趋于分歧。我花了很多时间来改进原始发布的算法(通常需要对开始条件进行良好的猜测),因此不需要猜测,现在可以由程序进行估计(这是对几种商用模拟器的(小)改进)。但是,比NR健壮的方法(或更好的NR版本,而不是我自己的方法)根本没有任何危害。
 

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
找到时间尝试一下,它不再是最慢的步骤。

实际上,现在调用Math.Sqrt是我最慢的步骤,它调用了35,700次,消耗了总运行时间的10%...因此,可能没有明显的潜在改进...

Speeded up version:
   static public unsafe double CalcAmix(Components cc, double[] XY, double[] ai, double[,] kijm, out double[,] Aij, out double[] sumAi)
        {
            // Debug.Print(": CalcAmix");
            //
            // T is in units of Kelvin; Tc in Kelvin; Pc in Pa
            //
            // Pointer Examples
         
            int n = cc.components.Count;
            double amix = 0;
            Aij = new double[n,n];
            sumAi = new double[n];
            int UpperBound0 = Aij.GetUpperBound(0);
            int UpperBound1 = Aij.GetUpperBound(1);

            fixed (double* ptr = Aij, aiPtr= ai, kijmPtr=kijm)
            {
                double* element = ptr;
                double* kijelement = kijmPtr;
                double* aielement = aiPtr;
                for (int i = 0; i <= UpperBound0; i++)
                {                
                    for (int j = 0; j <= UpperBound1; j++)
                    {
                        if (i == j)
                            *element = *aielement;
                        //else if (i > j)
                        //    *(ptr + i * UpperBound1 + j) = *(ptr + j * UpperBound1 + i);
                        else
                            *element = Math.Sqrt(*(aiPtr + i) * *(aiPtr + j)) * (1 - *kijelement);

                        element++;
                        kijelement++;
                    }
                   
                    aielement++;
                }
            }

            fixed (double* ptrAij = Aij, ptrXY = XY, ptrsumAI = sumAi)
            {
                double* elementAij = ptrAij;
                double* elementsumAI = ptrsumAI;
                double* elementXy = ptrXY;

                for (int i = 0; i < n; i++)
                {                
                    for (int j = 0; j < n; j++)
                    {
                        *elementsumAI += *elementXy * *elementAij;
                        elementXy++;
                        elementAij++;
                    }

                    amix += *elementsumAI * *(ptrXY+i);
                    elementXy = ptrXY;
                    elementsumAI++;
                }
            }
            return amix;
        }
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
我认为并行有很多机会,因此可以提高并行度,因为看起来您的输出没有重叠。我下班后会再看。

除非您可以滚动自己更快的基于平方根函数的查询,否则我认为您无法使平方根更快。

但是与此同时,我怀疑您要访问的传递到平方根的元素有问题:
C#:
Math.Sqrt(*(aiPtr + i) * *(aiPtr + j)) * (1 - *kijelement)
转换为数组符号:
C#:
Math.Sqrt(Aij[0,i] * Aij[0,j] * (1 - kijm[i,j])
I doubt that you always want just the first row of the Aij matrix.
 

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
是的,您的权利,现在您提起它时看起来很奇怪,当我测试它时,结果看起来还不错,这里晚了,所以明天要检查一下。如果结果实际上是正确的,也许可以简化程序,则最终矩阵应该围绕对角线对称,这在数学上可能有些奇怪。可能只是错了。实际上,应该能够将sqrt调用减少一半,因为它是对称的,但是我太讨厌让它今天工作了。需要重新思考。

parralell可能很有趣!

对于纯塔模拟,我无法减少对该函数的调用次数,但对于流和常规Flash计算,一定可以。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
我认为并行有很多机会,因此可以提高并行度,因为看起来您的输出没有重叠。我下班后会再看。

除非您可以滚动自己更快的基于平方根函数的查询,否则我认为您无法使平方根更快。

但是与此同时,我怀疑您要访问的传递到平方根的元素有问题:
C#:
Math.Sqrt(*(aiPtr + i) * *(aiPtr + j)) * (1 - *kijelement)
转换为数组符号:
C#:
Math.Sqrt(Aij[0,i] * Aij[0,j] * (1 - kijm[i,j])
I doubt that you always want just the first row of the Aij matrix.
我的错。您的命名约定使我感到困惑。现在,我要下班了,仔细看一下:
C#:
Math.Sqrt(*(aiPtr + i) * *(aiPtr + j)) * (1 - *kijelement)
转换为:
C#:
Math.Sqrt(ai[i] * ai[j]) * (1 - kijm[i,j])
现在看起来更合理了。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
因此,在没有使用任何并行性的情况下,我设法在笔记本电脑上以10000x10000矩阵运行电池时获得了这些计时:
C#:
Original pointers: Average: 729 ms
Back to arrays: Average: 916 ms
Back to arrays 2: Average: 650 ms
Back to arrays 3: Average: 585 ms
Spans: Average: 875 ms
Spans 2: Average: 610 ms
Spans 3: Average: 308 ms

Original pointers: Average: 801 ms
Back to arrays: Average: 893 ms
Back to arrays 2: Average: 749 ms
Back to arrays 3: Average: 580 ms
Spans: Average: 665 ms
Spans 2: Average: 571 ms
Spans 3: Average: 310 ms

(明天,我将尝试在台式机上运行代码,因为这往往会给出更一致的数字,而不会节流CPU来节省能源。)

使用来自帖子#12的以下代码变体。需要注意的关键事情之一是,我不想被内存分配时间分散注意力,所以我将这些分配给了非* Inner实现。希望,在我从一个变体中重构时,我不会感到无所适从到下一个。 * 2变体从第一个循环中除去条件,并在最终将值存储在第二个循环中之前使用临时变量来计算行总和。 * 3变体合并了第一和第二个循环,因此一次仅处理一行。

C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

class Program
{
    static public unsafe double CalcAmix(int n, double[] XY, double[] ai, double[,] kijm, out double[,] Aij, out double[] sumAi)
    {
        Aij = new double[n, n];
        sumAi = new double[n];
        return CalcAmixInner(n, XY, ai, kijm, Aij, sumAi);
    }

    static public unsafe double CalcAmixInner(int n, double[] XY, double[] ai, double[,] kijm, double[,] Aij, double[] sumAi)
    {
        double amix = 0;
        int UpperBound0 = Aij.GetUpperBound(0);
        int UpperBound1 = Aij.GetUpperBound(1);

        fixed (double* ptr = Aij, aiPtr = ai, kijmPtr = kijm)
        {
            double* element = ptr;
            double* kijelement = kijmPtr;
            double* aielement = aiPtr;
            for (int i = 0; i <= UpperBound0; i++)
            {
                for (int j = 0; j <= UpperBound1; j++)
                {
                    if (i == j)
                        *element = *aielement;
                    else
                        *element = Math.Sqrt(*(aiPtr + i) * *(aiPtr + j)) * (1 - *kijelement);

                    element++;
                    kijelement++;
                }

                aielement++;
            }
        }

        fixed (double* ptrAij = Aij, ptrXY = XY, ptrsumAI = sumAi)
        {
            double* elementAij = ptrAij;
            double* elementsumAI = ptrsumAI;
            double* elementXy = ptrXY;

            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    *elementsumAI += *elementXy * *elementAij;
                    elementXy++;
                    elementAij++;
                }

                amix += *elementsumAI * *(ptrXY + i);
                elementXy = ptrXY;
                elementsumAI++;
            }
        }
        return amix;
    }

    static public double CalcAmixArrays(int n, double[] XY, double[] ai, double[,] kijm, out double[,] Aij, out double[] sumAi)
    {
        Aij = new double[n, n];
        sumAi = new double[n];
        return CalcAmixArraysInner(n, XY, ai, kijm, Aij, sumAi);
    }

    static public double CalcAmixArraysInner(int n, double[] XY, double[] ai, double[,] kijm, double[,] Aij, double[] sumAi)
    {
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (i == j)
                    Aij[i,j] = ai[i];
                else
                    Aij[i,j] = Math.Sqrt(ai[i] * ai[j]) * (1 - kijm[i,j]);
            }
        }

        double amix = 0;
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < n; j++)
                sumAi[i] += XY[j] * Aij[i,j];

            amix += sumAi[i] * XY[i];
        }
        return amix;
    }

    static public double CalcAmixArraysInner2(int n, double[] XY, double[] ai, double[,] kijm, double[,] Aij, double[] sumAi)
    {
        for (int i = 0; i < n; i++)
        {
            var aii = ai[i];
            for (int j = 0; j < n; j++)
                Aij[i, j] = Math.Sqrt(aii * ai[j]) * (1 - kijm[i, j]);

            Aij[i, i] = aii;
        }

        double amix = 0;
        for (int i = 0; i < n; i++)
        {
            double sum = 0;
            for (int j = 0; j < n; j++)
                sum += XY[j] * Aij[i, j];

            sumAi[i] = sum;
            amix += sum * XY[i];
        }
        return amix;
    }

    static public double CalcAmixArraysInner3(int n, double[] XY, double[] ai, double[,] kijm, double[,] Aij, double[] sumAi)
    {
        double amix = 0;

        for (int i = 0; i < n; i++)
        {
            var aii = ai[i];
            double sum = XY[i] * aii;

            for (int j = 0; j < n; j++)
            {
                var value = Math.Sqrt(aii * ai[j]) * (1 - kijm[i, j]);
                Aij[i, j] = value;
                sum += XY[j] * value;
            }

            sum -= XY[i] * Aij[i, i];
            Aij[i, i] = aii;

            sumAi[i] = sum;
            amix += sum * XY[i];
        }

        return amix;
    }

    static public double CalcAmixSpansInner(int n,
                                            ReadOnlySpan<double> XY,
                                            ReadOnlySpan<double> ai,
                                            double[,] kijm,
                                            double[,] Aij,
                                            Span<double> sumAi)
    {
        for (int i = 0; i < n; i++)
        {
            var aii = ai[i];
            for (int j = 0; j < n; j++)
                Aij[i, j] = Math.Sqrt(aii * ai[j]) * (1 - kijm[i, j]);

            Aij[i, i] = aii;
        }

        double amix = 0;
        for (int i = 0; i < n; i++)
        {
            double sum = 0;
            for (int j = 0; j < n; j++)
                sum += XY[j] * Aij[i, j];

            sumAi[i] = sum;
            amix += sum * XY[i];
        }
        return amix;
    }

    static public unsafe double CalcAmixSpansInner2(int n,
                                             ReadOnlySpan<double> XY,
                                             ReadOnlySpan<double> ai,
                                             double[,] kijm,
                                             double[,] Aij,
                                             Span<double> sumAi)
    {
        fixed (double * kijmPtr = kijm, AijPtr = Aij)
        {
            var kijmSpan = new ReadOnlySpan<double>(kijmPtr, kijm.Length);
            var kijmRowISpan = kijmSpan.Slice(0);
            var AijSpan = new Span<double>(AijPtr, Aij.Length);
            var AijRowISpan = AijSpan.Slice(0);

            for (int i = 0; i < n; i++)
            {
                var aii = ai[i];
                for (int j = 0; j < n; j++)
                    AijRowISpan[j] = Math.Sqrt(aii * ai[j]) * (1 - kijmRowISpan[j]);

                AijRowISpan[i] = aii;
                kijmRowISpan = kijmRowISpan.Slice(n);
                AijRowISpan = AijRowISpan.Slice(n);
            }

            double amix = 0;
            AijRowISpan = AijSpan.Slice(0);
            for (int i = 0; i < n; i++)
            {
                double sum = 0;
                for (int j = 0; j < n; j++)
                    sum += XY[j] * AijRowISpan[j];

                sumAi[i] = sum;
                amix += sum * XY[i];
                AijRowISpan = AijRowISpan.Slice(n);
            }
            return amix;
        }
    }

    static public unsafe double CalcAmixSpansInner3(int n,
                                                    ReadOnlySpan<double> XY,
                                                    ReadOnlySpan<double> ai,
                                                    double[,] kijm,
                                                    double[,] Aij,
                                                    Span<double> sumAi)
    {
        fixed (double* kijmPtr = kijm, AijPtr = Aij)
        {
            var kijmSpan = new ReadOnlySpan<double>(kijmPtr, kijm.Length);
            var kijmRowISpan = kijmSpan.Slice(0);
            var AijSpan = new Span<double>(AijPtr, Aij.Length);
            var AijRowISpan = AijSpan.Slice(0);
            double amix = 0;

            for (int i = 0; i < n; i++)
            {
                var aii = ai[i];
                double sum = XY[i] * aii;

                for (int j = 0; j < n; j++)
                {
                    var value = Math.Sqrt(aii * ai[j]) * (1 - kijmRowISpan[j]);
                    AijRowISpan[j] = value;
                    sum += XY[j] * value;
                }

                sum -= XY[i] * AijRowISpan[i];
                AijRowISpan[i] = aii;

                sumAi[i] = sum;
                amix += sum * XY[i];

                kijmRowISpan = kijmRowISpan.Slice(n);
                AijRowISpan = AijRowISpan.Slice(n);
            }

            return amix;
        }
    }

    static Random _random = new Random(123);

    static double[] MakeRandomOneDimensionalArray(int n)
        => Enumerable.Range(0, n)
                     .Select(i => _random.NextDouble())
                     .ToArray();

    static double[,] MakeRandomTwoDimensionalArray(int n)
    {
        var arr = new double[n, n];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                arr[i, j] = _random.NextDouble();
        return arr;
    }

    static void TimeIt(string caption, Action action)
    {
        const int iterationCount = 5;
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for(int i = 0; i < iterationCount; i++)
            action();
        stopwatch.Stop();
        Console.WriteLine($"{caption}: Average: {stopwatch.ElapsedMilliseconds / iterationCount} ms");
    }

    static void Main()
    {
        int n = 10000;
        var xy = MakeRandomOneDimensionalArray(n);
        var ai = MakeRandomOneDimensionalArray(n);
        var kijm = MakeRandomTwoDimensionalArray(n);
        var aij = new double[n, n];
        var sumai = new double[n];

        TimeIt("Original pointers", () => CalcAmixInner(n, xy, ai, kijm, aij, sumai));
        TimeIt("Back to arrays", () => CalcAmixArraysInner(n, xy, ai, kijm, aij, sumai));
        TimeIt("Back to arrays 2", () => CalcAmixArraysInner2(n, xy, ai, kijm, aij, sumai));
        TimeIt("Back to arrays 3", () => CalcAmixArraysInner3(n, xy, ai, kijm, aij, sumai));
        TimeIt("Spans", () => CalcAmixSpansInner(n, xy, ai, kijm, aij, sumai));
        TimeIt("Spans 2", () => CalcAmixSpansInner2(n, xy, ai, kijm, aij, sumai));
        TimeIt("Spans 3", () => CalcAmixSpansInner3(n, xy, ai, kijm, aij, sumai));
    }
}
 

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
我的结果只是略有不同"Back to arrays 3"以最快的速度问世。因此,我将其与一些指针结合起来以获取以下内容,这些内容现在是我的PC上最快的。不知道为什么最后一次跨度测试如此不同。



option 3a:
    static unsafe public double CalcAmixArraysInner3a(int n, double[] XY, double[] ai, double[,] kijm, double[,] Aij, double[] sumAi)
        {
            double amix = 0;

            fixed (double* ptr = Aij, aiPtr = ai, kijmPtr = kijm)
            {
                double* elementIJ = ptr;
                double* kijelement = kijmPtr;
                double* aielement = aiPtr;
                double value;

                for (int i = 0; i < n; i++)
                {
                    var aii = ai[i];
                    double sum = XY[i] * aii;

                    for (int j = 0; j < n; j++)
                    {
                        kijelement++;
                        aielement++;
                        elementIJ++;

                        if (i > j)
                        {
                            value = *elementIJ;
                            Aij[j, i] = value;
                        }
                        else
                        {
                            value = Math.Sqrt(aii * *aielement) * (1 - *kijelement);
                            *elementIJ = value;
                        }

                        sum += XY[j] * value;
                    }

                    aielement = aiPtr;

                    sum -= XY[i] * Aij[i, i];
                    Aij[i, i] = aii;

                    sumAi[i] = sum;
                    amix += sum * XY[i];
                }
            }

            return amix;
        }
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
使用3a可获得更快的速度,因为您要进行平方根计算的一半,而只是镜像到下半部分。除非您将其他变体更新为也执行相同的镜像技巧,否则这不再是苹果与苹果的比较。

而且,紧密循环中的分支会降低性能。我的2个变体在第一个循环中删除条件是有原因的。现在,您已使用该检查在内部循环中重新引入了一个分支,以查看您是否在对角线以下。

无论如何,如果可以让您获得所需的速度,请尽情享受。尝试使代码变得更快很有趣。
 

银色阴影

活跃的成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
我的意思是,在我进行任何更改之前,您的CalcAmixArraysInner3在我的PC上显示为最快的速度,而Span3没有。

无论如何,感谢您的帮助,如果没有它,我将永远走不下去,您对我来说清楚了几件事,我也将它们应用于其他一些阵列。

非常感谢!
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,500
地点
弗吉尼亚州切萨皮克
编程经验
10+
这是我的PC上的结果(大约是2010年)。我喜欢带有leet编号的普通旧数组时间。 :)
C#:
Original pointers: Average: 1377 ms
Back to arrays: Average: 1337 ms
Back to arrays 2: Average: 1146 ms
Back to arrays 3: Average: 867 ms
Spans: Average: 1164 ms
Spans 2: Average: 1024 ms
Spans 3: Average: 821 ms

Original pointers: Average: 1335 ms
Back to arrays: Average: 1336 ms
Back to arrays 2: Average: 1154 ms
Back to arrays 3: Average: 868 ms
Spans: Average: 1163 ms
Spans 2: Average: 1026 ms
Spans 3: Average: 820 ms

令人惊讶的是,即使是采用节电模式的现代笔记本电脑,其运行性能也超过了旧PC。 :)
 
最佳 底部