Main Content

Control Inlining to Fine-Tune Performance and Readability of Generated Code

Inlining is an optimization technique that replaces a function call with the body of that function. Inlining eliminates the overhead of a function call, which can improve speed. Inlining can also create opportunities to further optimize the generated C/C++ code.

However, depending on your application, too much code inlining can also have disadvantages:

  • Inlining can increase the size of the generated C/C++ code and reduce code readability. For example, suppose that you call a certain function foo many times in your source MATLAB® code. If the code generator always inlines foo, the generated code size increases because foo is inlined every time it is called.

  • When you inline a function, the variables defined in the body of the function are part of the scope of the calling function. Therefore, the space allocated to these variables is not released from the stack when the inlined function completes its execution. When you do not inline a function, the allocated stack space is released when the function returns.

By default, the code generator prioritizes speed when generating MEX functions and standalone code. However, when standalone code contains user-written functions that call MathWorks® functions or MathWorks functions that call user-written functions, the code generator prioritizes code readability. In these cases, except for very small functions, the code generator usually does not inline calls between MathWorks code and user-written code. This behavior can keep the generated code cleaner by preserving the separation between user-written code and MathWorks code. You can change these defaults to fine-tune the inlining settings and generate code that meets the speed, readability, and stack space requirements of your application.

When inlining instructions conflict, the code generator follows a hierarchy of inlining controls to determine whether to inline a function in the generated code:

  • Calling a function using coder.inlineCall or coder.nonInlineCall overrides all other inlining controls.

  • The coder.inline('always') or coder.inline('never') directive inside the body of a MATLAB function overrides global inlining settings and code configuration settings.

  • Global inlining settings override code configuration settings.

  • If no other inlining controls conflict, the code generator exhibits the inlining behavior dictated by code configuration settings.

Control Inlining of a Specific MATLAB Function at the Call Site

To control the inlining of a specific MATLAB function at the call site, call the function using coder.inlineCall or coder.nonInlineCall. These functions override all other inlining controls, including coder.inline directives in the body of the called function, global inlining settings, and code configuration settings.

Control Inlining of a Specific MATLAB Function Within the Function

To instruct the code generator to either always or never inline a certain MATLAB function, use the coder.inline("always") or coder.inline("never") directives inside the body of the function. To learn more about these directives, see coder.inline.

The coder.inline("always") or coder.inline("never") directive inside the body of a MATLAB function overrides global inlining settings and code configuration settings. Some MathWorks functions include a call to the coder.inline directive. This directive thus overrides your global inlining settings and code configuration settings. To force or prevent inlining of such a function, use coder.inlineCall or coder.nonInlineCall.

Control Inlining Globally

If you generate code at the command line using the codegen command, you can use the option -O enable:inline to instruct the code generator to inline all functions, or use the option -O disable:inline to instruct the code generator not to inline any functions. For individual functions, you can override this option by using the coder.inline directive in the body of the function or by calling the function using coder.inlineCall or coder.nonInlineCall.

If you generate code using codegen with the -O disable:inline or -O enable:inline option, these options override the code configuration settings InlineBetweenUserFunctions, InlineBetweenMathWorksFunctions, and InlineBetweenUserAndMathWorksFunctions.

Control Inlining Using Code Configuration Settings

Your speed and readability requirements for the code generated for user-written functions and the code generated for MathWorks functions might differ from the code generation defaults. You can use code configuration settings to control inlining behavior when MathWorks functions call other MathWorks functions, when user-written functions call other user-written functions, and when user-written functions call or are called by MathWorks functions. The settings you use for MEX function generation can differ from those you use for standalone code generation.

The table shows the settings corresponding to different inlining goals.

Inlining GoalCode Configuration Object PropertyMATLAB Coder™ App Parameter

Control inlining behavior at all call sites where a user-written function calls another user-written function

InlineBetweenUserFunctions

On the All Settings tab, Inline between user functions

Control inlining behavior at all call sites where a MathWorks function calls another MathWorks function InlineBetweenMathWorksFunctions

On the All Settings tab, Inline between MathWorks functions

Control inlining behavior at all call sites where user-written function calls a MathWorks function or a MathWorks function calls a user-written function

InlineBetweenUserAndMathWorksFunctionsOn the All Settings tab, Inline between user and MathWorks functions

Set these parameters depending on the needs of your application:

  • "Always" – Inline all functions.

  • "Speed" – Use internal heuristics to determine whether to inline a function. This setting prioritizes optimized code.

  • "Readability" – Prioritize readability by only inlining very small functions. This setting balances code modularity and speed to produce readable code without negatively impacting performance.

  • "Never" – Never inline functions. This setting can negatively impact the performance of the generated code.

Global inlining controls or inlining controls that apply to specific functions override code configuration settings.

Example Inlining Strategy

