Reducing run time of a numerical calculation using a mex file in Matlab
Mostrar comentarios más antiguos
I wrote a Matlab code that involves doing a numeric calculation (relaxation), but it is quite slow. I learned of the possibility of using a mex file to run a C code and integrate it into Matlab, so I was thinking of doing the numerical calculation (which is relatively simple but involves loops and takes time) in C, and the rest (before and after) in Matlab.
The part of my Matlab code where the calculation is done:
% evolution of the potentials %
% note : for the index directions with periodic boundary conditions: index=mod(index-1,L)+1 . for index=index+1 it is mod(index,L)+1 , and for index=index-1 it is mod(index-2,L)+1 %
for i_t=1:max_relaxation_iterations
for q=1:length(i_eff_V_bounded) % this is set instead of running i=2:(L-1), j=1:L , k=1:L and ending up going over sites that are 0 in our effective system %
i=i_eff_V_bounded(q);
j=j_eff_V_bounded(q);
k=k_eff_V_bounded(q);
V0=V(i,j,k);
V1=( V(i+1,j,k)+V(i-1,j,k)+V(i,mod(j,L)+1,k)+V(i,mod(j-2,L)+1,k)+V(i,j,mod(k,L)+1)+V(i,j,mod(k-2,L)+1) )/( system(i+1,j,k)+system(i-1,j,k)+system(i,mod(j,L)+1,k)+system(i,mod(j-2,L)+1,k)+system(i,j,mod(k,L)+1)+system(i,j,mod(k-2,L)+1) ); % evolving the potential as the average of its occupied neighbors %
V(i,j,k)=V0+(V1-V0)*over_relaxation_factor; % evolving the potentials in time with the over relaxation factor %
delta_V_rms(i_t)=delta_V_rms(i_t)+(V1-V0)^2; % for each t at a given p, we sum over (V1-V0)^2 in order to eventually calculate delta_V_rms_avg %
delta_V_abs(i_t)=delta_V_abs(i_t)+abs(V1-V0); % for each t at a given p, we sum over |V1-V0| in order to eventually calculate delta_V_abs_avg %
delta_V_max(i_t)=max(abs(V1-V0),delta_V_max(i_t)); % for each t at a given p, we take the max of |V1-V0| from all the sites in order to eventually calculate delta_V_max_avg %
end
end
So in C it should be something like:
#include <stdio.h>
int mod(int x,int N) /* a function for the modulo operator (instead of the remainder operator which is the % operator) assuming N is positive (x can be negative) */
{
return (x%N+N)%N;
}
double d_abs(double x) /* a function for the absolute value operator */
{
if x<0
{
return -x;
}
else
{
return x;
}
}
double max(double x,double y) /* a function for the max operator */
{
if x>y
{
return x;
}
else
{
return y;
}
}
/* evolution of the potentials */
/* note : periodic boundary conditions for the j,k directions */
void potentials_evolution(int max_relax_iters,int N_eff_occ_sites,int i_eff_V_bounded[],int j_eff_V_bounded[],int k_eff_V_bounded[],int system[][][],over_relax_fact,double V[][][],double delta_V_rms[],double delta_V_abs[],double delta_V_max[])
{
int i_t,q,i,j,k;
double V0,V1;
for(i_t=0;i_t<max_relax_iters;i_t++)
{
for(q=0;q<N_eff_occ_sites;q++) /* going over only the occupied sites left in our effective system */
{
i=i_eff_V_bounded[q];
j=j_eff_V_bounded[q];
k=k_eff_V_bounded[q];
V0=V[i][j][k];
V1=( V[i+1][j][k]+V[i-1][j][k]+V[i][mod(j+1,L)][k]+V[i][mod(j-1,L)][k]+V[i][j][mod(k+1,L)]+V[i][j][mod(k-1,L)] )/( system[i+1][j][k]+system[i-1][j][k]+system[i][mod(j+1,L)][k]+system[i][mod(j-1,L)][k]+system[i][j][mod(k+1,L)]+system[i][j][mod(k-1,L)] ) /* evolving the potential as the average of its occupied neighbors */
V[i][j][k]=V0+(V1-V0)*over_relax_fact; /* evolving the potentials in time with the over relaxation factor */
delta_V_rms[i_t]=delta_V_rms[i_t]+(V1-V0)*(V1-V0); /* for each t at a given p, we sum over (V1-V0)^2 in order to eventually calculate delta_V_rms_avg */
delta_V_abs[i_t]=delta_V_abs[i_t]+d_abs(V1-V0); /* for each t at a given p, we sum over |V1-V0| in order to eventually calculate delta_V_abs_avg */
delta_V_max[i_t]=max(d_abs(V1-V0),delta_V_max[i_t]); /* for each t at a given p, we take the max of |V1-V0| from all the sites in order to eventually calculate delta_V_max_avg */
}
}
}
And so in Matlab I will replace the part of my Matlab code shown above with something like:
potentials_evolution(max_relax_iters,N_eff_occ_sites,i_eff_V_bounded,j_eff_V_bounded,k_eff_V_bounded,system,over_relax_fact,V,delta_V_rms,delta_V_abs,delta_V_max);
How do I implement this? I tried looking around (in places such as here: https://www.mathworks.com/help/matlab/matlab_external/standalone-example.html) for a simple way to do it but I couldn't figure out how to properly do it.
Note 1: This numeric calculation is done not just once but many times for different systems that are generated randomly (there is a `for` loop going over the different systems).
Note 2: I've never used mex files and my C is quite rusty.
EDIT: After hours of trying to figure how to build the mex file, I came up with this:
#include "mex.h"
mwSize mod(mwSize x,mwSize N) /* a function for the modulo operator (instead of the remainder operator which is the % operator) assuming N is positive (x can be negative) */
{
return (x%N+N)%N;
}
double d_abs(double x) /* a function for the absolute value operator */
{
if(x<0)
{
return -x;
}
else
{
return x;
}
}
double max(double x,double y) /* a function for the max operator */
{
if(x>y)
{
return x;
}
else
{
return y;
}
}
/* in matlab i will invoke potentials_evolution(max_relax_iters,N_eff_occ_sites,i_eff_V_bounded,j_eff_V_bounded,k_eff_V_bounded,system,over_relax_fact,V,delta_V_rms,delta_V_abs,delta_V_max); */
/* evolution of the potentials */
/* note : periodic boundary conditions for the j,k directions */
void potentials_evolution(mwSize L,mwSize max_relax_iters,mwSize N_eff_occ_sites,mwSize *i_eff_V_bounded,mwSize *j_eff_V_bounded,mwSize *k_eff_V_bounded,mwSize ***system,double over_relax_fact,double ***V,double *delta_V_rms,double *delta_V_abs,double *delta_V_max)
{
int i_t,q,i,j,k;
double V0,V1;
for(i_t=0;i_t<max_relax_iters;i_t++)
{
for(q=0;q<N_eff_occ_sites;q++) /* going over only the occupied sites left in our effective system */
{
i=i_eff_V_bounded[q];
j=j_eff_V_bounded[q];
k=k_eff_V_bounded[q];
V0=V[i][j][k];
V1=( V[i+1][j][k]+V[i-1][j][k]+V[i][mod(j+1,L)][k]+V[i][mod(j-1,L)][k]+V[i][j][mod(k+1,L)]+V[i][j][mod(k-1,L)] )/( system[i+1][j][k]+system[i-1][j][k]+system[i][mod(j+1,L)][k]+system[i][mod(j-1,L)][k]+system[i][j][mod(k+1,L)]+system[i][j][mod(k-1,L)] ) /* evolving the potential as the average of its occupied neighbors */
V[i][j][k]=V0+(V1-V0)*over_relax_fact; /* evolving the potentials in time with the over relaxation factor */
delta_V_rms[i_t]=delta_V_rms[i_t]+(V1-V0)*(V1-V0); /* for each t at a given p, we sum over (V1-V0)^2 in order to eventually calculate delta_V_rms_avg */
delta_V_abs[i_t]=delta_V_abs[i_t]+d_abs(V1-V0); /* for each t at a given p, we sum over |V1-V0| in order to eventually calculate delta_V_abs_avg */
delta_V_max[i_t]=max(d_abs(V1-V0),delta_V_max[i_t]); /* for each t at a given p, we take the max of |V1-V0| from all the sites in order to eventually calculate delta_V_max_avg */
}
}
}
/* in matlab: [V,delta_V_rms,delta_V_abs,delta_V_max] = potentials_evolution(i_eff_V_bounded,j_eff_V_bounded,k_eff_V_bounded,system,over_relax_fact,V_0,delta_V_rms_0,delta_V_abs_0,delta_V_max_0) */
void mexFunction(int nlhs,mxArray *plhs,int nrhs,const mxArray *prhs[])
{
size_t L, *dim_V, max_relax_iters, N_eff_occ_sites;
size_t *i_eff_V_bounded, *j_eff_V_bounded, *k_eff_V_bounded;
size_t ***system;
double over_relax_fact;
double ***V, ***V_0;
double *delta_V_rms, *delta_V_abs, *delta_V_max, *delta_V_rms_0, *delta_V_abs_0, *delta_V_max_0;
/* additional input for the calculation */
dim_V = mxGetDimensions(prhs[5]);
L = dim_V[0];
max_relax_iters = mxGetN(prhs[6]);
N_eff_occ_sites = mxGetN(prhs[0]);
/* input arguments */
i_eff_V_bounded = mxGetInt32s(prhs[0]);
j_eff_V_bounded = mxGetInt32s(prhs[1]);
k_eff_V_bounded = mxGetInt32s(prhs[2]);
system = mxGetInt32s(prhs[3]);
over_relax_fact = mxGetScalar(prhs[4]);
V_0 = mxGetDoubles(prhs[5]);
delta_V_rms_0 = mxGetDoubles(prhs[6]);
delta_V_abs_0 = mxGetDoubles(prhs[7]);
delta_V_max_0 = mxGetDoubles(prhs[8]);
/* output arguments */
plhs[0] = mxCreateNumericArray(mxGetNumberOfDimensions(prhs[5]),mxGetDimensions(prhs[5]),mxDOUBLE_CLASS,mxREAL);
V = mxGetDoubles(plhs[0]);
plhs[1] = mxCreateNumericMatrix(max_relax_iters,1,mxDOUBLE_CLASS,mxREAL);
delta_V_rms = mxGetDoubles(plhs[1]);
plhs[2] = mxCreateNumericMatrix(max_relax_iters,1,mxDOUBLE_CLASS,mxREAL);
delta_V_abs = mxGetDoubles(plhs[2]);
plhs[3] = mxCreateNumericMatrix(max_relax_iters,1,mxDOUBLE_CLASS,mxREAL);
delta_V_max = mxGetDoubles(plhs[3]);
/* performing the calculation */
potentials_evolution((mwSize)L,(mwSize)max_relax_iters,(mwSize)N_eff_occ_sites,(mwSize)i_eff_V_bounded,(mwSize)j_eff_V_bounded,(mwSize)k_eff_V_bounded,(mwSize)system,over_relax_fact,V,delta_V_rms,delta_V_abs,delta_V_max);
}
Is this correct syntax/form-wise and does this match my Matlab code (shown above)? Do I need something else for it to run?
Respuestas (1)
Jorik
el 10 de Ag. de 2019
If you have access to MATLAB Coder, it can let you generate a C-MEX implementation for your MATLAB code. If you use the Coder App, it will guide you through the steps to generate a MEX-function and validate it runs correctly.
If you'd like to write it manually, you'll need to learn the basics of the MEX API. I would start by copying and modifying a simple function like timestwo.c:
edit(fullfile(matlabroot, 'extern', 'examples', 'refbook', 'timestwo.c'))
You can lookup the API functions in the documentation to better understand the MEX and MATRIX API, e.g.:
and
9 comentarios
tensorisation
el 10 de Ag. de 2019
Editada: tensorisation
el 10 de Ag. de 2019
tensorisation
el 11 de Ag. de 2019
Editada: tensorisation
el 11 de Ag. de 2019
Jorik
el 11 de Ag. de 2019
The nice thing about MATLAB Coder, is that besides generating standalone C code, it allows you to create a MEX-Function with the generated C code to verify in MATLAB with test stimuli if it indeed gives the same results as the MATLAB code.
Did you try compiling this in MATLAB? I think I see a couple of violations of ANSI C syntax. For instance:
if x>y
in C this should be
if (x>y)
After fixing this, the next compiler error shows me:
error: use of undeclared identifier 'L'
BTW, I'm surprised to see a custom implementation for d_abs as I would expect fabs() to be good enough?
Anyway, if you're trying to get a faster implementation of your function by leveraging C code, I would personally always start with MATLAB Coder (if available) and then maybe tweak it a bit.
But of course if you'd like to know how to write C MEX-Functions, this could be a good candidate.
I would recommend (as this is your first MEX-Function), to attach a debugger when you run it the first time when building it succeeds and step through the code. It's quite easy to make MATLAB crash with MEX-Functions that poke at wrong memory locations ;-). In general it's easy to make small mistakes with "big" effects in C.
tensorisation
el 11 de Ag. de 2019
Editada: tensorisation
el 11 de Ag. de 2019
Jorik
el 12 de Ag. de 2019
2)
Yes you need to include math.h to use fabs(). I would expect the fabs() implementation to be at least as fast, e.g. it may very well be implemented as a macro, so that avoids the overhead of a function call.
3)
Yes I think that should be possible.
4)
There is no bool/logical type in ANSI C (89/90), so you'll have to use an integer. You can then decide, short int, int, long int, etc. balancing between maximum value ranges and possibly memory usage.
BTW, did you already try MEX'ing your source code? I still can't get it to compile, because of more errors (and warnings), e.g. the mexFunction function declaration is different in mex.h than in your code.
tensorisation
el 12 de Ag. de 2019
Editada: tensorisation
el 12 de Ag. de 2019
Jorik
el 13 de Ag. de 2019
1) I don't think I'd be helping you a lot if I would fully answer this question. Please look up the reference page, it has links to 6 examples that use this API to get the dimensions. Maybe you want to make a copy of one of the examples, change the code, compile it and inspect how it works. Just as a hint, this line is in the first example from the reference page:
if (temp[x]> ((mxGetDimensions(prhs[0]))[x]) ){
That looks quite similar, so in any case you definitely don't need to use a temporary variable.
2) Did you look at the mxGetLogicals function reference page? It links to an example that shows how you can use it. No, you should not use mxGetUint8s on an mxArray that is of class mxLOGICAL_CLASS. The function reference page for mxGetUint8s mentions you need to provide a pointer to an mxArray of class mxUINT8_CLASS.
3) Exactly what I'm saying, they're not the same, compare the plhs input argument below.
I took your mexFunction line and the corresponding lines from mex.h. (BTW, I only found it because my compiler gave an error, I would strongly recommend to try to compile your MEX source code to find errors/warnings for yourself, if your code doesn't compile there's definitely something wrong)
void mexFunction(int nlhs,mxArray *plhs,int nrhs,const mxArray *prhs[])
void mexFunction(
int nlhs, /* number of expected outputs */
mxArray *plhs[], /* array of pointers to output arguments */
int nrhs, /* number of inputs */
const mxArray *prhs[] /* array of pointers to input arguments */
);
tensorisation
el 17 de Ag. de 2019
Editada: tensorisation
el 17 de Ag. de 2019
tensorisation
el 28 de Ag. de 2019
Categorías
Más información sobre Write C Functions Callable from MATLAB (MEX Files) en Centro de ayuda y File Exchange.
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!