矩阵与物场的相对速度。

银色阴影

活跃成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
在某些背景下,我是工程师而不是专业程序员,所以对任何愚蠢的问题表示歉意。我有一个计算量大的程序,我想加快速度。我的理解(来自阅读论坛)是C#针对锯齿状数组进行了优化,但是下面的测试似乎没有说明。如果我做错了,请告诉我,使用托盘对象(可能是任何中等复杂的对象)的测试似乎太快了,代码是否有问题?

我在调试中获得的相对速度为: (注意:我只是意识到时间包括数组感染,但是没关系,数组将在程序中多次清除或重新初始化)。

对象/字段访问0ms
锯齿状阵列访问87ms
普通阵列访问12ms

如果我不算初始化数组,我分别得到0、7和7毫秒。



C#:
var watch = Stopwatch.StartNew();
double res=0;
int count = 10000;
Tray tray = column[0][4];
for (int i = 0; i < count; i++)
{
    tray = column[0][4];
    tray.T = i;
    res = tray.T;
}
watch.Stop();

var elapsedMs = watch.ElapsedMilliseconds;
Debug.WriteLine("Column Solution Time1 ms: " + elapsedMs.ToString() + " " + res.ToString());
MessageBox.Show("Object Field" + elapsedMs.ToString());


watch = Stopwatch.StartNew();
double res2 = 0;
int count2 = count;
double[][] tray1 = new double[count2][];
for (int i = 0; i < count2; i++)
    tray1 = new double[count2];

for (int i = 0; i < count2; i++)
{
    tray1 = i;
    res2 = tray1;
}
watch.Stop();

elapsedMs = watch.ElapsedMilliseconds;
Debug.WriteLine("Column Solution Time2 ms: " + elapsedMs.ToString() +" "+ res2.ToString());
MessageBox.Show("Jagged Matix" + elapsedMs.ToString());

watch = Stopwatch.StartNew();
double res3 = 0;
int count3 = count;
double[,] tray3 = new double[count3,count3];

for (int i = 0; i < count3; i++)
{
    tray3[i,i] = i;
    res3 = tray3[i,i];
}
watch.Stop();

elapsedMs = watch.ElapsedMilliseconds;
Debug.WriteLine("Column Solution Time3 ms: " + elapsedMs.ToString() + " " + res3.ToString());
MessageBox.Show("Matix" + elapsedMs.ToString());
 
Last edited:

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,536
地点
弗吉尼亚州切萨皮克
编程经验
10+
我的理解(来自阅读论坛)是C#针对锯齿数组进行了优化,
可以分享一些链接吗?这是我第一次听说。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,536
地点
弗吉尼亚州切萨皮克
编程经验
10+
我在调试中获得的相对速度为
不要针对调试进行性能测试。该代码是完全未优化的。生成的代码是为了帮助程序员更快地发现错误,而不是帮助程序更快地运行。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,536
地点
弗吉尼亚州切萨皮克
编程经验
10+
请注意,托盘1的上述部分应使用锯齿状的阵列,似乎已粘贴incorreclty。
您可以在代码标签中发布适当的代码吗?很有可能您的索引被剥夺了,因为您将代码以纯文本形式发布,而不是使用代码标签。那里的论坛软件在方括号中看到一些文本,并认为该文本是无效的BBcode标记。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,536
地点
弗吉尼亚州切萨皮克
编程经验
10+
归结为编译时间与运行时计算需要访问哪些内存。无论如何解释速度差异:

现场访问
当你有这样的代码
C#:
for (int i = 0; i < count; i++)
{
    tray = column[0][4];
    tray.T = i;
    res = tray.T;
}
在第3行,编译器可以看到对锯齿状数组的访问(请参见下文),但是索引是固定值0和4。通常,它将插入标准 运行 边界检查代码,但是如果可以推断出数组的大小,它将取消运行时边界检查,而直接获得对数组元素的引用。此外,如果要为Release而不是调试进行编译,则编译器通常足够聪明,可以将第3行从循环中移出,因为您总是在访问同一元素。

无论如何,第4行和第5行的现场访问速度很快,因为 编译时间 the offset of the field T is computed and then values are written to/read from that address.

阵列存取
当您具有如下所示的代码时:
C#:
for (int i = 0; i < count; i++)
{
    tray3[i,i] = i;
    res3 = tray3[i,i];
}
On lines 3 and 4, at the array index i,i is computed at 运行。它比锯齿形数组快的原因(请参阅下文)是因为通常在计算内存偏移之后仅执行一次边界检查。

锯齿状阵列访问
当您具有如下所示的代码时:
C#:
for (int i = 0; i < count; i++)
{
    tray1[i][i] = i;
    res3 = tray1[i][i];
}
在第3和第4行,有两个 运行 bounds array checks. The first check is done by tray1[i] to make sure that i is within range for the first dimension. If it is valid, that returns an array for the second dimension. To access an element in that second dimension array, another bounds check is performed.
 

银色阴影

活跃成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
归结为编译时间与运行时计算需要访问哪些内存。无论如何解释速度差异:

现场访问
当你有这样的代码
C#:
for (int i = 0; i < count; i++)
{
    tray = column[0][4];
    tray.T = i;
    res = tray.T;
}
在第3行,编译器可以看到对锯齿状数组的访问(请参见下文),但是索引是固定值0和4。通常,它将插入标准 运行 边界检查代码,但是如果可以推断出数组的大小,它将取消运行时边界检查,而直接获得对数组元素的引用。此外,如果要为Release而不是调试进行编译,则编译器通常足够聪明,可以将第3行从循环中移出,因为您总是在访问同一元素。

无论如何,第4行和第5行的现场访问速度很快,因为 编译时间 the offset of the field T is computed and then values are written to/read from that address.

