洛伦谈MATLAB的艺术

将想法转化为MATLAB

更快的表索引,datetime数组,和其他数据类型

今天我想介绍一位客座博主Stephen Doe,他在MathWorks的MATLAB文档团队工作。在今天的文章中,Stephen讨论了在索引表时如何利用最近的性能改进。同样的方法适用于许多不同的数据类型。虽然发布说明描述了性能改进,但在今天的帖子中,Stephen还基于一个简单的代码示例提供了进一步的建议。

内容

那么,是什么改善了,又是如何改善的呢?

从R2020a开始,MATLAB数据类型团队已经为索引特定类型的数组提供了实质性的性能改进。改进的性能来自于适当的优化。类中访问多个数组元素或给多个数组元素赋值时,它们最明显对于循环。改进后的数据类型为:

这些改进在两个版本(R2019b和R2020a)中进行了交付。因此,在这篇文章中,我比较了R2020a和R2019a的性能,这是没有任何这些改进的最新版本。总的来说,我们看到对于这些数据类型,数组元素的赋值比R2019a快很多倍。

  • 1.5-2x对a的元素赋值更快表格时间表变量
  • 数量级指定给的元素时更快(至少更快)calendarDuration,分类,datetime,期间数组

您可以在发行说明中找到完整的详细信息,包括说明不同版本之间性能改进的测试代码。碰巧,我们有了一种新的格式来描述性能增强,并提供了更多的定量细节。这些链接直接将您带到R2019b和R2020a的性能发行说明:

有了这样的细节,还有什么需要说的吗?是的. 我将向您展示如何最好地利用这些性能改进。我还将解释一些情况,这些改进不要生效。

表元素的赋值对于循环

下面是一个充分利用索引性能改进的简单示例。在这个例子中,我计算了一个弹丸在固定时间步长上的位置。我使用一个公式,其中每一步的位置和速度取决于前一步的位置和速度。我创建了一个表,并使用对于循环。图中显示了本例中弹丸所走的路径。

下面是一个函数,命名为,计算位置和速度,然后将它们分配到一张表格中。(它取决于另一个函数,一步,计算位置和速度在一个时间步长。)请注意对于-loop时,我使用点符号来访问表变量。然后我使用循环计数器索引变量(T.x(我),T.y(我)等等)。(我附上一步函数在这篇博文的最后。)

函数T =弹(v,角)%弹。函数创建弹丸位置和自由落体下的速度。dt = 0.001;%0.001秒T=表(“大小”30 / dt, [4],“VariableTypes”, (“替身”,“替身”,“替身”,“替身”],“VariableNames”, (“x”,“y”,“vx”,“v”]); T{1,:}=[0 v*cosd(角度)v*sind(角度)];对于i=2:高度(T)[T.x(i),T.y(i),T.vx(i),T.vy(i)]=步长(T.x(i-1),T.y(i-1),T.vx(i-1),T.vy(i-1),dt);终止终止

我们现在把起始速度50米/秒,角度45度。通过这个调用,输出表有30000行。我将使用函数显示前5行。

45 T =弹(50);头(T, 5)
Ans = 5×4 table x y vx vy ________ ________ ______ ______ 0 0 35.355 35.355 0.035355 0.035341 35.355 35.346 0.070711 0.070671 35.355 35.336 0.10607 0.10599 35.355 35.326 0.14142 0.1413 35.355 35.316

现在,让我们使用抽搐toc估计这个函数在两个不同版本的MATLAB: R2019a(在最近的改进之前)和R2020a的执行时间。

tic T =弹丸(50,45);toc

R2019a:13.06

R2020a:7.64秒。

(我在同一台机器上进行了这两个调用:一台Windows 10、Intel®Xeon®W-2133 @ 3.60 GHz的测试系统。)

这段代码在R2020a中大约快1.70倍!在使用非常大的表和时间表(数百万行)时,您将看到至少如此好的性能改进分类datetime数组。

现在,注意我写这个例子的方式是强迫使用a对于-循环。如果可以,最好的策略就是vectorize您的代码将获得最佳性能。“矢量化”代码相当于在数组上操作,而不是在数组的元素上操作。例如,打电话更有效Z = x + y而不是打电话Z (i) = x(i) + y(i)在一个对于循环。

但是,如果您有无法向量化的代码,因为它访问许多其他表或数组元素——就像我的示例中的代码一样——那么这些数据类型的性能改进将帮助您的代码运行得更快。

现在我们可以开始了,对吧?没有那么快。

脚本和试一试被认为有害的

好吧,有害的这是一种夸张。将代码放在脚本中或试一试块,或在工作区中使用变量。在所有这些情况下,我为函数返回完全相同的表。

但是,如果该代码位于脚本或try-catch块中,或者如果您以交互方式使用工作区变量,那么就没有性能增强。例如,让我在文件中将代码重写为脚本projector_script.m.在脚本中,我分配了相同的起始速度和角度。(我已附上一份projector_script.m直到这篇博文的结尾。)

%射弹脚本.M-创建射弹位置表的脚本%和自由落体下的速度。dt = 0.001;%0.001秒v = 50;% 50 m / s角度=45;% 45度T=表(“大小”30 / dt, [4],“VariableTypes”, (“替身”,“替身”,“替身”,“替身”],“VariableNames”, (“x”,“y”,“vx”,“v”]); T{1,:}=[0 v*cosd(角度)v*sind(角度)];对于i=2:高度(T)[T.x(i),T.y(i),T.vx(i),T.vy(i)]=步长(T.x(i-1),T.y(i-1),T.vx(i-1),T.vy(i-1),dt);终止

