自动转换MATLAB代码到C代码
参加本次演示,学习如何从MATLAB中自动生成可读和可移植的C代码®算法。使用MATLAB Coder™,您可以通过使用自动C代码生成平稳地从算法过渡到实现来减少开发时间。此C代码不需要任何进一步的MATLAB库,并且可以自由分发。
这个演示展示了如何使用命令行方法或图形化项目管理工具:
- 在MATLAB算法中引入实现需求,例如数据类型和大小、静态/动态内存分配和代码内联
- 生成可读的源代码、可执行文件或库
- 生成MEX函数,在MATLAB中验证生成代码的行为
- 生成MEX函数来加速您的MATLAB代码
记录日期:2014年8月19日
大家好,我是Daryl Ning,今天我们将讨论如何使用MATLAB Coder自动将MATLAB转换为C代码。首先,让我们从一个使用欧几里得距离测量的例子开始,我将展示如何使用MATLAB函数,并使用MATLAB Coder,生成通用的NCC代码。让我们回到MATLAB。在MATLAB中,你可以看到原始的函数,这个函数叫做欧几里得函数。欧几里得取某个输入向量x,然后将这个向量与一个向量代码本进行比较,然后返回距离最小的向量。你可以看到我们调用了另一个叫做norm的函数。
我们有一个for循环,还有一个if语句。但它只是一个非常基本的MATLAB函数。现在,当我们用MATLAB编码器运行这个,我们会得到这个C代码。如果你看一下这段C代码,你会看到我们有这个叫做norm的辅助函数,这是C代码,我会往下滚动一点。你会看到这里是我们的主要入口点函数,叫做欧几里得函数,同样是C代码。希望从这里,你能看到我们生成的C代码首先是通用的,而且是可读的。这是一个已经成功使用MATLAB编码器的客户。iSonea开发了用于喘息检测和哮喘管理的移动应用程序和服务软件。使用MATLAB Coder,他们能够减少手工编码的工作量,加速了算法开发迭代,最重要的是,他们的代码维护开销也减少了。
你可以在这里的引用中看到,他们说没有其他的环境或编程语言可以用来在相同的时间内产生类似的结果。让我们来看看议程。首先,我们将讨论MATLAB到c代码生成的动机。然后,我们将讨论生成C代码的三步工作流。然后,我们将查看一些用例并快速总结。那么为什么工程师要把MATLAB翻译成C语言呢?有很多原因。首先,他们可能需要在进程中实现C代码,或者将这些代码交给软件工程师,因此他们需要原始的NCC代码。或者,他们可能需要将他们的MATLAB算法与现有的C环境集成,因此他们想要使用源代码并创建某种静态或动态库。
他们想要这样做的另一个原因可能是,他们想要在桌面上作为独立的可执行文件对MATLAB算法进行原型化。最后,有些人只是想要MATLAB中的C代码来加速他们用户编写的MATLAB算法,基本上是为了更快地模拟。因此,如果我们从技术计算工作流的角度来考虑这个问题,我们有我们的访问数据阶段,我们的探索和发现阶段,以及我们的共享阶段,我们将展示的是如何从算法开发转移到部署,部署是独立的NCC代码。但更重要的是,我们如何在这些阶段之间快速地来回切换。如果我们想一下我们在做什么,我们从什么开始,我们从MATLAB环境开始。在MATLAB环境中,你可以做很多事情。所以你可以可视化你的数据,你可以开发你的算法,你甚至可以开发用户界面围绕你的算法,就像应用程序一样。
现在,如果你想把所有这些,你想把可视化,算法和用户界面部署到某个目标上,我们有一个解决方案,叫做MATLAB编译器。现在,MATLAB编译器可以从您的MATLAB算法创建独立的可执行文件和C共享库,包括可视化和围绕它的任何用户界面。这样做的缺点是,这个应用程序将需要称为MATLAB编译器运行时的东西,因此有一些开销。有一个引擎需要在后台运行。MATLAB编译器不会将MATLAB算法转换为C代码。然而,如果您只是想采用算法并创建独立的NCC代码,这就是MATLAB Coder的用用点。所以MATLAB Coder能够为大多数MATLAB算法生成C代码并为你提供独立可读的C代码就像你在最初的例子中看到的那样。然后,您可以使用这些C代码并对其进行任何操作,无论您是想创建可执行文件还是库。
这就是我们接下来要讲的内容,但我会在最后简单介绍一下MATLAB编译器的用例。让我们从一个介绍性的演示开始,我会向你展示如何使用MATLAB Coder应用程序来帮助你在代码生成过程中。让我回到MATLAB。好的,我们现在要看的只是一个简单的矩阵乘法。你可以看到这里我有一个基本的函数,它简单地将两个输入相乘,叫做mymult。现在,如果我想从这个函数生成C代码,我要做的是在MATLAB的apps选项卡,我要打开我的MATLAB Coder应用程序,如果我向下滚动到我的代码生成应用程序,我要点击MATLAB Coder。第一件事是它让我给我的项目起一个名字,所以我把它叫做mymult。
现在,当项目打开时,我首先需要添加入口点文件。要做到这一点,我可以简单地点击添加文件,然后点击mymult。现在,因为我们正在生成C代码,我现在需要定义输入大小及其类型。要做到这一点,我可以点击这里定义它们。对于输入a,让我们保持简单的开始,我说输入是一个双精度数,它是一个标量。同样的,对于b,它是一个双精度,它是一个标量。一旦我完成了这些,我可以点击构建选项卡,我可以将输出类型更改为C静态库,首先我将生成C代码。我不会编译它。然后我可以点击构建。当我点击构建,MATLAB Coder现在将继续从我的MATLAB函数生成C代码。 If there are any errors or any troubles that it runs into, it will give me some warnings here. But it was successful, and then I can click on View Report. And what you'll see here is that here is my main entry point function, so I've got returns a double and simply multiplies a and b.
现在,我将在后面的演示中讨论它生成的所有这些函数或C语言和头文件,但现在让我们回到这个函数,看看我们如何实际地改变输入定义并生成不同的代码。如果我回到这里的overview选项卡,我可以改变这里的输入定义来生成不同的乘法。另外,我还可以使用某种测试平台自动定义这些大小和类型。如果我回到MATLAB,你会看到我已经创建了一个测试平台,它调用了mymult。你可以在这个测试台中看到,它将大小为3 × 4的双输入与大小为4 × 5的双输入相乘,它只是创建了一些随机矩阵来做这个。我可以使用这个测试平台在MATLAB Coder项目中自动定义我的类型。如果我回到这里,我能做的就是点击自动定义类型,我所需要做的就是指定那个测试台,我的测试台。如果我运行这个,MATLAB Coder会做的就是运行这个测试平台,从这个测试平台,它会找出实际输入的数据类型和大小。
我可以点击使用这些类型,你可以看到MATLAB Coder已经自动更新了我的输入定义。现在,当我单击Build时,我可以生成一个矩阵乘法,而不是标量乘法。如果我单击View Report,现在你会看到我的主入口点函数有一个嵌套的四个循环,它执行我在测试台上定义的矩阵乘法。当您手动将MATLAB转换为C时,会遇到哪些挑战?因为这是大多数人在没有MATLAB Coder的情况下会做的事情。所以他们会有人在MATLAB中做算法设计,然后他们会把它重新编码成C或c++进行部署,不管他们想要原始的C代码还是一些库或可执行文件。但是在这里,通常你需要验证C代码和MATLAB代码的行为方式是一样的,因为通常你的MATLAB代码是你的黄金参考,你想要确保手动实现的行为和原始的MATLAB算法是一样的。
这里有一个验证的步骤。不仅如此,通常会发生的是算法会改变。这可能是为了改进算法,也可能是为了改变设计的要求。但不可避免的是,算法会改变,这意味着您必须在手动编写的C代码中进行适当的更改。这种迭代可以在任何设计周期中发生很多很多次。所以我们这里的问题是我们实际上有一个独立的功能和实现规范。我们需要同时管理MATLAB代码和C代码。因此,在开发过程中修改需求变得非常困难,因为这意味着任何需求变更都会导致更多的迭代。而且要使参考MATLAB代码与C代码保持同步是非常困难的。除此之外,每个人都是人,因此您可能还会引入一些需要调试和修复的手动编码错误。
所以整个过程或者整个手工过程都是非常耗时和昂贵的。在这样做时,有一些实现方面的考虑。例如,还记得我们最初的函数吗,它是一个简单的乘法。这里,我们把函数写成a = foo (b和c)其中a = b乘以c,怎么实现呢?它可以是一个元素乘以一个元素,它可以是一个点积,它可以是一个矩阵乘以。或者,数据类型可以不同。它们可以是逻辑的,可以是整数,可以是任何东西。所以当在C语言中实现这个时,它不仅仅是简单地说返回b乘以C。这个简单的乘法可能是完全不同的,这取决于最初的算法设计者打算如何将它集成到一些更大的代码库中。这就是多态性的一个例子。您还必须考虑如何处理内存分配。
MATLAB非常易于使用,因为它只会在需要时动态分配内存。但是当您实现C代码时,您需要决定是使用静态内存分配还是动态内存分配,或者是两者的组合。关于多态性,你处理的是矩阵还是数组,因为这会影响到实现。你在MATLAB代码中是否使用定点数据类型,如何在C语言中将其转换为整数类型?所以最终的结果是一个基本的MATLAB函数,像这样的卡尔曼滤波器可以是五行MATLAB代码,因为它基本上是很多矩阵运算。这可能会变成数百行C代码。当我们使用MATLAB到C语言的自动转换时,我们的想法是算法设计和代码生成都是在MATLAB中完成的。所以我们最初的算法设计者可以简单地生成一个MEX下降。现在,如果你不熟悉什么是MEX文件,MEX文件基本上是编译过的C代码。它就像一个可以直接从MATLAB中调用的库。
MEX文件确实有一个C代码的包装器,它允许你将MATLAB数据类型传递给C,当你从MATLAB生成C代码时,创建MEX文件的想法是,这样我们就可以验证生成的C代码的行为与MATLAB相同。一旦我们验证了这段C代码的行为与MATLAB相同,我们就可以继续创建库或可执行文件。或者,我们可以继续迭代算法。但由于现在不需要人工翻译,整个过程变得更加流畅。所以有了MATLAB Coder,你基本上可以在MATLAB中维护一个设计并且更快地转换到C语言。更快地使用C语言的好处是,你实际上可以对你的原始算法进行更多的测试,并花时间在MATLAB中改进算法,而不是实现一些可能仅仅因为时间限制而无法实现的东西。
那么,让我们来看看从MATLAB生成C代码的三步工作流。在这里。你可以看到第一步是你需要准备你的MATLAB算法来生成代码。所以当你准备你的MATLAB算法时,你需要决定你要做什么实现选择,但也要确保你的MATLAB算法使用了受支持的语言特性。所以当从MATLAB生成C代码时,我们不支持所有的MATLAB语言,但我们支持大部分的MATLAB语言。我稍后会讲到这个。所以一旦你准备好了你的代码,你就需要测试你的MATLAB代码是兼容的。您需要验证您拥有的MATLAB代码实际上可以生成您的代码,然后您可以对此代码执行迭代以进行优化,最后,您可以生成MEX文件以根据原始测试台架验证c代码实现。一旦您完成了这个验证,您就可以获取原始C代码、NCC代码,并将其作为源代码、库或可执行文件实现。这要看你怎么用了。
为了给你们一个将MATLAB算法转换成C语言的工作流程的例子,我现在要讲一个更广泛的例子使用牛顿-拉弗森算法。让我们再次跳出MATLAB。让我们关闭一些。让我们打开牛顿-拉弗森算法。我们要做的就是用牛顿搜索法来求一个数的n次方根。在看代码之前,我先给大家举个例子。这是我的试验台。所以这里的NRT函数等于10的四次方根。好的,我想要10的四次方根,我要指定一个公差值。之所以用这个是因为算法是迭代的,所以我想告诉它什么时候停止。 So if I was to execute this, you'll see that the fourth root of 10 using this algorithm is about 1.7783. So let's take a look at the algorithm itself.
你可以看到它返回n次根。它还返回迭代的次数,以及它在每次迭代中计算的值的历史。它的输入数是可变的。如果我向下滚动,我可以让它最大化,我们可以在算法中看到如果输入指定的值小于0,我们就会返回0。或者,如果用户指定的值大于0,那么我们将执行牛顿搜索算法。如果我们看一下,牛顿搜索算法就像你在这里看到的,基本上是一个while循环,它会循环并迭代找到第n个根。您将注意到的一件事是,它将在50次迭代后自动停止。再往下,你会在这里看到一个子函数,这个子函数是用来计算导数的。我们得到了一个主函数。我们有一个子函数。
这将返回n次根以及历史,如果我们回到主调用函数,当我们得到历史时,我们也可以计算出达到最终值所需的迭代次数。这就是这个函数的作用。那么让我们用MATLAB Coder从这个函数生成C代码。但我要做的第一件事是使用所谓的代码准备工具。因为也许我想做的是先感受一下我要花多长时间来完成代码生成。我能做的就是选择我想要为之生成代码的函数。我右键点击它,你可以看到下面有一个选项,Check Code Generation Readiness。当我选择它时,它会告诉我从这个函数生成C代码有多困难。你可以看到这里代码生成准备得分是5,所以MATLAB认为它可能只需要很小的改变就能让这个MATLAB代码生成C代码。
它甚至按代码结构进行了分解,因此您可以看到内部函数生成C代码有多么困难。在目前的状态下,它看起来很不错。我们再打开我们的Coder应用。这次,我将把我们的项目命名为NRT。和上次一样。我们将添加入口点文件NRT.m。然后,我将使用我的测试平台自动定义类型,而不是指定输入是什么。这就是我的测试台。让我们运行它,Coder应用程序可以找出输入数据类型是什么。在这种情况下,很简单。 They're just three scalar doubles. So the first thing I need to do then when I generate C code if you remember our three-step workflow is to check that it actually can generate C code. And we can do this by trying to create a MEX function. So if I just try to create a MEX function to start with, if I click on build, this will try to generate C code and then turn it into a MEX function for verification.
现在,如果它遇到错误,它会告诉我。你可以看到它实际上出现了错误。我要打开错误报告,你可以看到这里出现的错误列表。如果我选择第一个,你会看到这一行被高亮了。它说当我将鼠标悬停在这里时,有一个未定义的函数变量h。对局部变量的第一次赋值将决定它的类。它告诉我的是当我第一次在这个函数中赋值一个局部变量时,它会定义为class。通过这样说,我实际上已经把它定义为一个标量。问题是,如果你看到另一个小红色下划线,这是在一个循环中,我们实际上在做的是我们在这个循环中增加h的大小。这就是我们得到这个错误消息的原因。但是我知道迭代次数最多是50次,所以我能做的是预先分配内存给这个值h,它是一个历史记录,希望这能解决我们的问题。
如果我回到MATLAB,使用牛顿搜索算法,这就是我们的问题所在。我在这里加一条线,h = 0的50个元素的向量。我们先预分配内存,然后保存。看看这个是否能帮到我们。如果我再次打开我的代码生成应用程序并尝试再次构建它-好,现在代码生成成功了。我不会看报告,因为它实际上是一个MEX函数。我们可以看一下,但它有很多包装器代码,这让它看起来有点乱。在任何情况下,生成MEX函数只是为了验证。所以我们能做的就是用MEX函数来验证生成的C代码和原始的MATLAB算法是一样的。为了做到这一点,我们在这里有这个验证部分。 You can see we'll use our original test file test bench.
重要的是这个复选框。它说它将把入口点调用重定向到MEX函数。这意味着当我们运行我们的测试平台时,它不会调用NRT,也就是我们的MATLAB函数,它会调用MEX函数,也就是编译后的C代码。我们的MEX函数在这里被调用,NRT下划线MEX。因此,这将做的是将任何呼叫重定向到NRT到NRT下划线MEX。如果我点击运行,它会运行测试平台,但当它运行测试平台时,它会调用我们的MEX函数。如果你看一下命令窗口,你会发现我们得到了相同的答案。这就是我们的验证我们生成的C代码和MATLAB代码的行为是一样的。现在,显然你可以在这里建立一个更复杂的测试平台,但这只是这个例子的一个简化。现在我们已经验证了C代码的行为和MATLAB一样,我们要做的是,我们现在只生成没有MEX包装器的原始C代码。 So let me just change our output time to C static library, and we'll just generate the code and click on Build.
现在,这将为这个函数创建原始的NCC代码。所以我们可以看一下报告。我们来看看这份报告。首先我们可以看到上面有一个辅助函数。看起来它在处理一些无限大的数,一些非有限的数。如果我继续向下滚动,您将看到这里是NRT的主要入口点函数,然后是它为实现该特定函数而生成的所有C代码。我们总共有175行C代码。我想我还要指出一件事如果你回到MATLAB代码,在MATLAB代码中它也会给出原始MATLAB代码的报告。当你将鼠标悬停在这些变量上时,你可以看到它给出了关于大小,类和复杂性的信息。当您试图调试代码时,这有时会很有用。 I just thought I'd point that out.
但如果我们回到C代码,你会注意到的第一件事是我们生成了很多函数。我们为什么要生成这么多函数呢?首先我们看到的是初始化函数和终止函数。现在,无论何时生成C代码,我们都会生成一个初始化函数和一个终止函数。调用initialize是一个很好的实践。在这里,初始化函数。在你调用你的主入口点函数之前当你把它集成到你的目标代码中,然后接着调用终止函数。显然在初始化函数中,它会初始化状态。也许你有一些持久的记忆。在调用主入口点函数之前这样做是很重要的。 Terminate may be used. If you've opened up maybe some file pointers, it will close down some file pointers. Things like that. But the important thing to remember is always call the initialize function first before calling the entry point function, and then call the terminate to finish if required. Now, there may not always be something inside the initialize functions and the terminate functions, but it's a good practice to simply keep them in your target code because if you happen to change your algorithm and we do generate some code which happens to be put inside that initialize function, after you've generated the code there is no need to change the code in your target environment.
你可以让它保持原样,因为它已经调用了初始化,它会调用你的入口点,它会调用你的终止。这里的其他函数是这些getimf C, getnan,这些都是处理非有限数的代码。我们这样做是因为MATLAB可以处理非有限数,如果你碰巧生成了需要处理这个的代码,那么你就可以开始了。但如果你不需要这种支持,你也可以关闭它。要做到这一点,我们要做的是进入更多设置,在更多设置中,我有一些选项。其中一个选项是Speed,你可以看到我实际上可以关闭对非有限数字的支持。如果我这样做并点击关闭,然后重新构建它,MATLAB将在没有非有限数支持的情况下重新构建代码。现在,如果您的代码确实需要支持非有限数,它将在这里抛出一个错误,并告诉您需要再次选中该方框。然而,在这种情况下,我们不需要它。所以当我看报告时,你可以看到现在我们有更少的C和头文件,如果我们向下滚动,你会注意到我们在入口点函数上面的原始帮助文件不再在那里。
看起来这个辅助函数就是用来处理非有限数的。如果我们向下滚动,这就是我们所有的代码。我们现在的代码行数从175行减少到115行,仅仅是因为去掉了对非有限数量的支持。我想指出的另一件事是你们会看到这里实际上有评论。你可以看到这里有一个注释,在50次迭代结束后,这里有一个注释,决定迭代。现在,这些注释直接来自于MATLAB代码,来自MATLAB代码的注释将帮助你从MATLAB代码追踪到C代码。它并不总是有效,因为在生成C代码时执行了一些优化。我们不仅仅做一行对一行的翻译。否则,效率会非常低。但是,它会帮助你在某种程度上追踪原始的MATLAB代码在参考生成的C代码时的位置。
但这里我想指出的是在这个确定迭代之后,我们可以再次看到一堆C代码看起来像是在执行while循环来检查一些东西。如果我们回到这里的MATLAB代码,你会看到这是我们的确定迭代,但在那之后我们只有一行MATLAB代码。我们有一行MATLAB代码,却生成了多行C代码。为什么呢?如果我们看一下这段MATLAB代码,它要做的是确定计算n次根需要多少次迭代。虽然只有一行MATLAB代码就能完成任务,但这是一种极其低效的计算迭代次数的方法。这就是我想如果你在MATLAB Coder中输入垃圾,你很可能会得到垃圾。所以在构建算法时你需要更聪明这样才能生成漂亮的C代码。
这里,这行代码是不必要的。我们可以将这一行代码替换为迭代等于历史的总和不等于零。我的意思是,这可能是另一种计算迭代次数的方法基于由历史不等于0返回的逻辑向量。如果我保存这个,然后重新生成C代码并查看报告,我将向下滚动到代码的那个区域,你可以看到这里我们已经折叠了很多代码。之前大概是115行。现在,我们只需要对MATLAB代码做一个很小的改变就可以减少到92,这使得算法更有效。你甚至可以更进一步。假设你想更详细地计算迭代次数,甚至写得有点像C代码。这里我可以说迭代等于0然后做一个for循环循环JJ等于1到历史长度。我们可以说迭代等于迭代加1。 Else, we will break.
我需要检查一下。如果JJ的历史不等于零,那么我们实际上会增加迭代的次数。这更像是C档的方式来做同样的事情。如果我保存它,回到我的应用,重建代码,看一下报告,现在我们只剩下大约90行代码。我想在这里我想指出的一点是即使在我的MATLAB代码中我写了一个包含if语句的for循环,MATLAB Coder足够聪明,它会发现,与其用if语句做for循环,不如把它改成带有几个条件的while循环。这是MATLAB Coder可以为你做的一个优化的例子。好,现在我们只剩90行代码了。我想看的另一个东西是这个入口点函数。
当我们看入口点函数时,我们有了所有的输入。然后我们得到了输出,也就是n次方根,也就是迭代次数。但如果我们看一下这里,历史看起来有点奇怪。输出历史。我本来期望一个50元素的向量,输出我们的历史值,但相反,我看到的是这个历史数据变量和历史大小。每当你看到这两个,我猜这两个变量,像这样的参数,这表明你在代码中有一些变量大小。MATLAB所做的就是返回一些数据,它也返回你在预分配内存中的位置,因为它并不总是知道,因为它的大小可以变化。现在,如果我们回到我们的代码,我们可以弄清楚为什么会这样。它发生在主函数NRT中。虽然牛顿搜索算法会返回一个50元素的历史向量,因为记得在牛顿搜索算法中,我们预先分配了50个元素,问题是这个NRT函数当它的值小于0时,它会给历史分配一个0的值这意味着MATLAB Coder会看到这个,说,历史可以是一个标量,也可以是一个50元素的向量。
这就是可变大小的用途。为了解决这个问题,已知历史为0,我们可以把它定义为一个0的向量。这意味着历史将永远是一个50个元素的向量。保存一下,回到MATLAB Coder应用重新构建。重建后,我们就可以看一下报告了。你在这里看到的是这比我们预期的要多。我们的输出只是一个50个元素的向量,这也清理了代码体的一部分。现在我们只剩下82行代码了,开始的时候是170行,现在我们只有82行代码,只做了一些小修改。你会注意到的另一件事是牛顿搜索算法实际上已经内联到这个入口点函数中。
在MATLAB的主NRT中,我们有这个else语句如果输入大于0,我们调用牛顿搜索算法。但在实际的C代码中,你会看到牛顿搜索算法,这是else语句牛顿搜索算法已经内联到代码中了。现在,假设你不想让它内联。现在,如果Coder认为只有几行代码它会内联它会让它更有效率。但是如果你愿意,你可以强制关闭它。我们可以这样做,在我的牛顿搜索算法中,在顶部这里,我可以说,看,coderdotinline never。永远不要内联这段代码。如果我这样做了,我可以回到代码生成,重建它,当我重建它的时候,你会在这个报告中看到在我的入口点函数中在我的else语句之后,我实际上调用了牛顿搜索算法。如果我点击它,你可以看到这实际上是一个单独的C代码,它有自己的头文件。
好了,让我们把它关掉。希望这能让你更好地理解如何使用这个工作流从MATLAB代码中生成C代码。我之前提到过,MATLAB Coder只支持MATLAB语言的一个子集。我们不能为所有的MATLAB语言生成C代码。这个小维恩图告诉你我们支持什么。淡蓝色里面的所有东西都是支持的,我们支持可变大小的数据,我们支持定点持久化类,全局变量,结构体,但你会注意到外面有些东西是我们不支持的。所以可视化是我们不支持的。我们不会为绘图或冲浪之类的东西生成C代码。如果你使用本地Java调用,我们不会生成C代码,如果你使用单元格数组,我们还不支持它们。有一些东西是我们不支持的,但这些都有完整的文档。 In terms of features and functions, you can see here here, here's a table.
支持MATLAB的大部分特性和函数。支持所有不同的矩阵和数组,大多数数据类型以及编程结构。在函数方面,我们支持MATLAB的大部分基函数。我们支持大量的信号处理工具箱、通信系统工具箱和计算机视觉系统工具箱,以及统计工具箱中的一些功能。但是,这些都是有充分记录的。现在我已经向您展示了如何生成C代码,让我们看一下一些用例。所以我提到的用例首先是将算法与定制软件集成,加速MATLAB算法,在pc上原型算法以及在嵌入式进程上实现。让我们看一个代码集成的例子。在这里,我将为缩放算法生成一些代码,然后将其集成到Visual Studio的父项目中。
我们回到MATLAB。我要关掉一些。让我打开我要讲的算法。为了向你们展示它是怎么做的首先,让我来运行这个小模拟。在左边,我们有一个被稳定的像。这里有一个缩放因子。我要做的是放大稳定的图像。你可以看到,当我移动滑动条时,我可以放大它。同样地,我可以把它变小。现在,我没有为整个应用程序生成代码。 It's just for the algorithm which does the zoom. So if I stop this, we'll take a look with this algorithm. The algorithm is called image process. It takes an input image as well as some factor. Obviously, if the factor is greater than one it will magnify, if the factor is less than one it will shrink it. So you can think if you're magnifying an image—let's just say you've made an image twice as big—you'd need to somehow interpolate between each of the pixels to make that image bigger.
这就是这个算法的作用。现在,我想这个算法做什么并不重要。我们只是想证明我们可以把它整合到另一个系统中。为了节省时间,我先打开一个已有的项目。现在,你会看到一件有趣的事情,我把输入图像定义为double类型和冒号无穷乘以冒号无穷。这意味着输入维数是无穷大到无穷大的任何值,这意味着我不知道输入图像有多大,这意味着我必须使用动态内存分配。如果我点击构建,在更多设置下面你会看到我可以在内存下面设置它。可以看到,我可以启用可变大小支持,这实际上是默认开启的,然后还启用动态内存分配。所以你可以控制它。如果你想使用静态内存分配,你显然可以取消选择,但这样我们就不能为这个项目生成代码了。
我点击构建。这个程序要做的就是生成实现那个算法所需的C代码。但是由于我们使用的是动态内存分配,它还将包括一些其他函数。所以你可以看到,如果我们看一下图像处理的入口点函数,你会看到输入图像是这种类型的EMX数组下划线实下划线t。现在,这是因为输入是未知的,MATLAB Coder需要做的是创建它自己的数据结构来处理动态内存分配。现在,因为我们正在创建自己的数据结构,所以我们还创建了一个API来允许您使用这些自定义数据结构。如果我看这个图像过程下划线EMX api。cpp,这里你会看到这些函数来帮助你处理这个自定义数据结构。我猜你们会看到EMX create real,这就是如何创建这种数据结构的东西。你可以看到它基本上被指定为一些数据以及指定的大小。
同样地,因为我们正在创建我们自己的数据结构,我们在这里也有一个效用函数,一旦你完成它,它就会被破坏。现在我们生成了代码,让我们将其集成到另一个项目中。再一次,让我打开一个现有的项目。让我们打开这个解。这个会在Visual Studio中打开。不好意思,我把它放到MATLAB外面。这里的想法是,我们刚刚创建了一些通用的NCC代码,我们想把它集成到另一个用C或c++编写的环境中。所以我在这里打开一个现有的解决方案,这个解决方案实际上做的是它从网络摄像头获取输入流,然后我们从网络摄像头获得的数据将输入到我们的缩放算法中,然后显示它。这是我的文件。让我们看一下父项目中的main。cpp文件。
我们将使用一些开放CV实用程序来与网络摄像头对话,并在我们的PC上显示它。但如果你翻看这个主文件,主要要看的是这里我们使用了效用函数,这个EMX create_ real _ t,我们创建了我们需要使用的自定义数据结构。这里是我们运行初始化函数的地方,这里是我们创建输出图像的地方因为我们有一些未知大小的输入图像和一些未知大小的输出图像。如果我再往下滚动一点,这里有一个for循环它只是从网络摄像头数据中填充数据,这是由MATLAB Coder对代码生成器的调用。这是图像处理函数。一旦我们处理了它,我们填充输出数组。我们来构建解。好了,现在已经建好了。我把它关闭,然后进入发布目录。
你可以看到,这是我刚刚创建的视频摄像头。EXE,我要做的是在MATLAB中调用它。我将使用我的Bang Operator视频网络摄像头,并将它放大1.5。现在,你将看到的是我的笔记本电脑上有一个网络摄像头,所以我只是移动它。我现在在办公。这是一把椅子,你可以看到这是放大的图像放大了1.5倍。这是我们从MATLAB Coder中生成的C代码运行在其他父Visual Studio项目中。好的,我们可以做的另一件事是我们可以将现有的C代码集成到代码生成过程中。如果你有一些现有的C代码而不是用MATLAB Coder生成的C代码,这是很有用的,这可能是因为你有一些优化的实现你正在使用的函数。
有很多方法可以做到这一点,我将向你们展示一个简单的方法,使用一个叫做Coder的东西。target和code . cul。让我们回到MATLAB,看一下这个自定义卷积函数,我要对它积分。所以我在这里做的是一个卷积。这是我的试验台。我可以运行这个。它有两个输入向量,然后我可以做卷积。这就是它所做的。但在实际的函数本身,这里它被称为自定义com下划线ceval。这就是这个函数。我构建这个的方式允许我调用外部C代码。 So by the way, the actual C code I'm going to use is here. It's called custom underscore com dot c. It takes the input signal as well as the signal length, the input kernel as well as the kernel length, and then returns the output And then what we've got here is just some C code for a convolution. So in the MATLAB code itself, when I call this MATLAB function it branches off into two separate paths depending on what MATLAB is doing.
当我使用编码器时。目标,我可以说,如果我在MATLAB中执行如果我给它这个输入,它会测试它是否在MATLAB中运行。如果这个函数在MATLAB中运行,我将调用MATLAB的原生卷积函数,因为我需要因为我需要它在MATLAB中运行以进行仿真。否则,如果我不在MATLAB中运行,我会生成代码。如果我在生成代码,我想做的是调用一个外部C函数,叫做custom conv,当我调用这个外部C函数时,我将传入我的输入x,但我将通过只读引用传递它。我将传递x的长度,我也将传递内核,通过引用传递,以及长度,然后我将返回y的输出,所以当你调用C函数时,这处理了数据类型转换的类型。所以当我为这个函数生成C代码时,它不会使用这条路径。它将使用这条路径并集成一些外部C代码。
我们来证明一下。我们回到MATLAB -哦,我已经在MATLAB中了但我要打开一个现有的项目,这里我有我的入口点函数。现在,我要做的唯一一件事是在更多设置中当我生成C代码时,你可以看到在这里的自定义代码中,我要选择tab,我可以指定自定义C代码所在的位置以及包含目录一旦我指定了,我就可以构建它。当我构建它时,你会注意到当你看C代码时它并没有从com函数生成C代码。相反,它所做的是直接调用我之前给你们看过的那个自定义的下划线com函数。如果我点击它,它就在这里。所以,我们在这里所做的是我们已经能够将一些外部C代码集成到代码生成过程中,这在你有优化例程时很有用。如果你想加速MATLAB算法呢?
我总是说的第一件事是,与其看C代码生成,不如先看更好的算法。例如,如果你在做矩阵逆,也许你可以用不同的方法来解决同一个问题。看看使用优化库的不同更有效的实现。或者,考虑使用额外的计算资源——matlab可以通过并行计算工具箱访问额外的进程和gpu。因此,在学习C代码生成之前,还有许多其他选项可以参考。当涉及到加速算法时,你应该做的第一件事就是优化你的MATLAB代码。研究并行计算选项。如果您正在处理DSP系统和通信,请查看系统对象。尝试使用MEX实现您自己的自定义代码,但如果您不想编写自己的C代码,那么您可以在这里尝试MATLAB C代码生成。
这里我要用的例子是再次运行牛顿-拉弗森算法并将我们生成的MEX文件与原始的MATLAB代码进行比较。如果我回到MATLAB,我在这里写了一个小脚本。我将运行1000次迭代。现在,我要做的第一件事是为MATLAB代码计时,你可以看到在这个MATLAB代码中,我所做的就是调用NRT算法,我用一个勾来包装它,并对执行进行计时。让我们在MATLAB中运行几次。在MATLAB中,平均需要0.003秒。现在,让我们计算MEX代码的时间。所以我之前所做的是我已经将NRT算法转换为一个MEX文件,并且我使用了我在这个演示之前展示的示例的最后迭代。所以我要做同样的事情,但是我要运行1000次MEX下跌,看看平均时间。好,当我运行几次时,我看到它有一些变化。 I mean, we're looking at small numbers here, but we're looking at about 2.7, 2.8 by 10 to the negative five seconds to run the same algorithm.
如果我看一下加速因子,所以在这个特殊的情况下,我们看到的是大约11的加速因子,但这并不能代表你将得到什么。这取决于你写的MATLAB代码,但你可以加快速度。加速因子会有所不同,我猜你经常会看到加速的地方是当你处理通信和信号处理时,几乎总是当你处理定点算法时,还有当你最初的MATLAB代码有很多for循环和状态时,你无法将你的算法向量化。这就是你经常看到加速的地方。当你在做很多线性代数比如矩阵计算的时候,你不会看到速度的提高,因为在MATLAB中,它们已经非常快了,因为它们调用IPP和BLAS库,而且它们已经是多线程计算了。所以当你尝试MEX这种类型的计算时,你不会看到太多的速度提升。
它们已经在MATLAB中优化过了。如果您有并行计算工具箱,我们还支持多核的MEX功能。因此,如果您正在使用parfor循环,例如,当您生成MEX函数时,它也将使用开放mp技术运行多核。但这要求你有一个支持open mp的编译器。同样地,如果您只是生成独立的C代码,而不是一个MEX文件,它也将使用这个open mp。但同样,你需要一个支持open mp的编译器。我想人们可能想做的另一件事是在PC上创建算法原型。在这里可以使用生成的代码并提供一个主点C文件作为入口点,让MATLAB不仅生成独立的C代码,还将其编译为XE。让我们来看看其中一个例子。我将再次运行这个视频稳定演示。 So similar to the one you saw before when you saw the zoom.
这使用了一大堆计算机视觉系统工具箱系统对象。如果我运行这个而不是看代码,你会看到这个视频稳定。我之前提到过,MATLAB Coder不支持可视化的可移植C代码生成。现在,这个特殊的计算机视觉系统工具箱系统对象是个例外。我们支持可视化。我们将支持这个窗口的代码生成,这主要是因为它不是一个独立的C语言,它实际上是一个C语言库,它将包括——而且它将依赖于平台。让我们看看我们能做什么。回到文件夹,打开这个项目,点击build。现在,我已经将输出类型指定为c可执行文件。我必须指定一个主函数。
如果我看一下主函数,它是这样的。我必须包括头文件。然后在主函数中,它很简单。我要做的就是调用初始化函数,调用入口点函数,然后调用终止函数,这就是我要做的。让我们回到项目中进行构建。当我点击构建,它会从MATLAB代码中生成C代码,一旦完成它就会编译main。C文件。完成后,它会在当前目录下给我一个可执行文件,我可以运行它。还需要在前面展示的自定义代码部分下的更多设置选项卡中指定main.C文件的位置。代码生成就完成了。回到我的当前文件夹,你会看到一个视频稳定器。exc。 Let me open that outside MATLAB. And there you have that same little stabilization application. If you want to work with Simulink, what we have is a MATLAB function block in Simulink. So in Simulink, we've got this MATLAB function block.
你可以合并MATLAB代码,当Simulink运行时,Simulink会把这些MATLAB代码转换成C代码并把它转换成一个库,这样它在Simulink世界里运行得更快。记得当我们有这两个选项时,我在一开始给你们展示了这两个选项,我们讨论的是MATLAB Coder。好,现在让我简单地讨论一下MATLAB编译器。基本上,MATLAB编译器还允许你共享应用程序允许你创建exe, dll,你还可以创建Excel插件,和Java类。MATLAB编译器的优点是它实际上支持几乎所有的MATLAB语言,大部分的MATLAB语言和工具箱。这意味着你根本不需要做那么多改变。但正如我之前所说,缺点是它还需要MATLAB Compiler运行时引擎,虽然这是一种免版税的部署,但它确实意味着这些编译过的应用程序只能运行在可以运行MATLAB的平台上,因为MATLAB Compiler不提供通用的NCC代码。
它所做的就是围绕你的代码编写一个包装器并将其编译成一个可以从MCR调用的形式。所以如果你在考虑你应该选择什么,你必须考虑你的要求是什么。因此,对于MATLAB编码器,它将为您提供可移植和可读的C源代码。MATLAB编译器会给你可执行文件或者一些共享库。MATLAB Coder只支持MATLAB语言的一个子集和一些工具箱。另一方面,MATLAB编译器几乎支持MATLAB的大部分功能和语言。MATLAB Coder不需要运行时。它只是通用的C代码,你可以拿走并编译,而MATLAB可执行文件和为MATLAB编译器创建的软件库将需要MATLAB编译器运行时。但这两种解决方案都是免费的。
一旦生成了代码或库,就可以对它们做任何想做的事情。我没有给你们一个定点的例子或者一个使用MATLAB Coder的嵌入式应用程序的例子,但是我有一个用户故事。这是来自VivaQuant,他们开发并验证了他们的嵌入式ECG感应设备。他们使用MATLAB以及我们的定点工具和MATLAB Coder将他们的算法实现到手臂皮层m系列处理器上。你可以在这里看到结果他们实际上加速了300%的发展。他们实际上能够最小化他们的功率和内存,并使用这个工作流程执行更严格的测试。让我们快速总结一下。我们在这里向您展示的是如何从算法开发转移到通用NCC代码,以及如何在两者之间快速来回切换,并对算法进行更改并自动生成C代码。这样做确实能加速你的发展。
如果你想想你的传统工作流程,通常你开发你的算法,你测试它,你手动把它转换成C语言,然后你做更多的测试和迭代,这个过程,会变得非常非常耗时。当您使用自动C代码生成时,可以节省大量时间,这些节省的时间可以用于改进算法和测试算法,以确保所部署的内容符合生产质量。这里的关键是——MATLAB提供了从浮点和定点MATLAB代码到C代码的直接路径。它适用于需要源代码且内存占用很小的应用程序,因此无法负担在后台运行MATLAB Compiler运行时的费用。自动生成可以帮助您加速设计迭代,并减少验证工作。有关更多信息,您可以访问我们的产品页面mathworks.com/products/matlab coder。2022世界杯八强谁会赢?从这里,您可以申请试用许可证。或者,您可以向Mathworks客户经理请求试用,也可以通过以下电子邮件地址或电话号码直接与我们联系。
您也可以从以下列表中选择一个网站:
如何获得最佳的网站性能
选择中国站点(中文或英文)以获得最佳站点性能。其他MathWorks国家站点没有针对您所在位置的访问进行优化。