在MATLAB的艺术洛伦

将想法转化为MATLAB

在表、datetime数组和其他数据类型中更快地建立索引

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

内容

那么,哪些方面得到了改善,如何改善?

从R2020a开始,MATLAB数据类型团队已经为索引特定类型的数组提供了大量的性能改进。改进的性能来自于就地优化。当您访问或分配值给a中的许多数组元素时,它们最明显对于循环。改进后的数据类型为:

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

  • 1.5-2x在分配a的元素时更快表格时间表变量
  • 数量级分配给的元素时更快(至少)calendarDuration分类datetime,持续时间阵列

你可以找到的全部细节,包括说明发行版之间的性能提升,在发行说明测试代码。碰巧,我们用于描述与定量更详细的性能增强的新格式。这些链接可直接用于R2019b和R2020a业绩发布说明:

有了这些细节,还有什么需要说的吗?是的。我要告诉你如何最好地利用这些性能改进。我还要介绍一些情况,其中这些改进生效。

对于循环

这里有一个简单的例子,它利用索引性能改进充分利用。在这个例子中,我计算弹丸在常规时间步长的位置。我使用一个公式,其中在每个步骤中的位置和速度取决于前一步骤的位置和速度。我创建了一个表,我给你的位置和速度表使用的行对于循环。图中显示了本例中投射体所走的路径。

这是一个名为的函数,计算位置和速度,然后将它们赋给一个表。(它取决于另一个函数,一步,该函数在一个时间步长上计算位置和速度。)请注意对于-循环,我使用点符号访问表变量。然后我使用循环计数器索引到变量(T.x(我)T.y(我),等等)。(我附上一步函数,请到本博客文章末尾。)

函数T =弹(v,角)%弹。函数创建弹丸的位置和表自由下落时的速度。dt = 0.001;%0.001秒T =表(“大小”30 / dt, [4],“VariableTypes”,(“替身”“替身”“替身”“替身”),“VariableNames”,(“x”“y”“vx”“v”]);Ť{1},:= [0 0 V * COSD(角度)V *信德(角度)];对于i = 2:height(T) [T.x(i), T.y(i), T.vx(i), T.vy(i)] = step(T.x(i-1), T.y(i-1), T.vx(i-1), T.vy(i-1), dt);结束结束

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

45 T =弹(50);头(T, 5)
表x y vx vy为0 0 35.355 35.355 0.035355 0.035341 35.346 0.070711 0.070671 35.336 0.10607 0.10599 35.336 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.7倍!您将看到性能的改进,至少在非常大的表和时间表(数百万行)上是如此之好,而且in也是如此分类datetime数组。

现在,注意我写这个例子的方式是强制使用a对于循环。如果可以的话,所有的最好的策略是vectorize您的最佳性能的代码。“向量化”码几乎装置操作上的阵列,而不是一个数组的元素。例如,它的更高效的通话z = x + y而不是打电话z(i) = x(i) + y(i)在一个对于循环。

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

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

脚本和试着抓认为是有害的

好吧,有害的是夸张。这是完全正常的把你的代码脚本,或内试着抓块,或与在工作区中的变量的工作。在所有这些情况下,代码我写的函数返回完全相同的表。

但是,如果该代码是在一个脚本或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”]);Ť{1},:= [0 0 V * COSD(角度)V *信德(角度)];对于i = 2:height(T) [T.x(i), T.y(i), T.vx(i), T.vy(i)] = step(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);

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

而且,当索引代码位于a中时,改进也会丢失试着抓块,即使当该块是自身的函数内。但在这种情况下,你可以通过将索引代码到一个单独的功能,性能回来。(P.S.这同样适用于嵌套函数如此。)

我不会去到有关的全部细节试着抓在这篇文章中。然而,我在这篇文章的结尾附加了两个文件,projectile_try_catch.mprojectile_try_regained.m。这些文件显示了问题及其解决方法。

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

示例文件 类型的代码 执行时间
projectile.m 函数 7.64秒
projectile_script.m 脚本 11.83秒
projectile_try_catch.m 索引码试着抓 11.75小号
projectile_try_regained.m 试着抓块,但索引代码在单独的功能 7.43秒

最佳实践

综上所述,这里要牢记的数据类型,如编写代码时以获得最佳性能的最佳实践表格datetime,分类

  • 当你可以向量化代码。例如,操作上表变量(T.X)代替表变量的元素(T.X(我)
  • 把你的表格datetime,分类在函数中索引代码,如果你做了很多索引而不能向量化你的代码。
  • 避免脚本,至少对于执行大量索引的代码是这样。将索引代码放在一个函数中。
  • 避免的try-catch块的代码,做了很多索引。把它放在自己的功能。

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

代码示例

函数[x, y,vx,vy] = step(x,y,vx,vy,dt)%STEP.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”]);Ť{1},:= [0 0 V * COSD(角度)V *信德(角度)];对于i = 2:height(T) [T.x(i), T.y(i), T.vx(i), T.vy(i)] = step(T.x(i-1), T.y(i-1), T.vx(i-1), T.vy(i-1), 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”]);Ť{1},:= [0 0 V * COSD(角度)V *信德(角度)];对于i = 2:height(T) [T.x(i), T.y(i), T.vx(i), T.vy(i)] = step(T.x(i-1), T.y(i-1), T.vx(i-1), T.vy(i-1), dt);结束函数T = projectile_try_catch (v,角)%PROJECTILE_TRY_CATCH.M  - 副本PROJECTILE.M的,但一个try-catch%的块。R2020a就地优化是因为在try-catch丢失%的块。dt = 0.001;%0.001秒T =表(“大小”30 / dt, [4],“VariableTypes”,(“替身”“替身”“替身”“替身”),“VariableNames”,(“x”“y”“vx”“v”]);试一试Ť{1},:= [0 0 V * COSD(角度)V *信德(角度)];对于i = 2:height(T) [T.x(i), T.y(i), T.vx(i), T.vy(i)] = step(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,角)%这是对PROJECTILE_TRY_REGAINED创建表的代码。%为了获得最佳性能,请不要在这里使用的try-catch,而是在%调用函数。dt = 0.001;T =表(“大小”30 / dt, [4],“VariableTypes”,(“替身”“替身”“替身”“替身”),“VariableNames”,(“x”“y”“vx”“v”]);Ť{1},:= [0 0 V * COSD(角度)V *信德(角度)];对于i = 2:height(T) [T.x(i), T.y(i), T.vx(i), T.vy(i)] = step(T.x(i-1), T.y(i-1), T.vx(i-1), T.vy(i-1), dt);结束结束




用MATLAB®R2020a发布

|

评论

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