罗兰在MATLAB的艺术

把想法变成MATLAB

请注意

罗兰在MATLAB的艺术已经退休,不会被更新。

加快MATLAB应用程序

今天我想介绍一个客座博客,莎拉等Zaranek,他是一个应用程序工程师在MathWorks。今天她谈到加快代码从客户获得可接受的性能。

内容

介绍

客户遇到了缓慢的性能问题和她在MATLAB代码。她看到这样缓慢的表现,她决定重新编码算法在另一种语言。我想给她一些简单的技术在MATLAB代码可以带她到一个更合理的运行时间。

我参军的帮助下,我的应用程序工程师建议可能的变化。他们中的许多人也在一边帮腔了一些很好的建议。我已将我们大部分的思想整合成几个代码版本来与你分享。这些版本说明我们的思维过程,我们通过代码协作来提高它的速度。

在这篇文章中,我重点

  • 预先配置
  • 向量化
  • 消除重复的计算

这段代码是做什么的?

出于安全原因,用户不能给我她的确切代码。所以我们将使用的代码在这个博客作为一个近似的代码使用。

代码生成的位置在一个二维网格尺寸资料片通过nx2。这些网格位置作为初始值和最终使用的位置计算的主要部分。代码遍历所有可能的组合的初始和最终的位置。

给定一个初始和最终的位置,计算的代码数量指数。如果指数小于某个阈值,gausThresh,指数然后用来计算潜艇包含指标对应用于计算初始和最终位置

对于有些人来说,这可能是更好的理解通过查看代码本身。

最初的代码

%初始化网格和初始和最终点资料片= 10;nx2 = 10;x1l = 0;x1u = 100;x2l = 0;x2u = 100;x1 = linspace (x1l x1u,资料片+ 1);x2 = linspace (x2l、x2u nx2 + 1);limsf1 = 1:资料片+ 1;limsf2 = 1: nx2 + 1;% Initalize其他变量t = 1;sigmax1 = 0.5;sigmax2 = 1;σ= t * [sigmax1 ^ 2 0;0 sigmax2 ^ 2);invSig =发票(σ);detSig =侦破(σ);expF = [1 0;0 1];n =大小(expF, 1); gausThresh = 10; small = 0; subs = []; vals = [];%遍历所有可能的初始%和最后的位置和计算%的值指数如果指数> gausThresh %。i1 = 1:资料片+ 1i2 = 1: nx2 + 1f1 = limsf1f2 = limsf2%初始和最终的位置ξ= (x1 (i1) x2 (i2)) ';xf = (x1 (f1) x2 (f2)) ';指数= 0.5 * (xf - expF * xi)”* invSig * (xf - expF * xi);如果指数> gausThresh小=小+ 1;其他的= 1 /(√(2 *π)^ n * detSig))* exp(指数);潜艇=[潜艇;i1 i2 f1 f2);瓦尔斯= [vals;);结束结束结束结束结束

这是一个结果的图形化表示资料片= nx2 = 100。因为数据相当密集,我放大了一部分全图。红线连接初始和最终点指数< gausThresh和线的厚度反映的价值瓦尔斯

打开(“results.fig”);

这是我电脑上的运行时,诸如一个T60联想启用了多线程的双核笔记本电脑。

displayRunTimes (1)
资料片nx2时间50 50 100 100 9826 296秒秒

听M-Lint

第一和最容易的一步是听的建议M-Lint,附带的静态代码分析器的核心MATLAB。你可以通过编辑或命令行访问它。在本例中,我使用命令行和定义一个简单的函数displayMlint这显示紧凑。

输出= mlint (“initial.m”);displayMlint(输出);
“潜艇”可能是生长在一个循环。考虑preallocating速度。“val”可能是生长在一个循环。考虑preallocating速度。

遵循这些建议,我知道我需要preallocate一些数组。

为什么?

MATLAB为每个数组需要连续的内存块。因此,通过反复调整数组,我不断问MATLAB来寻找一个新的连续内存块并将旧数组元素移动到新的块。如果我preallocate最大的空间需要的数组,我没有遭受这样的额外成本搜索和移动。

然而,由于我不确定最后的大小潜艇var我不知道我应该preallocate大小。我可以使他们的最大可能大小。然而,的情况下资料片= nx2 = 100,需要一个100 ^ 4元素的向量,这让我陷入麻烦!

试一试0 (1100 ^ 4)结束显示器(ME.message)
内存不足。为你的选择输入帮助记忆。

因此,我需要块分配的变量。

我这样做首先preallocating一个合理的大小(这并不创建一个内存不足错误)潜艇var然后增加分配合理,按原样,如果潜艇var超越他们的预先分配的大小。

通过这种方式,我只遭受几次调整数组的成本,而不是每次添加一个新元素。

我还引入了一个函数,初始化,变量的初始化,这样你不需要反复看代码的一部分,不会改变。

预先配置的代码

