Tuesday, August 12, 2008

Optimisation through MEX files

Whilst MATLAB is an excellent expressive tool, it can occasionally run a little bit slow for our liking. However, the folks at Mathworks have provided an interface that can be used to speed up code execution in particular circumstances.

MEX Files

MATLAB allows for compilation of C or Fortran sub-routines into a DLL (or equivalent) such that it can be called from within MATLAB as per any other function.

I'll be using a simple example I came across a while ago when attempting to read in large GPS logs containing on the order of a million GPS position records. As expected, the process of parsing these files took some time. What was unexpected was where the code was using up CPU time.

A quick run of the MATLAB Profiler revealed that approximately 50% of my processing time was spent in the calculation of the NMEA checksum (defined here). The MATLAB calculateChecksum function used is outlined below.
%===============================================================================
% Description : Calculate the NMEA Checksum for the supplied string. Calculated
% as the successive bitwise exclusive OR of all characters
%===============================================================================
function checksum = calculateChecksum(sentence)

% Initialise checksum

checksum = uint8(0);


for i_char = 1:length(sentence)

checksum = bitxor(checksum, uint8(sentence(i_char)));

end


checksum = dec2hex(checksum, 2);


end

To demonstrate the CPU usage of the above code snippet, a short test function was created:
function test_Checksum

nmea_sentence = 'GPGGA,195237,4308.639,S,07744.402,E,1,03,3.2,365.3,M,-34.5,M,1001,';
cs = '';

tic
for i = 1:50000
cs = calculateChecksum(nmea_sentence);
end
toc

% Verify Checksum
if (~strcmp(cs, '7F'))
error('Incorrect Checksum calculated');
end
end
The result of this function when executed several times:
Elapsed time is 4.990806 seconds.
Elapsed time is 4.978824 seconds.
Elapsed time is 5.029520 seconds.
Looking at the profiler output (run independently):

The majority of the time is spent performing the iterative XOR and the conversion from decimal to hexadecimal.

Using this example from Mathworks as a guide I created a simple MEX compatible C function that would calculate the 2 character hexadecimal checksum from a supplied string.
#include "mex.h"
#include <stdio.h>


void calculateChecksumFunction(const char* in_string, char *out_string)
{
int checksum_as_int = 0;
int i, str_length = strlen(in_string);

for (i = 0; i < style="color: rgb(51, 102, 255);">int
)*(in_string++);
}

checksum_as_int &= 0xFF;
sprintf(out_string, "%02X", checksum_as_int);
}

//****************************************************************
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
char *input_buf, *output_buf;
int buflen, status;

/* Check for proper number of arguments. */
if (nrhs != 1)
mexErrMsgTxt("One input required.");
else if (nlhs > 1)
mexErrMsgTxt("Too many output arguments.");

/* Input must be a string. */
if (mxIsChar(prhs[0]) != 1)
mexErrMsgTxt("Input must be a string.");

/* Input must be a row vector. */
if (mxGetM(prhs[0]) != 1)
mexErrMsgTxt("Input must be a row vector.");

/* Get the length of the input string. */
buflen = (mxGetM(prhs[0]) * mxGetN(prhs[0])) + 1;

/* Allocate memory for input and output strings.
* output string should be 2 ASCII characters (plus terminator) */
input_buf = mxCalloc(buflen, sizeof(char));
output_buf = mxCalloc(3, sizeof(char));

/* Copy the string data from prhs[0] into a C string
* input_buf. */
status = mxGetString(prhs[0], input_buf, buflen);
if (status != 0)
mexWarnMsgTxt("Not enough space. String is truncated.");

/* Calculate checksum and store result in output_buf */
calculateChecksumFunction(input_buf, output_buf);

/* Format return as a mex-string */
plhs[0] = mxCreateString(output_buf);

return;
}

This MEX compatible C file was then compiled using the 'mex' command from the MATLAB command window:
mex calculateChecksumMEX.c
This created a DLL in the same directory named calculateChecksumMEX.dll.

Substituting a call to calculateChecksumMEX in the test function redirects the processing to the created DLL.

The speed improvement is immediately noticeable:
Elapsed time is 0.423503 seconds.
Elapsed time is 0.425224 seconds.
Elapsed time is 0.430266 seconds.
An order of magnitude speed improvement was gained through the simple technique of identifying and isolating portions of code which were using the most CPU time and performing these operations in an an efficient C sub-routine.

Now MEX is not the silver bullet for every slow performing MATLAB function, but can prove to be useful. I would always recommend running the MATLAB Profiler over your code at least once to identify regions of poor performance. Poorly written MATLAB can run orders of magnitude slower than well written MATLAB.

2 comments:

  1. Spell check please:
    optimization

    ReplyDelete
  2. Step out of your sheltered American world please:

    English (Australian)

    ReplyDelete