In this section, we develop a function to apply a first-order linear
recursive filter to a vector of data. The S-PLUS function filter does
what we want, but we’ll ignore it for now in favor of the following
pure S code:
1 2 3 4 5 6 7 8 | Ar <- function(x, phi) { n <- length(x) if (n>1) for (i in 2:n) x[i] <- phi * x[i - 1] + x[i] x } |
Looping is traditionally one area where S-PLUS tends to be
significantly slower than compiled code, so we can rewrite the above
code in C as follows, creating a file Ar.c:
1 2 3 4 5 6 | void arsim(double *x, long *n, double *phi) { long i; for (i=1; i<*n; i++) x[i] = *phi * x[i-1] + x[i] ; } |
This code is purely C language code; there are no dependencies on C
libraries, or on S-PLUS, or on the Windows API. Such code should be
portable to most operating systems and most Windows compilers. We
will use Microsoft Visual C++ for our examples. It is quite simple to
create a DLL from this code using Visual C++ 6.0:
1. Start Visual C++ 6.0, and from the File menu, select New.
2. From the New dialog, select the Projects tab.
3. In the Project Workspace dialog, specify a name for the project (such as “ar”), and for Project Type choose “S-PLUS Chapter DLL (.C and .Call)” (not MFCAppWizard (dll)).
4. Replace the sample arC code with the code above for Ar.c.
5. From the Build menu, choose Rebuild All.
Visual C++ will build a DLL in your ar project directory with the
name S.dll. If we want to use our loaded call very often, it will save
us time to define an S-PLUS function that calls the code:
1 2 3 4 5 6 7 8 | ar.compiled <- function(x, phi) { .C("arsim", as.double(x), length(x), as.double(phi))[[1]] } |
In the common case where you are writing a number of C++, C, or
Fortran programs and will be developing S language wrappers for
them all, it is most convenient to create an S-PLUS chapter in the C++
project directory:
> createChapter("c:\\cprojects\\ar")
[1] "c:\\cprojects\\ar"
When you attach this chapter, the DLL S.dll is automatically loaded.
You can then create your S function ar.compiled in that directory,
and it will be ready for you to use.
Trying the code with a call to ar.compiled yields the following:
> ar.compiled(1:20, .75)
[1] 1.000000 2.750000 5.062500 7.796875 10.847656
[6] 14.135742 17.601807 21.201355 24.901016 28.675762
[11] 32.506822 36.380116 40.285087 44.213815 48.160362
[16] 52.120271 56.090203 60.067653 64.050739 68.038055
You lose some flexibility in the function by writing it in C. Our
ar.compiled function converts all input data to double precision, so it
won’t work correctly for complex data sets nor objects with special
arithmetic methods. The pure S-PLUS version works for all these
cases. If complex data is important for your application, you could
write C code for the complex case and have the S-PLUS code decide
which C function to call. Similarly, to make ar.compiled work for
data in classes with special arithmetic methods, you could have it call
the C code only after coercing the data to class "numeric", so that it
could not invoke special arithmetic methods. This might be too
conservative, however, as there could be many classes of data without
arithmetic methods which could use the fast C code.
Another approach would be to make ar.compiled a generic function,
for which the default method calls the C code for numeric data. For
classes with special arithmetic methods, pure S-PLUS code could be
dispatched. Those classes of data without special arithmetic methods
could include a class method for ar.compiled that would coerce the
data to class "numeric" and invoke the default method on the now
numeric data, thus using the fast compiled code, then postprocess the
result if needed (perhaps just restoring the class). Using the objectoriented
approach is more work to set up, but gives you the chance to
combine the speed of compiled code with the flexibility of S-PLUS
code.