罗兰关于MATLAB的艺术

将想法转化为MATLAB

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

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

内容

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

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

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

  • 1.5 - 2 x对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 0 v*cosd(角)v*sind(角)];我= 2:高度(T) [T.x(我),T.y(我),T.vx(我),T.vy (i)) =步骤(T.x(张),T.y(张),T.vx(张),T.vy(张),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被认为是有害的

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

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

% PROJECTILE_SCRIPT。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 0 v*cosd(角)v*sind(角)];我= 2:高度(T) [T.x(我),T.y(我),T.vx(我),T.vy (i)) =步骤(T.x(张),T.y(张),T.vx(张),T.vy(张),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);

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

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

我就不详细讲了try - catch方块在这篇文章。然而,我在这篇文章的结尾附上了两个文件,projectile_try_catch.mprojectile_try_regained.m.这些文件显示了问题及其解决方法。

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

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

最佳实践

总之,在为数据类型(例如)编写代码时,需要记住以下最佳实践以获得最佳性能表格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 0 v*cosd(角)v*sind(角)];我= 2:高度(T) [T.x(我),T.y(我),T.vx(我),T.vy (i)) =步骤(T.x(张),T.y(张),T.vx(张),T.vy(张),dt);结束结束% PROJECTILE_SCRIPT。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 0 v*cosd(角)v*sind(角)];我= 2:高度(T) [T.x(我),T.y(我),T.vx(我),T.vy (i)) =步骤(T.x(张),T.y(张),T.vx(张),T.vy(张),dt);结束函数T = projectile_try_catch (v,角)% PROJECTILE_TRY_CATCH。M -投射体的副本。M,但是要试着接住%。R2020a就地优化由于尝试捕获而丢失%。dt = 0.001;% 0.001秒T =表(“大小”30 / dt, [4],“VariableTypes”,[“替身”“替身”“替身”“替身”],“VariableNames”,[“x”“y”“vx”“v”]);试一试T{1,:} = [0 0 v*cosd(角)v*sind(角)];我= 2:高度(T) [T.x(我),T.y(我),T.vx(我),T.vy (i)) =步骤(T.x(张),T.y(张),T.vx(张),T.vy(张),dt);结束结束结束函数T = projectile_try_regained (v,角)% PROJECTILE_TRY_REGAINED。M -投射体的副本。M,但是要试着接住块放在一个单独的函数中。R2020a就地优化因为try-catch块在一个单独的本地函数中。试一试T = projectile_local (v,角);结束结束函数T = projectile_local (v,角)%这是创建projectile_try_收复表的代码。为获得最佳性能,此处不要使用try-catch,而应在%调用函数。dt = 0.001;T =表(“大小”30 / dt, [4],“VariableTypes”,[“替身”“替身”“替身”“替身”],“VariableNames”,[“x”“y”“vx”“v”]);T{1,:} = [0 0 v*cosd(角)v*sind(角)];我= 2:高度(T) [T.x(我),T.y(我),T.vx(我),T.vy (i)) =步骤(T.x(张),T.y(张),T.vx(张),T.vy(张),dt);结束结束




发布与MATLAB®R2020a

|

评论

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