Generating C Code from Your MATLAB Algorithms
I am pleased to introduce guest blogger Arvind Ananthan. Arvind is the Product Marketing Manager for MATLAB Coder and Fixed-Point Toolbox. His main focus in this post is to introduce basics of MATLAB Coder, talk about the workflow, its use cases, and show examples of generated C code.
Contents
A Brief History of MATLAB to C
In April, 2011, MathWorks introduced MATLAB Coder as a stand-alone product to generate C code from MATLAB code. This tool lets user generate readable, portable, and customizable C code from their MATLAB algorithms.
Many astute readers will notice that C code generation from MATLAB isn't really brand new - and that we've had this capability to generate C code from MATLAB for quite some time now. Yes, that's true - we've been incubating this technology for quite some time till we felt it was ready to debut as a stand alone tool. Here's a timeline of this technology over the past few years:
- 2004 - Introduced the Embedded MATLAB Function block in Simulink
- 2007 - Introduced the emlc function in Real-Time Workshop (now called Simulink Coder) to generate stand alone C from MATLAB
- 2011 - Released MATLAB Coder, the first stand-alone product from MathWorks to generate portable and readable C code from MATLAB code.
A Simple Example
Let me introduce the basics of using MATLAB Coder through a very simple example that multiplies two variables. Let's generate C code from the following MATLAB function that multiplies two inputs:
dbtype simpleProduct.m
1 function c = simpleProduct(a,b) %#codegen 2 c = a*b;
To generate C code from this function using MATLAB Coder, I first have to specify the size and data types of my inputs - I can do this through the MATLAB Coder UI (shown in the section below) where I specify my inputs as a [1x5] and [5x2] single precision matrices, and subsequently generate the following C code:
dbtype simpleProduct.c
1 #include "simpleProduct.h" 2 3 void simpleProduct(const real32_T a[5], const real32_T b[10], real32_T c[2]) 4 { 5 int32_T i0; 6 int32_T i1; 7 for (i0 = 0; i0 < 2; i0++) { 8 c[i0] = 0.0F; 9 for (i1 = 0; i1 < 5; i1++) { 10 c[i0] += a[i1] * b[i1 + 5 * i0]; 11 } 12 } 13 }
MATLAB is a polymorphic language which means a single function, such as simpleProduct, can accept input arguments of different size and data types and output correct results. This function can behave as a simple scalar product, a dot product, or a matrix multiplier depending on what inputs you pass.
Languages like C are said to be “stongly typed,” which requires you to create a different version of a function for every variation of input data size and types. Hence, when you write C code to implement simpleProduct, you have to know ahead of time the sizes and the data types of your inputs so you can implement the right variant.
While MATLAB Coder helps you move from the highly flexible world of MATLAB to a strongly typed world of C, you still have to specify all of the constraints C expects. You do this in MATLAB Coder by creating a MATLAB Coder project file (simpleProduct.prj in this case) where you can specify the various code generation parameters including the sizes and data types of your inputs as shown below.
You can also edit the default configuration parameters by clicking on the 'More Settings' link which appears on the 'Build' tab of this project UI .
In the example above, I turned off a few default options such as including comments in the generated code just so you can see how compact the code is. You not only get the option to generate comments in the resulting C code, but also inlcude the original MATLAB code as comments in the corresponding sections of the generated code. This helps with traceability of your C code to your original algorithms.
Detailed MATLAB Coder Worfklow
The simple example above quickly illustrates the process of generating code with MATLAB coder and shows how the resulting C code looks.
Naturally, your real-world functions are going to be much more involved and may run into hundreds or even thousands of lines of MATLAB Code. To help you handle that level of complexity, you would need an iterative process, like the 3-step workflow described here, that guides you through the task of code generation incrementally:
- Prepare: First, prepare your code to ensure that you can indeed generate code from your MATLAB algorithm. The convenience of MATLAB language doesn't map directly to the constrained behavior of C. You may have to re-write portions of your MATLAB code so it uses the MATLAB language features that support this mapping from MATLAB to C.
- Test & Verify: Next, you generate a MEX function to test that your preparation step is correct. If the tool is successful in generating a MEX function then you are ready to verify the results of this MEX function against the original MATLAB code. If not, you'd have to iterate on the previous step till you can successfully generate a MEX function.
- Generate: Finally, you generate C source code and further iterate upon your MATLAB code in order to control the look and feel or the performance of your C code.You can also generate an optimized MEX function by turning off certain memory integrity checks and debug options that could slow down its execution at this stage.
I'll now demonstrate this concept using a slightly more involved example. The following MATLAB code implements the Newton-Raphson numerical technique for computing the n-th root of a real valued number.
dbtype newtonSearchAlgorithm.m
1 function [x,h] = newtonSearchAlgorithm(b,n,tol) %#codegen 2 % Given, "a", this function finds the nth root of a 3 % number by finding where: x^n-a=0. 4 5 notDone = 1; 6 aNew = 0; %Refined Guess Initialization 7 a = 1; %Initial Guess 8 count = 0; 9 h(1)=a; 10 while notDone 11 count = count+1; 12 [curVal,slope] = fcnDerivative(a,b,n); %square 13 yint = curVal-slope*a; 14 aNew = -yint/slope; %The new guess 15 h(count)=aNew; 16 if (abs(aNew-a) < tol) %Break if it's converged 17 notDone = 0; 18 elseif count>49 %after 50 iterations, stop 19 notDone = 0; 20 aNew = 0; 21 else 22 a = aNew; 23 end 24 end 25 x = aNew; 26 27 function [f,df] = fcnDerivative(a,b,n) 28 % Our function is f=a^n-b and it's derivative is n*a^(n-1). 29 30 f = a^n-b; 31 df = n*a^(n-1);
Our first step towards generating C code from this file is to prepare it for code generation. For code generation, each variable inside your MATLAB code must be intialized, which means specifying its size and data type. In this case, I'll choose to initialize the variable h to a static maximum size, h = zeros(1,50), as it doesn't grow beyond a length of 50 inside the for loop.
You can invoke the code generation function using the GUI or the command line (through the codegen command). Without worrying about the details of this approach, let's look at the generated C code:
dbtype newtonSearchAlgorithmMLC.c
1 #include "newtonSearchAlgorithmMLC.h" 2 3 void newtonSearchAlgorithmMLC(real_T b, real_T n, real_T tol, real_T *x, real_T 4 h[50]) 5 { 6 int32_T notDone; 7 real_T a; 8 int32_T count; 9 real_T u1; 10 real_T slope; 11 notDone = 1; 12 *x = 0.0; 13 a = 1.0; 14 count = -1; 15 memset((void *)&h[0], 0, 50U * sizeof(real_T)); 16 h[0] = 1.0; 17 while (notDone != 0) { 18 count++; 19 u1 = n - 1.0; 20 u1 = pow(a, u1); 21 slope = n * u1; 22 u1 = pow(a, n); 23 *x = -((u1 - b) - slope * a) / slope; 24 h[count] = *x; 25 if (fabs(*x - a) < tol) { 26 notDone = 0; 27 } else if (count + 1 > 49) { 28 notDone = 0; 29 *x = 0.0; 30 } else { 31 a = *x; 32 } 33 } 34 }
The subfunction fcnDerivative is inlined in the generated C code. You can choose to not inline the code by putting this command coder.inline('never') in the MATLAB file.
Support for Dynamic Sizing
If you have variable in your MATLAB code that needs to vary its size during execution, you can choose to deal with this in three different ways in the generated C code:
- Static allocation with fixed maximum size: you can initialize variables to the maximum possible static size (like I did in the example above). In the generated code, memory is prealloacted to this size.
- Variable sizing with maximum size allocation: this option will declare the memory for the variable in the generated code to its maximum possible size, but you can dynamically grow or shrink the variable size within this allocated maximum.
- Variable sizing with dynamic memory allocation: this results in the generated code using malloc to allocate the memory for the variables that change in size during code execution.
The last two options can be enabled by turning on the variable-sizing feature in the configuration parameters. However, do remember that enabling this option also makes the resulting C code bigger in size - so if you can avoid it, you can get much more compact and possibly more efficient code.
Use Cases of MATLAB Coder
The primary use of MATLAB Coder is to enable algorithm developers and system engineers working in MATLAB to quickly generate readable and portable C code. The generated C code can be used in different ways supporting different workflows. Here are a few use cases of MATLAB Coder:
- Create standalone executables from your algorithms for prototyping on a PC
- Speed up your MATLAB algorithm code (or portions of it) by generating and executing the MEX functions
- Integrate your MATLAB algorithms as C source code or compiled library with your hand-written software
I won't be talking about creating standalone executables or creating libraries from MATLAB Coder in this blog; I'll, however, say a few words on generating MEX functions.
Generating MEX Functions for Simulation Acceleration
The MEX interface enables you to integrate C code into MATLAB. One common use of MEX is to speed up performance bottlenecks in MATLAB code by re-writing them in C and executing them back in MATLAB as MEX functions. MATLAB Coder can save you time and effort by automatically generating MEX functions from your MATLAB code (and allowing you to import legacy C code easily as well).
The speed-up you get through automatic MEX generation can vary quite a bit depending on the application. In some cases, you may not get any speed up at all, or possibly even a slow-down, as MATLAB language has gotten quite smart and efficient in computing many built-in functions by automatically taking advantage of processor specific routines (such as Intel Performance Primitives) and multithreading to utilize multiple cores.
A few guidelines that you can use to determine if your algorithm is a good candidate for speed-up with MEX are:
- your algorithm contains multiply nested for loops, and possibly handles state calculations (making each iteration dependent on the previous one)
- your MATLAB code is hard/impossible to vectorize
- most of the processing cycles in your algorithm are not spent on already optimized built-in functions such as fft or inv, etc.
- you don't rely heavily on toolbox functions (especially those unsupported for code generation) in your algorithms
In these cases, you can expect to see a speed-up. Let's look at a realistic example that illustrates this concept.
The example I chose here is one that my colleague Sarah Zaranek had highlighted in her guest blog on vectorization.
I modified the original MATLAB script from that article into a function as only functions are supported by MATLAB Coder. The other change that I made to the original script was to initialize the output variables subs and vals (as all variables in your code has to be defined/intialized once when used with MATLAB Coder). And by turning on the variable sizing feature (using dynamic memory allocation), I didn't have to preallocate it to a sufficiently large enough size to accomodate its growth, which would use more memory (often a lot) than needed.
The following statements create and use a MATLAB Coder configuration object with support for variable size arrays and dynamic memory allocation to generate a MEX function.
% Create a MEX configuration object cfg = coder.config('mex'); % Turn on dynamic memory allocation cfg.DynamicMemoryAllocation = 'AllVariableSizeArrays'; % Generate MEX function codegen -config cfg gridSpeedUpFcn -args {10} disp('MEX generation complete!')
Warning: There is no short path form of 'H:\Documents\LOREN\MyJob\Art of MATLAB' available. Short path names do not include embedded spaces. The short path name feature may be disabled in your operating system. Short path names are required for successful code generation, therefore it may not be possible to successfully complete the code generation process. To avoid this problem, enable short path name support in your operating system, or do not attempt code generation in paths containing spaces. MEX generation complete!
This generates a MEX function in the current folder. I'll use tic, and toc to estimate the execution times of the original MATLAB function and its MEX version.
I run each function multiple times inside a for loop and use only the last few runs for our computation time calculation so we can minimize the effects of initialization and caching.
MToc = zeros(1,30); MexToc = zeros(1,30); for j = 1:30 tic; gridSpeedUpFcn(10); MToc(j) = toc; end for j = 1:30 tic; gridSpeedUpFcn_mex(10); MexToc(j) = toc; end eff_M_time = mean(MToc(1,21:30)); eff_Mex_time = mean(MexToc(1,21:30)); disp(['MATLAB code execution time (with nx=10): ', num2str(eff_M_time)]) disp(['MEX code executing time (with nx=10): ', num2str(eff_Mex_time)]) disp([' Speed up factor: ', num2str(eff_M_time/eff_Mex_time)]);
MATLAB code execution time (with nx=10): 0.31652 MEX code executing time (with nx=10): 0.0027057 Speed up factor: 116.9806
The speed up we see in this example is more pronounced for larger values of nx (and would vary between different computers).
Vectorization and Code Generation
Sarah, in her blog article, explains quite nicely on how to vectorize this code using meshgrid and ndgrid to explode the variables in support of vectorization, and get similar levels of speed up.
Sarah paid a small price in code readability for her vectorization efforts, but a much larger price in memory consumption. This points to a common trade-off when vectorizing MATLAB code: execution time vs. memory consumption. The temporary variables which enable us to remove loops through matrix math are often very large, effectively limiting the number of iterations you can take before running out of memory. For instance, if we run her algorithm for a 1,000x1,000 grid instead of a 100x100 grid, we would create 9 double precision variables that are about 7.5 GB each!
Code generation can effectively deliver similar speed-up benefits (and with less effort in some cases), as it's trying to explicitly achieve the same for loop operations in C code that a vectorized code achieves. However, the downside for most users with the code generation approach is that it's available with a separate product (higher cost), and only MATLAB code that supports code generation can automatically be converted to MEX as well.
Customizing and Optimizing the Generated Code
Now with most of the basics of code generation out of the way, a few questions might be on your mind:
- "If I want the resulting code to look different, can I just edit the generated code?" You can certainly edit the generated code; as you have just seen in the examples shown here, the code is very readable. However, once you modify it, you lose the connectivity to the original MATLAB code that generated it.
- "How can I optimize the code, or customize its look and feel?" One easy way to modify the generated code is to change MATLAB Coder configuration parameters. Another way to customize the generated code is to directly edit the MATLAB code such as explicitly breaking up certain compact vector/matrix operations into for loops or other constructs you'd like to see in the generated code.
- "What about incorporating hand-optimized or processor specific intrinsics for certain portions of the code?" coder.ceval is a command in MATLAB Coder that let's you integrate custom C code into your MATLAB code for both simulation and code generation. Using this you can use hand-optimized or legacy C code in your algorithms. Embedded Coder is another product that adds many code optimization and customization capabilities to MATLAB Coder such as using code replacement technology to incorporate optimized C code. It also lets you to customize the look-and-feel of the code.
Learning More About MATLAB Coder
You can find more information about MATLAB Coder on the product page, which includes demos and recorded webinars. The product documentation is also an excellent resource to find answers to questions you might have when starting with this product.
Please feel free to share your thoughts and comments on topics discussed here in this posting here.
댓글
댓글을 남기려면 링크 를 클릭하여 MathWorks 계정에 로그인하거나 계정을 새로 만드십시오.