在表、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.m和projectile_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);结束结束
评论
想留下评论,请点击在这里登录您的MathWorks帐户或创建一个新帐户。