现在我调用函数并计时:

抽搐projectile_script toc

R2019a:12.36

R2020a:11.83

这两个版本的区别在于现在小!发生了什么事?

嗯,这很复杂。本质上,MATLAB将就地优化应用于这一行代码中完成的索引:

[T.x(我),T.y(我),T.vx(我),T.vy (i)) =步骤(T.x(张),T.y(张),T.vx(张),T.vy(张),dt);

要利用这些数据类型的就地优化,必须在函数中执行索引。如果您使用工作区变量或在脚本中这样做,您将看不到完整的性能改进。

而且,当索引代码处于试一试块,即使该块本身在函数中。但是在这种情况下,您可以通过将索引代码放入单独的函数中来恢复性能。(注:嵌套函数也是如此。)

我不想详细讨论这个问题试一试方块在这篇文章。然而,我在这篇文章的结尾附上了两个文件,projectile_try_catch.mprojectile_try_regained.m.这些文件显示了问题及其解决方法。

总而言之,这个表显示了我为这篇文章编写的不同代码片段,以及每种情况下您可以期待的性能。

示例文件 类型的代码 执行时间
projectile.m 函数 7.64秒
projector_script.m 脚本 11.83秒
projectile_try_catch.m 中的索引代码试一试 11.75秒
projectile_try_regained.m 试一试块,但索引代码在单独的函数中 7.43秒

最佳实践

总而言之,在为数据类型(例如,XML)编写代码时,要记住以下最佳实践,以获得最佳性能表格,datetime,分类:

  • 可以时向量化代码。例如,对表变量(T.X)而不是表变量的元素(T.X(我))
  • 把你的表格,datetime,分类在函数中索引代码,如果你做了很多索引并且不能向量化你的代码。
  • 避免脚本,至少对于需要大量索引的代码。将索引代码放在函数中。
  • 避免Try-catch块用于进行大量索引的代码。把它放到它自己的功能中。

由于MATLAB数据类型团队继续致力于提高性能,我们希望听到更多关于我们的数据类型的经验。请用表格,时间表,datetime,或分类数组在这里

代码示例

函数[x, y,vx,vy] = step(x,y,vx,vy,dt)%步骤M-根据输入位置计算位置和速度,%速度和局部重力加速度。以米为单位的位置,%速度单位为m/s,dt单位为秒。g = -9.8;% -9.8 m / s ^ 2vy=vy+g*dt;y=y+vy*dt+(g/2)*dt^2;x=x+vx*dt;终止函数T =弹(v,角)%弹。函数创建弹丸位置和自由落体下的速度。dt = 0.001;%0.001秒T=表(“大小”30 / dt, [4],“VariableTypes”, (“替身”,“替身”,“替身”,“替身”],“VariableNames”, (“x”,“y”,“vx”,“v”]); T{1,:}=[0 v*cosd(角度)v*sind(角度)];对于i=2:高度(T)[T.x(i),T.y(i),T.vx(i),T.vy(i)]=步长(T.x(i-1),T.y(i-1),T.vx(i-1),T.vy(i-1),dt);终止终止%射弹脚本.M-创建射弹位置表的脚本%和自由落体下的速度。dt = 0.001;%0.001秒v = 50;% 50 m / s角度=45;% 45度T=表(“大小”30 / dt, [4],“VariableTypes”, (“替身”,“替身”,“替身”,“替身”],“VariableNames”, (“x”,“y”,“vx”,“v”]); T{1,:}=[0 v*cosd(角度)v*sind(角度)];对于i=2:高度(T)[T.x(i),T.y(i),T.vx(i),T.vy(i)]=步长(T.x(i-1),T.y(i-1),T.vx(i-1),T.vy(i-1),dt);终止函数T = projectile_try_catch (v,角)%投射物\u TRY\u CATCH.M-投射物.M的副本,但带有一个TRY CATCH%街区。R2020a就地优化由于尝试捕获而丢失%街区。dt = 0.001;%0.001秒T=表(“大小”30 / dt, [4],“VariableTypes”, (“替身”,“替身”,“替身”,“替身”],“VariableNames”, (“x”,“y”,“vx”,“v”]);试一试T{1,:} = [0 0 v*cosd(角)v*sind(角)];对于i=2:高度(T)[T.x(i),T.y(i),T.vx(i),T.vy(i)]=步长(T.x(i-1),T.y(i-1),T.vx(i-1),T.vy(i-1),dt);终止终止终止函数T = projectile_try_regained (v,角)% PROJECTILE_TRY_REGAINED。M -投射体的副本。M,但是要试着接住块放在一个单独的函数中。R2020a就地优化因为try-catch块在一个单独的本地函数中。试一试T = projectile_local (v,角);终止终止函数T = projectile_local (v,角)%这是为重新获得的抛射物创建表的代码。为获得最佳性能,此处不要使用try-catch,而应在%调用函数。dt = 0.001;T=表(“大小”30 / dt, [4],“VariableTypes”, (“替身”,“替身”,“替身”,“替身”],“VariableNames”, (“x”,“y”,“vx”,“v”]); T{1,:}=[0 v*cosd(角度)v*sind(角度)];对于i=2:高度(T)[T.x(i),T.y(i),T.vx(i),T.vy(i)]=步长(T.x(i-1),T.y(i-1),T.vx(i-1),T.vy(i-1),dt);终止终止




发布与MATLAB®R2020a

|

评论

要留下评论,请点击在这里登录到您的MathWorks帐户或创建一个新帐户。