To balance the speed and readability of the generated code, you can instruct the code generator to perform these actions:

  • Preserve the modularity of the user-written code for better readability, even if doing so reduces the speed of the generated code. For this behavior, set InlineBetweenUserFunctions to "Readability".

  • Generate highly optimized code for MathWorks functions, even if doing so results in less readable code, because you are less likely to inspect this part of your code base. For this behavior, set InlineBetweenMathWorksFunctions to "Speed".

  • Preserve modularity between user-written code and MathWorks code. For this behavior, set InlineBetweenUserAndMathWorksFunctions to "Readability". Because only very small MathWorks functions are inlined when inlining is set to "Readability", the generated code looks similar to your MATLAB code.

Example: Control Inlining Between User-Written Functions and MathWorks Functions

This example shows how to control inlining behavior at all call sites where a user-written function calls a MathWorks function.

Define a Function That Calls a MathWorks Function

Define a MATLAB function named useBessely that accepts a double array x as input, processes the input array by using the bessely function, and returns an array that has the same type and size as x.

type useBessely.m
function out = useBessely(x)
out = x + bessely(3,x);
end

Generate Standalone Code with Default Inlining Settings

Define a code configuration object named cfg to generate source code for a static C++ library. By default, the code generator optimizes readability when a user-written function calls or is called by a MathWorks function in standalone code. To maintain code readability, the code generator generally does not inline MathWorks functions called by user-written functions.

cfg = coder.config("lib");
cfg.TargetLang = "C++";
cfg.GenCodeOnly = true;

Generate code for the useBessely function by using the cfg object and specify the input as a 1-by-100 double type. Generate a report using the -report option, and use the -d option to create a folder readable_version for the generated code.

codegen -config cfg useBessely -args {zeros(1,100)} -report -d readable_version
Code generation successful: To view the report, open('readable_version/html/report.mldatx')

Inspect the generated C++ code in the file useBessely.cpp. Observe that the C++ function useBessely calls another C++ function coder::bessely that contains the code generated for the MathWorks function bessely. This behavior keeps the code generated from the MATLAB function you wrote separate from the code generated from the MathWorks function.

type(fullfile("readable_version","useBessely.cpp"))
//
// File: useBessely.cpp
//
// MATLAB Coder version            : 24.2
// C/C++ source code generated on  : 05-Sep-2024 13:48:58
//

// Include Files
#include "useBessely.h"
#include "bessely.h"
#include "rt_nonfinite.h"

// Function Definitions
//
// Arguments    : const double x[100]
//                creal_T out[100]
// Return Type  : void
//
void useBessely(const double x[100], creal_T out[100])
{
  coder::bessely(x, out);
  for (int i{0}; i < 100; i++) {
    out[i].re += x[i];
  }
}

//
// File trailer for useBessely.cpp
//
// [EOF]
//

Generate Code with Modified Inlining Settings

To instruct the code generator to use internal heuristics to determine whether to inline MathWorks functions called by user-written functions, set the code configuration object property InlineBetweenUserAndMathWorksFunctions to 'Speed'. This setting can generate C++ code that is more efficient but less readable than highly modularized code.

cfg.InlineBetweenUserAndMathWorksFunctions = 'Speed';

Generate code using the cfg code configuration object. Specify the input as a 1-by-100 double type. Create a report using the -report option, and use the -d option to create a folder speed_version for the generated code.

codegen -config cfg useBessely -args {zeros(1,100)} -report -d speed_version 
Code generation successful: To view the report, open('speed_version/html/report.mldatx')

Inspect the generated C++ code in the file useBessely.cpp. Observe that a separate C++ function has not been generated for the MathWorks function bessely. Instead, the code generator has inlined the code for the MathWorks bessely function into the useBessely function that you wrote.

type(fullfile("speed_version","useBessely.cpp"))
//
// File: useBessely.cpp
//
// MATLAB Coder version            : 24.2
// C/C++ source code generated on  : 05-Sep-2024 13:49:25
//

// Include Files
#include "useBessely.h"
#include "cbinu.h"
#include "cbknu.h"
#include "cospiAndSinpi.h"
#include "cuoik.h"
#include "rt_nonfinite.h"
#include <cmath>

