Archive for December, 2007

A good day’s hacking

Everyone had the day off, so I decided to work on making a graphing library. I’ve been on a fairly constant quest for easy data visualization, running through several options:

  1. homegrown SVG renderer (javascript)
  2. homegrown png renderer (C#)
  3. Gruff (ruby)
  4. some graphing capability from Dojo (javascript)
  5. cl-plplot

I’ve never been happy with any of these, for a few reasons:

  1. SVG support for IE is a pain
  2. C# requires a million objects to do anything
  3. Gruff had good defaults, but had some annoying bugs, and I ended up jumping through a lot of hoops to get some basic aggregation going.  I looked at monkey-patching gruff, but that seemed like a really bad idea
  4. The dojo charting was slow, and the setup was verbose (this may have changed)
  5. cl-plplot output wasn’t very attractive. Great for scientific usages, but for business apps it didn’t have enough rounded edges.

I want something that’s concise, has good defaults, rendered to PNG, and is easy to use from lisp. So today I tried rolling my own, and I’m pretty happy with the results:

pie-chart-sample.png

The code to generate this graph is pretty concise, too:

(defun pie-chart-sample ()
  "draws a simple pie chart, rending to pie-chart-sample.png"
  (render-chart
   (make-instance 'pie-chart
		  :data-items (make-items '(40
					    (10 "baz")
					    (60 "bar" (.5 .5 .5)))))
   "pie-chart-sample.png"))

This demonstrates a few things:

  1. data can be specified as value, a value/label, or value/label/color
  2. it automatically choses colors if unspecified
  3. the labels are automatically positioned off to the side

There’s still some places to cleanup the API, I don’t like having make-items in there, and a lot of the behind the scenes is messy as all hell.

I started by looking at the source for Paragent, a lisp-based IT management tool. I first saw really attractive graphs coming from lisp there, and it impressed me a lot. It was building on cl-vectors, salza, and zpb-ttf to make png files. There was a lot of weird stuff going on in the paragent code, so I instead opted to port my old javascript code to lisp, using vecto for the rendering.

I ran into a brick wall pretty early when calculating the wedges, as I’ve forgotten just about all the math I ever knew (had to look up the distance formula, for christsakes). The main problem was figuring out how to draw the curved part. I spent a long time staring at Bézier curves before giving up and taking a different approach. I made a circular clip-path (basically a mask) for the chart, and drew big enough wedges to fill the circle, side-stepping my mathematical shortcomings. There’s some goofy code to account for large slices, but it worked for all values I tested.

I did a LOT of learning on this one, working through some package semantics and adding some sparse lisp-unit tests. A few more days worth of effort for other chart types, and I might have something worthy of release.

Comments (1)

String building in Lisp

We’re grinding (in the RPG sense) with lisp at work, and keep coming into problems that have multiple simple solutions, but don’t have the instincts/knowledge to readily identify which one is best. One such problem is building up strings, in a couple of cases. I took some time this morning to go through the cases, using a 10000 iteration loop and time to do a simple benchmark. The tests were run on SBCL 1.0.11.

UPDATE: added metrics with *PRINT-PRETTY* off, as suggested in comments. This has a big effect.

Conversion

There are some cases where we wanted a number as a string, as part of building HTML. For this demo, we’ll put the number in variable *d*:

(defvar *d* 19.80)

We found three options:

  1. cl-interpol: #?”${*d*}” => “19.8″
  2. (princ-to-string *d*) => “19.8″
  3. (format nil “~a” *d*) => “19.8″

Here’s how they compared:

method bytes consed. page faults calls to %EVAL system run time (s) user run time (s) real time (s)
cl-interpol, *PRINT-PRETTY* nil 5,677,440 0 0 0.0 0.085987 0.087
cl-interpol 22,964,840 0 0 0.001 0.138979 0.137
princ-to-string, *PRINT-PRETTY* nil 0 0 0 0.0 0.0 0.0
princ-to-string 0 0 0 0.0 0.0 0.0
format, *PRINT-PRETTY* nil 6,483,008 0 0 0.0 0.110984 0.111
format 23,763,096 0 0 9.99e-4 0.166974 0.168

Princ-to-string is the bigger winner in this case.

Simple Prepending

For the this case, we have some decimal value in a variable called *d*, and want it in a string, prepended with a “$”.

We identified three possible solutions:

  1. (arnesi:join-strings (list “$” (princ-to-string *d*))) => “$19.8″
  2. cl-interpol: #?”$${*d*}” => “$19.8″
  3. (format nil “$~a” *d*) => “$19.8″