%初始化资料片= 10;nx2 = 10;(x1, x2, limsf1、limsf2 expF, gausThresh,小,invSig, detSig, n] =初始化(资料片,nx2);%初始猜测预先配置毫米= min((资料片+ 1)^ 2 * (nx2 + 1) ^ 2, 10 ^ 6);潜艇= 0(毫米,4);瓦尔斯= 0(毫米,1);counter = 0;%遍历所有可能的初始%和最后的位置i1 = 1:资料片+ 1i2 = 1: nx2 + 1f1 = limsf1f2 = limsf2ξ= (x1 (i1) x2 (i2)) ';% %初始位置xf = (x1 (f1) x2 (f2)) ';% %最终位置指数= 0.5 * (xf - expF * xi)”* invSig * (xf - expF * xi);%增加预先配置,如果必要的如果计数器长度= = (val)潜艇=[潜艇;0(毫米,4)];瓦尔斯= [vals;0(毫米,1)];结束如果指数> gausThresh小=小+ 1;其他的%柜台了counter = counter + 1;= 1 /(√(2 *π)^ n * detSig))* exp(指数);潜艇(柜台)= (i1 i2 f1 f2);瓦尔斯(柜台)=;结束结束结束结束结束%去除来自预先配置的零组件瓦尔斯= vals (vals > 0);潜艇=潜艇(vals > 0);

预先配置使得更快的代码。

displayRunTimes (2)
资料片nx2时间50 50 100 100 4228 267秒秒

然而,这并不完全是加速我正在寻找。所以,让我们试着使用向量化处理代码。

向量化

由于MATLAB是一种基于数组的语言,编写的代码的矩阵和数组操作往往比代码依赖循环,特别是在嵌套的情况循环。在vectorizing、矩阵和向量操作代替等价的循环计算。

有几种可能性vectorizing这段代码。我将探索两个在这个博客条目。

  • Vectorize内部两个循环
  • Vectorize内部三个循环

预先配置的代码和向量化的内部两个循环

试1:Vectorize内部两个循环

%初始化资料片= 10;nx2 = 10;(x1, x2, limsf1、limsf2 expF, gausThresh,小,invSig, detSig, n] =初始化(资料片,nx2);瓦尔斯=细胞(资料片+ 1,nx2 + 1);%细胞预先配置潜艇=细胞(资料片+ 1,nx2 + 1);%细胞预先配置[xind, yind] = meshgrid (limsf1 limsf2);xyindices = [xind (:)”;yind (:));(x, y) = meshgrid (x1 (limsf1), x2 (limsf2));xyfinal = [x (:)”;y (:) ');exptotal = 0(长度(xyfinal), 1);%的遍历所有可能的组合的位置i1 = 1:资料片+ 1i2 = 1: nx2 + 1 xyinitial = repmat ((x1 (i1); x2 (i2)], 1,长度(xyfinal));扩张= 0.5 * (xyfinal - expF * xyinitial);expb = invSig * (xyfinal - expF * xyinitial);exptotal(: 1) =扩展(1:)。* expb(1:) +扩展(2:)。* expb (2:);指数=找到(exptotal < gausThresh);expreduced = exptotal (exptotal < gausThresh);= 1 /(√(2 *π)^ n * detSig)) * exp (- (expreduced));vals {i1、i2} =;潜艇{i1、i2} = [i1 *的(1、长度(索引));i2 *的(1、长度(索引));xyindices(指数);xyindices(指数)];结束结束%重塑和转换输出的%简单矩阵的格式瓦尔斯= cell2mat (vals (:));潜艇= cell2mat(潜艇(:));小=((资料片+ 1)^ 2 * (nx2 + 1) ^ 2)长度(潜艇);

哇!只有这一点向量化,运行时间大大减少。

displayRunTimes (3)
资料片nx2时间50 50 100 100 1.51秒19.28秒

我们去这里我说明的主要技术。

  • 使用meshgridrepmat创建矩阵
  • 利用向量和矩阵运算
  • 在适当的时候使用element-wise操作

meshgrid产生的两个向量xy职位的所有可能的组合xy输入向量。换句话说,给定的向量xy,meshgrid给所有可能的位置在网状或网格上定义一个人xy输入向量。n维版meshgridndgrid在下一个示例中,我使用它。

这个函数repmat复制和瓷砖的数组。

通常我使用矩阵作为向量化的一部分,所以我尽可能用向量和矩阵运算。

然而,在一个段的代码实际上是快来显式地写出一个向量元素乘以元素。这是有点不直观,所以让我们更详细地讨论这个问题。

计算指数在原始代码,有一系列的向量和矩阵乘法最初的和最后的位置,每一个由双元素向量表示。初始和最终位置的值然后通过循环改变了每一步。

矢量化的代码时,所有的初始位置和最后的位置现在包含在两个矩阵xyinitialxyfinal。因此,计算的价值指数,只有对角线的乘法的扩张expb是相关的。然而,在这种情况下,把一个矩阵的对角线繁殖是一个相当昂贵的操作。相反,我选择把它写成一个操作中的元素。

我只有向你展示一些有用的函数向量化在这篇文章中。在这里是一个更广泛的向量化的有用的功能列表。

预先配置的代码和向量化内部三个循环

试2:Vectorize内部三个循环

%初始化资料片= 10;nx2 = 10;(x1, x2, limsf1、limsf2 expF, gausThresh,小,invSig, detSig, n] =初始化(资料片,nx2);limsi1 = limsf1;limsi2 = limsf2;% ndgrid给出了矩阵的所有可能的组合(并绑定,cind] = ndgrid (limsi2、limsf1 limsf2);[a, b, c] = ndgrid (x2, x1, x2);瓦尔斯=细胞(资料片+ 1,nx2 + 1);%细胞预先配置潜艇=细胞(资料片+ 1,nx2 + 1);%细胞预先配置%将网格转换成单一向量使用在一个循环b = b (:);并=并(:);绑定=绑定(:);cind = cind (:);expac = (:) - c (:);%计算x2-x1%迭代初始x1位置(i1)i1 = limsi1 exbx1 = b-x1 (i1);expaux = invSig (2) * exbx1。* expac;指数= 0.5 * (invSig (1) * exbx1。* exbx1 + expaux);指数=找到(指数< gausThresh);expreduced =指数(指数< gausThresh);vals {i1} = 1 /(√(2 *π)^ n * detSig)). * exp (-expreduced);潜艇{i1} = [i1 *的(1、长度(索引));并(指数)的;绑定(指数)的;cind(指数)“]”;结束瓦尔斯= cell2mat (vals (:));潜艇= cell2mat(潜艇(:));小=((资料片+ 1)^ 2 * (nx2 + 1) ^ 2)长度(潜艇);