谢谢,我认为这对我来说已经很有意义了,我认为这可能是一个更好的示例,因为我真正想测试的是可能的计算速度。这里的列是索引对象的索引对象,因此实际上不是数组,对不起,我没有弄清楚。实际上,托盘对象被检索一次,然后在某些字段上进行了大量计算。另一种方法是将所有内容存储在交错或多维的数组中。我认为您的意思是,多维数组比锯齿状数组快,尤其是如果我必须相当频繁地重新初始化它们时,它更快吗?并且在可能的情况下,将数据存储为对象上的字段的速度可能更快,在此情况下,一次检索对象然后对字段执行几次计算?如果对象(即托盘)必须使用分度器检索很多,那么Presumaby也会更慢,并且可能类似于使用数组吗?

托盘=列[0] [4];
C#:
for (int i = 0; i < count; i++)
{
    tray.T = i;
    res = tray.T;
}
 

银色阴影

活跃成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
不要针对调试进行性能测试。该代码是完全未优化的。生成的代码是为了帮助程序员更快地发现错误,而不是帮助程序更快地运行。

谢谢,是的,我在调试和重新发布中都进行了尝试,发布速度显然更快,但是具有类似的相对性能。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,536
地点
弗吉尼亚州切萨皮克
编程经验
10+
众所周知,过早的优化是邪恶的根源。 :)

通常,可以让您提高速度的是更好的算法。如果可以使用复杂度分析证明您已经在使用最佳算法,那么该是时候着眼于此类微优化,以提高访问速度。

根据经验,分支较少的代码运行得更快。边界检查涉及分支。现代处理器通过读取/执行前面的几条指令来实现流水线。当他们到达分支时,他们试图预测将采取哪个分支,并继续使用这些指令填充管道。如果预测错误,则需要清除管道。这会减慢处理速度。

另一个经验法则是可以更好地利用数据局部性的代码,其运行速度更快。因此,如果可以将需要播放的数据保持在一起,则可以利用现代处理器的快速内存缓存。当数据不在快速内存缓存中时,需要将其从主内存中拉出。

尝试搜索Microsoft的Bjarne Stroustrups演讲的视频,其中包括C和C ++数据结构老师的宠儿以及最喜欢的面试问题:链接列表。他介绍了为何尽管理论上具有O(1)复杂性,但它却被裤子击败了。"bad" O(n) array.
 

银色阴影

活跃成员
已加入
2020年3月7日
留言内容
30
编程经验
10+
众所周知,过早的优化是邪恶的根源。 :)

通常,可以让您提高速度的是更好的算法。如果可以使用复杂度分析证明您已经在使用最佳算法,那么该是时候着眼于此类微优化,以提高访问速度。

根据经验,分支较少的代码运行得更快。边界检查涉及分支。现代处理器通过读取/执行前面的几条指令来实现流水线。当他们到达分支时,他们试图预测将采取哪个分支,并继续使用这些指令填充管道。如果预测错误,则需要清除管道。这会减慢处理速度。

另一个经验法则是可以更好地利用数据局部性的代码,其运行速度更快。因此,如果可以将需要播放的数据保持在一起,则可以利用现代处理器的快速内存缓存。当数据不在快速内存缓存中时,需要将其从主内存中拉出。

尝试搜索Microsoft的Bjarne Stroustrups演讲的视频,其中包括C和C ++数据结构老师的宠儿以及最喜欢的面试问题:链接列表。他介绍了为何尽管理论上具有O(1)复杂性,但它却被裤子击败了。"bad" O(n) array.

该算法简单易行,牛顿拉普森,建立大量矩阵&反演等。除了带外元素反演的三对角矩阵外,其他缓慢的步骤往往涉及Exp&日志功能。可能我做不到的事情很多(我知道大约更快的log / exp等方法,但是还没有真正尝试过)。一些简单的改进,例如使用字段代替属性等,很容易将求解时间减少了一半。我曾尝试解决例程中内置的处理器核心中的矩阵,但是这样做的开销使它不值得付出努力。血流动力学程序可能有一些技巧,可能会有所帮助。对于一个简单的蒸馏塔问题,当前的解决时间约为400毫秒,我的感觉也许是可以压缩到300毫秒左右,但可能不少于300毫秒。

对于完整的化工厂仿真来说,时间至关重要,因为它可能是非常反复的,而这300ms仅是数百或数千步中的一步。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,536
地点
弗吉尼亚州切萨皮克
编程经验
10+
就我个人而言,我也将在牛顿-拉弗森停留。从维基百科来看,似乎有两种创建方法,一种是在牛顿时代创建的,另一种是在1956年创建的,它们收敛于根的速度更快。他们也许值得调查。

我将假设您已经探索了一些知名的现成数字库,或者内置的Matrix类太小,或者您决定不看它们,因为您想要编写自己的数字的乐趣。

查看可以展开一些循环的地方。当前的C#编译器和JIT编译器还不如C / C ++编译器好,它们在最佳状态下会自动展开。这使我可以在一些对性能至关重要的代码中加快速度。

Next look for opportunities for parallelization. Matrix operations are usually ripe for this, but as you are discovering, there maybe a need to change the underlying representation to let each thread access data independently and quickly. In one bit of code I had, it was faster to make two copies of the same matrix: one row major and another column major, and then use Span<T> to get more speed. (I was expecting the overhead of making the copy would overwhelm the speed savings, but I was surprised to find that it was worth it.)

If you are willing to compile code with the /unsafe flag, and use unsafe blocks of code, you can get several more microseconds but using pointers. This will involve you computing matrix offsets yourself. Personally, if/when using computing the offlsets becomes too unweildy, I would rather just write some C++ code in another DLL and P/Invoke the C++ code.
 
Last edited:
最佳 底部