矩阵和对象场相对速度。

银匠

众所周知的成员
加入
3月7日,2020年
消息
46
编程经验
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,892
地点
切萨皮克,va.
编程经验
10+
我的理解(来自阅读论坛)是C#针对锯齿状阵列进行了优化,
你能分享一些链接吗?这是我听说过的第一个。
 

跳伞运动员

工作人员
加入
2019年4月6日
消息
2,892
地点
切萨皮克,va.
编程经验
10+
我得到的相对速度(在调试中)是
不要对调试进行干扰测试。代码完全没有优化。生成的代码是帮助程序员更快地找到错误,而不是帮助程序运行更快。
 

跳伞运动员

工作人员
加入
2019年4月6日
消息
2,892
地点
切萨皮克,va.
编程经验
10+
注意TRAY1的上面部分应该取消锯齿状数组,似乎已经粘贴了不符合的粘贴。
您可以在代码标记中发布相应的代码。您已经删除了索引的机会,因为您将代码发布为纯文本而不是使用代码标记。有论坛软件在方括号中看到了一些文本,并认为文本是无效的BBCode标记。
 

跳伞运动员

工作人员
加入
2019年4月6日
消息
2,892
地点
切萨皮克,va.
编程经验
10+
这一切都归结为编译时间VS运行时计算需要访问内存的内存。无论如何解释速度差异:

现场访问
当你有一个看起来这样的代码
C#:
for (int i = 0; i < count; i++)
{
    tray = column[0][4];
    tray.T = i;
    res = tray.T;
}
在第3行时,编译器看到访问锯齿状数组(见下文),但索引是固定值0和4.通常它将插入标准 运行 绑定检查代码,但如果它可以推断数组的大小是什么,它将带走运行时绑定检查,并直接获取对数组元素的引用。此外,如果您正在编译释放而不是调试,则编译器通常足够智能以升离循环中的第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.
 

银匠

众所周知的成员
加入
3月7日,2020年
消息
46
编程经验
10+
这一切都归结为编译时间VS运行时计算需要访问内存的内存。无论如何解释速度差异:

现场访问
当你有一个看起来这样的代码
C#:
for (int i = 0; i < count; i++)
{
    tray = column[0][4];
    tray.T = i;
    res = tray.T;
}
在第3行时,编译器看到访问锯齿状数组(见下文),但索引是固定值0和4.通常它将插入标准 运行 绑定检查代码,但如果它可以推断数组的大小是什么,它将带走运行时绑定检查,并直接获取对数组元素的引用。此外,如果您正在编译释放而不是调试,则编译器通常足够智能以升离循环中的第3行,因为您始终始终访问相同的元素。

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

谢谢我认为现在对我有所了解,我认为这可能是我真正试图测试的事情是可能的计算速度。这里的列是索引对象的索引对象,所以实际上不是数组,抱歉我没有明确。实际上,托盘对象被检索一次,然后在某些字段上进行大量计算。替代方案是将所有内容存储在阵列中,无论是锯齿状还是多维。我认为你的意思是,多维数组是锯齿状的阵列更快,特别是如果我必须经常重新安排它们?如果可能的存储数据作为对象上的字段,可以更快地检索对象,然后在字段上执行多个计算器?假设如果对象(即托盘)必须使用索引器检索很多,它也会慢,也许与使用数组类似?

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

跳伞运动员

工作人员
加入
2019年4月6日
消息
2,892
地点
切萨皮克,va.
编程经验
10+
作为一个着名的报价:过早优化是邪恶的根源。  :)

通常会让你更快的速度是更好的算法。如果您可以使用复杂性分析来证明您已经使用了最佳算法,即观看像您尝试提高访问速度的微优化的时间。

作为经验的规则,分支较少的代码更快。绑定检查涉及分支。现代处理器通过阅读/执行几条指令来实现流水线。当他们击中分支机构时,他们试图预测将采取哪个分支并继续使用这些指令填充管道。如果预测错误,则需要清除管道。这减慢了处理。

另一个拇指规则是代码,使得更好地利用数据局部性速度更快。因此,如果您可以保留您需要靠近的数据,您将利用现代处理器的快速内存缓存。当数据不在快速内存缓存中时,需要从主存储器中拉出。

尝试在Microsoft上搜索Bjarne Stroustrups的视频,了解C和C ++数据结构教师的宠儿,以及喜欢的面试问题主题:链接列表。他涵盖为什么它虽然它具有理论o(1)复杂性,但它会击败它的裤子"bad" O (n) array.
 

银匠

众所周知的成员
加入
3月7日,2020年
消息
46
编程经验
10+
作为一个着名的报价:过早优化是邪恶的根源。  :)

通常会让你更快的速度是更好的算法。如果您可以使用复杂性分析来证明您已经使用了最佳算法,即观看像您尝试提高访问速度的微优化的时间。

作为经验的规则,分支较少的代码更快。绑定检查涉及分支。现代处理器通过阅读/执行几条指令来实现流水线。当他们击中分支机构时,他们试图预测将采取哪个分支并继续使用这些指令填充管道。如果预测错误,则需要清除管道。这减慢了处理。

另一个拇指规则是代码,使得更好地利用数据局部性速度更快。因此,如果您可以保留您需要靠近的数据,您将利用现代处理器的快速内存缓存。当数据不在快速内存缓存中时,需要从主存储器中拉出。

尝试在Microsoft上搜索Bjarne Stroustrups的视频,了解C和C ++数据结构教师的宠儿,以及喜欢的面试问题主题:链接列表。他涵盖为什么它虽然它具有理论o(1)复杂性,但它会击败它的裤子"bad" O (n) array.

该算法失败,牛顿拉文森,大量矩阵建筑&反转等。除了带有离线元素反转的三对角线矩阵外,其他慢步往往涉及exp&日志功能。可能不是我可以做的巨额量(我知道对数日志/ ex等的近似方法近似,但不是真的尝试)。使用字段而不是属性等的一些简单的改进很容易减半的解决时间。我尝试解决在内部内置的处理器核心中的矩阵,但是这样做的开销使其不值得努力。可能有一些可能有帮助的血流动力学例程的技巧。当前的解决方案时间为一个简单的蒸馏栏问题是大约400ms,我的感觉可能会被挤压到大约300ms,但可能没有少于那个。

对于完整的化学植物仿真,时间至关重要,它可以非常迭代,这300ms只是数百或数千次的一步。
 

跳伞运动员

工作人员
加入
2019年4月6日
消息
2,892
地点
切萨皮克,va.
编程经验
10+
就个人而言,我也会停在牛顿 - 拉申。看着维基百科,看起来有两种方法,在牛顿的时间里制作,另一个近期为1956年,这汇集了速度。他们可能值得调查。

我打算假设你已经探索了一些骗局数字图书馆,或者内置的矩阵类太小,或者你决定不要看他们,因为你想要自己写作的快乐。

看看你可以展开一些环的地方。当前的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:
最佳 底部