software development and consulting

I am working on some Lisp code. I am trying to mimic the basic structure of a large C++ project. I think the way the C++ project is structured is a good fit for the tasks involved.

Most of the C++ stuff is done with classes. Most of the methods of those classes are virtual methods. Many of the methods will be called multiple times every hundredth of a second. Very few of the virtual methods ever get overridden by subclasses.

So, I got to thinking. Does it make sense for me to use CLOS stuff at all for most of this? Would it be significantly faster to use (defstruct …) and (defun …) instead of (defclass …) and (defgeneric …)?

My gut instinct was: Yes. My first set of test code didn’t bear that out. For both the classes and the structs, I used (MAKE-INSTANCE …) to allocate and (WITH-SLOTS …) to access. When doing so, the classes with generic functions took about 1.5 seconds for 10,000,000 iterations under SBCL while the structs with non-generic functions took about 2.2 seconds.

For the next iteration of testing, I decided to use accessors instead of slots. I had the following defined:

I then switched from using calls like (slot-value instance ‘slot-three) in my functions to using calls like (class-slot-three instance) or (sub-struct-slot-three instance). Now, 10,000,000 iterations took 2.6 seconds for the classes and 0.3 seconds for the structs in SBCL. In Clozure (64-bit), 10,000,000 iterations with classes and with method-dispatch and with accessors took 11.0 seconds, with classess and without method-dispatch but with accessors took 6.1 seconds, and with structs and without method-dispatch and with accessors took 0.4 seconds.

There is much more that I can explore to tune this code. But, for now, I think the answer is fairly easy. I am going to use structs, functions, and accessors for as many cases as I can. Here is the generic-function, class, struct test code with accessors. The first loop uses classes and generic functions. The second loop uses classes and regular functions. The third loop uses structs and regular functions.

The structs are five times faster. Certainly, both are fast enough for almost everything. But, for realtime, interactive 3D stuff, there is enough going on that some of those 5x’s could make a big difference. Certainly, I don’t want it to only be able to do 1/2 the resolution or a quarter of the polygons that the C++ version does if I can help it.

Yes, this falls in the “premature optimization” category. But, it also falls in the “easier to do now” and “just about as pretty” and “some of it will have thousands of accesses per frame” categories.

There are probably ways. I would have to experiment quite a bit to get a way that I felt confident would work throughout. I have since been reconsidering my stance on this. I think I’m still going to use classes and generic functions for things like cameras and meshes that I expect to get called only a couple of times per frame and use structs for things like facets and vertexes that will be referenced hundreds of thousands of times per frame.

I still haven’t fully embraced Agile programming, but I have done enough fundamental changes to APIs in my time, that I’m reasonably confident I can go back and retro-fit whatever I need to if some portion is weighting me down.

Related posts:

Speedy Matrix Multiplication in Lisp, Again… In response to my earlier post about hidden memory allocations, Jason Cornez pointed out how to rework my loop so that Allegro wouldn’t need to allocate memory. The new structure avoids using the summing keyword in (loop …) and doesn’t use the return-value from (loop …). (declaim (ftype (function ((simple-array single-float (12))...

Trying to Unconfound Lisp Speeds In the original version of my Optimizing Lisp Some More article, I did a bad comparison between SBCL and Clozure. SBCL supports two different ways to declare the arguments to a function. Clozure only supports one of those ways. As such, my declarations didn’t matter at all to Clozure. I...

Even More Optimization In the previous article, I showed a chunk of code for multiplying a vector by a matrix. Nikodemus Silvola, on the sbcl-help mailing list, recommended that I change the matrix to a one-dimensional array instead of a two dimensional array. Doing that takes me from ten million iterations in 1.038...

Optimizing Lisp Some More Today, I spent a large chunk of the day trying optimize a very small chunk of code. The code takes a matrix with three rows and four columns. It multiplies it by a vector that has three rows (and a pretend fourth row that is set to one). Here is...