Here’s how they compared:

method bytes consed. page faults calls to %EVAL system run time (s) user run time (s) real time (s)
arnesi:join-strings, *PRINT-PRETTY* nil 6,320,064 0 0 9.99e-4 0.099985 0.1
arnesi:join-strings 23,598,928 0 0 0.002 0.158975 0.159
cl-interpol, *PRINT-PRETTY* nil 5,675,904 0 0 0.0 0.078988 0.079
cl-interpol 22,961,800 0 0 0.001 0.144978 0.144
format, *PRINT-PRETTY* nil 6,801,568 0 0 0.0 0.122982 0.122
format 24,085,088 0 0 0.0 0.186971 0.186

In this case, cl-interpol wins on all counts, after compile time.

Dynamic Prepending

In this case, our prefix is determined by another variable:

(defvar *prefix* “$”)

  1. (arnesi:join-strings (list *prefix* (princ-to-string *d*))) => “$19.8″
  2. cl-interpol: #?”${*prefix*}${*d*}” => “$19.8″
  3. (format nil “~a~a” *prefix* *d*) => “$19.8″
method bytes consed. page faults calls to %EVAL system run time (s) user run time (s) real time (s)
arnesi:join-strings, *PRINT-PRETTY* nil 6,319,784 0 0 0.0 0.099985 0.101
arnesi:join-strings 23,602,432 0 0 0.001 0.155977 0.156
cl-interpol, *PRINT-PRETTY* nil 5,679,672 0 0 0.0 0.081987 0.083
cl-interpol 40,239,976 0 0 0.0 0.19597 0.196
format, *PRINT-PRETTY* nil 7,282,504 0 0 0.002 0.13298 0.131
format 41,845,632 0 0 0.001 0.234964 0.234

Now things get a little messier, and dependent on the setting of *PRINT-PRETTY*. join-strings wins big if *PRINT-PRETTY is on, but if its off, then cl-interpol takes the cake again.

Simple Wrapping

In this case we want to put some text before and after our value.

The options:

  1. (arnesi:join-strings (list “$” (princ-to-string *d*) ” USD”)) => “$19.8 USD”
  2. cl-interpol: #?”$${*d*} USD” => “$19.8 USD”
  3. (format nil “$~a USD” *d*) => “$19.8 USD”

The data:

method bytes consed. page faults calls to %EVAL system run time (s) user run time (s) real time (s)
arnesi:join-strings, *PRINT-PRETTY* nil 6,722,200 0 0 0.0 0.095985 0.096
arnesi:join-strings 24,001,352 0 0 0.001 0.165975 0.163
cl-interpol, *PRINT-PRETTY* nil 5,845,912 0 0 0.0 0.093986 0.094
cl-interpol 23,122,736 0 0 0.001 0.147977 0.149
format, *PRINT-PRETTY* nil 7,439,600 0 0 0.0 0.13298 0.132
format 24,725,176 0 0 0.001 0.19897 0.199

cl-interpol is still the winner, which is expected given the results of the simple prepending.

Dynamic Wrapping

Now let’s put the suffix into a variable and see how it goes:

(defvar *suffix* "USD")

The options:

  1. (arnesi:join-strings (list *prefix* (princ-to-string *d*) ” ” *suffix*)) => “$19.8 USD”
  2. cl-interpol: #?”${*prefix*}${*d*} ${*suffix*}” => “$19.8 USD”
  3. (format nil “~a~a ~a” *prefix* *d* *suffix*)=> “$19.8 USD”

The results:

method bytes consed. page faults calls to %EVAL system run time (s) user run time (s) real time (s)
arnesi:join-strings, *PRINT-PRETTY* nil 6,964,576 0 0 0.0 0.100985 0.101
arnesi:join-strings 24,248,160 0 0 0.001999 0.166975 0.168
cl-interpol, *PRINT-PRETTY* nil 5,842,032 0 0 0.0 0.099985 0.099
cl-interpol 57,682,928 0 0 0.001 0.250961 0.251
format, *PRINT-PRETTY* nil 8,560,800 0 0 0.0 0.155977 0.157
format 60,406,968 0 0 0.001 0.341948 0.342

Another mixed result, depending on *PRINT-PRETTY*. Again we have join-strings if you need *PRINT-PRETTY*, cl-interpol if not.

My Conclusions

Use princ-to-string for simple conversions, use cl-interpol everywhere if you can turn off *PRINT-PRETTY*. If I need *PRINT-PRETTY*, then use cl-interpol and replace it with join-strings when tuning performance.

Comments (4)