Sunday, June 17, 2012

Calling a 3rd party DLL from Base SAS and SAS IMLPlus

I recently finished Rick Wicklin's Statistical Programming with SAS/IML Software.  Great book and provides a great learning resource for SAS IML.

One of the neat things about SAS IMLPlus is it's ability to call 3rd party libraries.  These libraries can be written in Java or any Windows DLL.  Base SAS, since 9.2, has had the ability to link and call a C/C++ compiled library (DLL in Windows, or .so in *NIX).  So let's compare the two ways to write a function and use it in your SAS programs.

First, go read the great tutorial on using FCMP and Visual Studio to create a function for Base SAS.  Many thanks to the excellent folks at SAS Tech Support for writing that.  We'll use the same function for our example.

First the Base SAS example.  We are going to call the DLL two ways.  The TS example shows how to create an FCMP wrapper around a function linked in PROC PROTO.   FCMP also allows us to write functions in SAS code to be called in the Data Step.  So we will write a SAS factorial function, call it,  and the C function wrapped in a SAS function.

options cmplib=(sasuser.proto_ds sasuser.fcmp_ds);

proc proto stdcall package=sasuser.proto_ds.cfcns;
  link 'c:\users\pazzula\documents\visual studio 2010\Projects\SASExampleLib\Debug\SASExampleLib.dll';
 
  int myfactorial(int n) ;

run;

proc fcmp inlib=sasuser.proto_ds outlib=sasuser.fcmp_ds.sasfcns;
   function cfactorial(x);
      return (myfactorial(x));
   endsub;

   function sasFactorial(x);
      p = 1;
      do i=2 to x;
           p = p*i;
        end;
        return (p);
   endsub;
quit;

data test;
n=100000000;
format test $24.;

start = datetime();
Test = "cfactorial";
do i=1 to n;
sp = cfactorial(10);
end;
end = datetime();
elapse = end - start;
ave = elapse / n;
output;

start = datetime();
Test = "sasFactorial";
do i=1 to n;
sp = sasFactorial(10);
end;
end = datetime();
elapse = end - start;
ave = elapse / n;
output;

drop i;
format start end datetime.
       n comma16.
         elapse time12.4;
run;
On my laptop, the cFactorial function averages 1.1633E-7 per call and the sasFactorial averages 1.0204E-7.  The times are relatively close.  It has been my experience that a more complex, well written, C function can outperform a FCMP written function.  There is overhead in calling the function, so that is why we see the simple SAS function running faster.

To call the function in IML Studio (using IMLPlus), we must create and declare a DllFuntion object.  In this object we specify the path to the DLL, the function name, and the number of parameters it takes.  We pass the parameters in order using the NextArgIs*() functions where * is the type.  We call the function with the Call_*() method (again * is the return type).
declare String sPathName;
sPathName = "c:\users\pazzula\documents\visual studio 2010\Projects\SASExampleLib\Debug\SASExampleLib.dll";

declare String sFuncName = "myfactorial";

declare DllFunction func = new DllFunction();
func.Init(sPathName, sFuncName, 1);
func.NextArgIsInt32(10);

s = datetime();

n = 10000;
do i=1 to n;
ret = func.Call_Int32();
end;
el = datetime() - s;
print "elapse: " el ;
print "Average call time: " (el/n) ;
The time elapsed on my laptop is 2.017 seconds for an average of 2.017E-4.  MUCH slower than Base SAS. Why?

The reason is that IML Studio is written in Java.  It is use the Java Native Interface to call the function.  Every time it calls it, the return value is taken from the C dll, into Java, and then into SAS.  Modify the do loop like this and you see a much higher throughput.

declare int objRet;
n = 10000;
do i=1 to n;
objRet = func.Call_Int32();
end;
Now the time is .967 seconds or 9.67E-5.  Still not as fast as Base SAS, but pretty quick.  I actually chatted with the guys at SAS TS about this and they tell me the limiting factor is the jump from Java into SAS.  That is why declaring the objRet in IMLPlus (which is held on the client, not the SAS session) is so much faster.  The take away is to limit the number of trips from the client objects into SAS IML variables.  If you have an array, fill it fully in IMLPlus, and then pass it to SAS.  Don't pass each element from IMLPlus into an IML matrix.

I hope this helps as people look to extend SAS and SAS IML.  Feel free to ask me a question in the comments if you have further questions.

No comments:

Post a Comment