// Function Definitions
//
// Arguments    : const double x[100]
//                creal_T out[100]
// Return Type  : void
//
void useBessely(const double x[100], creal_T out[100])
{
  creal_T CY;
  creal_T b_CY;
  creal_T cy;
  double spn;
  for (int k{0}; k < 100; k++) {
    double d;
    int ierr;
    d = x[k];
    ierr = 0;
    if (std::isnan(d)) {
      CY.re = rtNaN;
      CY.im = 0.0;
    } else {
      double AZ;
      if (d == 0.0) {
        CY.re = rtMinusInf;
        CY.im = 0.0;
      } else if (d == 0.0) {
        CY.re = 0.0;
        CY.im = 0.0;
        ierr = 1;
      } else {
        creal_T ZN;
        double im;
        double re;
        int NUF;
        CY.re = 0.0;
        CY.im = 0.0;
        if (d == 0.0) {
          ierr = 1;
        } else {
          ierr = 0;
          ZN.re = d * 0.0;
          ZN.im = -d;
          AZ = std::abs(d);
          if (!(AZ > 0.0)) {
            AZ = 0.0;
          }
          if (AZ > 1.0737418235E+9) {
            ierr = 4;
          } else if (AZ > 32767.999992370605) {
            ierr = 3;
          }
          if (AZ < 2.2250738585072014E-305) {
            ierr = 2;
          } else {
            NUF = coder::cuoik(ZN, 2, CY);
            if (NUF < 0) {
              ierr = 2;
            } else if (NUF <= 0) {
              coder::cbknu(ZN, CY);
              ZN = CY;
              AZ = 1.0;
              if (std::fmax(std::abs(CY.re), std::abs(CY.im)) <=
                  1.0020841800044864E-289) {
                ZN.re = 4.503599627370496E+15 * CY.re;
                ZN.im = 4.503599627370496E+15 * CY.im;
                AZ = 2.2204460492503131E-16;
              }
              re = ZN.re * 0.63661977236758138 - ZN.im * 0.0;
              im = ZN.re * 0.0 + ZN.im * 0.63661977236758138;
              CY.re = AZ * re;
              CY.im = AZ * im;
            }
          }
        }
        if ((ierr == 0) || (ierr == 3)) {
          b_CY.re = 0.0;
          b_CY.im = 0.0;
          if (d == 0.0) {
            ierr = 1;
          } else {
            ierr = 0;
            ZN.re = d * 0.0;
            ZN.im = d;
            AZ = std::abs(d);
            if (!(AZ > 0.0)) {
              AZ = 0.0;
            }
            if (AZ > 1.0737418235E+9) {
              ierr = 4;
            } else if (AZ > 32767.999992370605) {
              ierr = 3;
            }
            if (AZ < 2.2250738585072014E-305) {
              ierr = 2;
            } else {
              NUF = coder::cuoik(ZN, 2, b_CY);
              if (NUF < 0) {
                ierr = 2;
              } else if (NUF <= 0) {
                boolean_T guard1;
                boolean_T guard2;
                guard1 = false;
                guard2 = false;
                if ((ZN.re == 0.0) && (d < 0.0)) {
                  int nw;
                  ZN.re = -ZN.re;
                  ZN.im = -d;
                  nw = coder::cbinu(ZN, b_CY);
                  if (nw < 0) {
                    NUF = -1;
                    if (nw == -2) {
                      NUF = -2;
                    }
                    guard2 = true;
                  } else {
                    cy.re = 0.0;
                    cy.im = 0.0;
                    nw = coder::cbknu(ZN, cy);
                    if (nw != 0) {
                      NUF = -1;
                      if (nw == -2) {
                        NUF = -2;
                      }
                      guard2 = true;
                    } else {
                      AZ = coder::internal::scalar::cospiAndSinpi(-0.0, spn);
                      re = 0.0 * b_CY.re - -3.1415926535897931 * b_CY.im;
                      im = 0.0 * b_CY.im + -3.1415926535897931 * b_CY.re;
                      b_CY.re = re + (-AZ * cy.re - -spn * cy.im);
                      b_CY.im = im + (-AZ * cy.im + -spn * cy.re);
                      guard1 = true;
                    }
                  }
                } else {
                  coder::cbknu(ZN, b_CY);
                  guard1 = true;
                }
                if (guard2) {
                  if (NUF == -1) {
                    ierr = 2;
                  } else {
                    ierr = 5;
                  }
                }
                if (guard1) {
                  ZN = b_CY;
                  AZ = 1.0;
                  if (std::fmax(std::abs(b_CY.re), std::abs(b_CY.im)) <=
                      1.0020841800044864E-289) {
                    ZN.re = 4.503599627370496E+15 * b_CY.re;
                    ZN.im = 4.503599627370496E+15 * b_CY.im;
                    AZ = 2.2204460492503131E-16;
                  }
                  re = ZN.re * 0.63661977236758138 - ZN.im * -0.0;
                  im = ZN.re * -0.0 + ZN.im * 0.63661977236758138;
                  b_CY.re = AZ * re;
                  b_CY.im = AZ * im;
                }
              }
            }
          }
          if ((ierr == 0) || (ierr == 3)) {
            CY.re = b_CY.re - CY.re;
            CY.im = b_CY.im - CY.im;
            re = 0.0 * CY.re - 0.5 * CY.im;
            im = 0.0 * CY.im + 0.5 * CY.re;
            CY.re = re;
            CY.im = im;
          }
        }
      }
      if (ierr == 5) {
        CY.re = rtNaN;
        CY.im = 0.0;
      } else if (ierr == 2) {
        CY.re = rtInf;
        CY.im = 0.0;
      }
      if (d > 0.0) {
        AZ = CY.re;
        CY.re = AZ;
        CY.im = 0.0;
      }
    }
    out[k].re = d + CY.re;
    out[k].im = CY.im;
  }
}

//
// File trailer for useBessely.cpp
//
// [EOF]
//

See Also

| | | | | |

Related Topics