现在代码运行在更少的时间。

displayRunTimes (4)
资料片nx2时间50 50 100 100 0.658秒8.77秒

我为什么不试试所有的循环进行向量化?

我当然可以。在这种情况下,我没有看到太多的好处,总向量化,而变得有点难读的代码。所以,我不会显示完全向量化代码。考虑性能和代码的可读性,尝试2似乎是最好的解决方案。

矢量化的代码之后,您可以很容易地看到,我反复计算值,只需要计算一次。同时,假设一些向量保持相同的形式(如σ),其他的计算(例如,InvSig)是不必要的。

这些变化是很容易做的,和一些更高级的,但我想完成这个条目最快的版本的代码从应用程序工程参与者。

预先配置的代码,向量化,只计算一次

资料片= 100;nx2 = 100;x1l = 0;x1u = 100;x2l = 0;x2u = 100;x1 = linspace (x1l x1u,资料片+ 1);x2 = linspace (x2l、x2u nx2 + 1);limsi1 = 1:资料片+ 1;limsi2 = 1: nx2 + 1; limsf1 = 1:nx1+1; limsf2 = 1:nx2+1; t = 1; sigmax1 = 0.5; sigmax2 = 1; sigma = t * [sigmax1^2 sigmax2^2]; detSig = sigma(1)*sigma(2); invSig = [1/sigma(1) 1/sigma(2)]; gausThresh = 10; n=3; const=1 / (sqrt((2 * pi)^n * detSig));% ndgrid给出了矩阵的所有可能的组合%的位置,除了limsi1我们遍历(并绑定,cind] = ndgrid (limsi2、limsf1 limsf2);[a, b, c] = ndgrid (x2, x1, x2);瓦尔斯=细胞(资料片+ 1,nx2 + 1);%细胞预先配置潜艇=细胞(资料片+ 1,nx2 + 1);%细胞预先配置%将网格转换成单一向量%使用一个for循环b = b (:);并=并(:);绑定=绑定(:);cind = cind (:);expac = (:) - c (:);expaux = invSig (2) * expac。* expac;%迭代初始x1的位置i1 = limsi1 expbx1 = b-x1 (i1);指数= 0.5 * (invSig (1) * expbx1。* expbx1 + expaux);%找到指数,指数< gausThresh指数=找到(指数< gausThresh);%找到和保持值exp < gausThresh哪里expreduced =指数(指数< gausThresh);vals {i1} = const。* exp (-expreduced);潜艇{i1} = [i1 *的(1、长度(索引));并(指数)的;绑定(指数)的;cind(指数)“]”;结束瓦尔斯= cell2mat (vals (:));潜艇= cell2mat(潜艇(:));小=((资料片+ 1)^ 2 * (nx2 + 1) ^ 2)长度(潜艇);

,这是不可思议的。

displayRunTimes (5)
资料片nx2时间50 50 100 100 0.568秒8.36秒

很好的加速效果。事实上,你看到运行时减少到约2%的原始时代。

额外的资源

在这篇文章中,我关注的是预先配置,向量化,只计算一次。不过,也有额外的技术来帮助你加快MATLAB代码诊断瓶颈,如使用分析器使用mex files,使用bsxfun,支持多线程。MATLAB的文档有一个非常有用的部分性能。我鼓励你去那里阅读更多关于这些和其他相关主题。

你的例子

告诉我关于你的一些方式加快MATLAB应用程序或随意发布技术我没有说明的例子在这里




使用MATLAB®7.6发表

|
  • 打印
  • 发送电子邮件