Older blog entries for nikodemus (starting at number 40)

Inline Caches with LOAD-TIME-VALUE, part 2

So why would you want to use LOAD-TIME-VALUE to implement a cache instead of doing something like this:

(let (cache)
  (defun parse-timestamp (string)
    (let ((cached cache))
      (if (equal string (car cached))
          (cdr cached)
          (let ((stamp (%parse-timestamp string)))
            (setf cache (cons string stamp))
            stamp)))))

...or this?

(defparameter *cache* nil)

(defun parse-timestamp (string)
  (let ((cached *cache*))
    (if (equal (car cached) string)
        (cdr cached)
        (let ((stamp (%parse-timestamp string)))
          (setf *cache* (cons string stamp))
          stamp))))

It's the reason why I'm talking about inline caches. While using toplevel closure or a global variable to implement the cache works fine, consider inlining (and compiler-macros.)

Simply by proclaiming the LOAD-TIME-VALUE-using function INLINE, you will be giving each call site their own cache--which is often exactly what you want. (Admittedly this timestamp parsing probably doesn't warrant that.)

Similarly, you can define the a two-level cache with both local/call-site and global lookups (though again, timestamp parsing almost certainly doesn't need this):

(defvar *global-timestamp-cache* nil)
(defconstant +timestamp-cache-max-size+ 12)

;;; A global lookup with a simple size-limited cache.
(defun parse-timestamp (string)
  (let* ((cache *global-timestamp-cache*)
         (stamp (cdr (assoc string cache :test #'equal))))
    (or stamp
        (let ((stamp (%%parse-timestamp string)))
          (setf *global-timestamp-cache*
                (cons (cons string stamp)
                      (if (>= (length cache) +timestamp-cache-max-size+)
                          (subseq cache 0 (1- +timestamp-cache-max-size+))
                          cache)))
          stamp))))

;;; Two cases: constant string arguments get constant timestamps,
;;; and more importantly, we add a per-call-site 1-element cache.
(define-compiler-macro parse-timestamp (string)
  (if (stringp string)
      `',(%parse-timestamp string)
      (alexandria:with-gensyms (cache cached stamp)
        (alexandria:once-only (string)
          `(let* ((,cache (load-time-value (cons 'local-timestamp-cache nil)))
                 (,cached (cdr ,cache)))
            ;; NOTINLINE inhibits compiler-macro expansion.
            (declare (notinline parse-timestamp))
            (if (equal ,string (car ,cached))
                (cdr ,cached)
                (let ((,stamp (parse-timestamp ,string)))
                  (setf (cdr ,cache) (cons ,string ,stamp))
                  ,stamp)))))))

I keep saying that timestamp parsing doesn't need local (call-site) caches. What does?

  1. Things that are in the really critical path where avoiding a function call is a win.
  2. Things where there are multiple arguments ("keys"), and one or more of them are often constant: (foo 'bar 'quux zot). In this case the local lookups are much more likely to result in a cache hit because only one argument out of three varies--and since the local lookup only needs to index using a single key, it is always going to be faster than the global cache lookup which needs to index using all three keys/arguments.

Final note regarding caches, and inline caches in particular: don't go overboard with them. Unless you actually need them, they will hinder your performance more than they help it: a cache miss means extra work done compared to a straight up call, so unless the number of cache hits is sufficient to compensate for that they're not worth it. As always, the profiler is your friend.

Syndicated 2011-02-21 06:25:42 from Nikodemus Siivola

Inline Caches with LOAD-TIME-VALUE

This isn't anything new, but a handy technique that isn't maybe as well known as it could be.

Let's say you're receiving stuff that has timestamps over a stream, and you see that parsing those timestamp strings is taking more time than you'd like.

What's more, you notice that often enough several consequtive timestamps are identical -- so you're parsing the same string over and over again.

Obviously any memoization technique could serve well here, but here's how you would do it with an inline 1-element cache.

(defun parse-timestamp (string)
  ;; LOAD-TIME-VALUE is kind of like "static" in C. Value of
  ;; CACHE will be bound to the _same_ CONS cell every time
  ;; this code run. It could just as well be any other
  ;; object, but a CONS is a nice lightweight object to use
  ;; for a 1-element cache.
  ;;
  ;; In its CDR we will store another CONS whose CAR is the
  ;; string and whose CDR is the corresponding timestamp.
  ;; Then we can use EQUAL to compare the strings, and pick
  ;; the cached value when appropriate.
  ;;
  ;; The second CONS is used so that we can replace it
  ;; atomically when needed: if multiple threads are running
  ;; in parallel, we don't want to mutate a cell. Otherwise
  ;; another thread might have just compared the string only
  ;; to have the timestamp change under its feet --
  ;; returning the wrong result.
  ;;
  ;; This "replace a whole object containing multiple
  ;; interdependant values" is a very useful portable
  ;; thread-safety technique (it only assumes that object
  ;; initialization has a memory barrier so that all threads
  ;; see consistent values of the new CONS immediately: if
  ;; they see the string in the new CONS, they must also see
  ;; the timestamp.)
  (let* ((cache (load-time-value (cons 'timestamp-cache nil)))
         (cached (cdr cache)))
    (if (equal (car cached) string)
        (cdr cached)
        (let* ((timestamp (%parse-timestamp string))
               (new-cached (cons string timestamp)))
          (setf (cdr cache) new-cached)
          timestamp))))

Syndicated 2011-02-19 09:26:43 from Nikodemus Siivola

Always use *earmuffs*

I you're an old hand with Common Lisp, you can stop reading now. If on the other hand you occasionally find youself wondering: "When -- and why -- should I use *earmuffs* to name special variables?" do read on.

(I originally posted this as a comment here, but figured that maybe this deserves saying here too.)

You always want the earmuffs.

There's never any real excuse for leaving them out. The reason comes in two parts:

Case 1: binding a special variable by accident.

(defparameter foo "foo!")

(defun say-it ()
  (write-line foo))

(defun say-more (foo)
  (say-it)
  (format t "now say ~A~%" foo))

(say-more "bar!") will print out

say bar!
now say bar!

instead of the intended

say foo!
now say bar!

In this case the earmuffs tell you that binding the variable may alter behaviour globally.

Case 2: typo causing you to read a special instead of a local -- but there's no warning.

Normally you get a compile-time warning and a run-time error for

(defun foo (bar)
  bat)

but if you've previously done

(defparameter bat "baseball")

then you don't get either, and will waste time debugging, trying to figure out where things go wrong.

If your code is for your personal enjoyment no-one cares if you use earmuffs or not, but when you post code or publish it, not marking *special* variables special will waste other people's time. Please don't do that.

Not just if your code has a bug: whenever I see

(defparameter no-earmuffs ...)

I realize I need to read the code extra-carefully because there's no guarantee that code that looks innocent at a glance will not have non-local side-effects or dependencies.

Always use the earmuffs. It's true that when you understand the rules you can break them, but for this one it is really rare to find a real exception.

Syndicated 2010-11-14 18:13:28 from Nikodemus Siivola

Screaming Zebras

I hope everyone isn't completely fed up with Screamer posts yet.

Stephan Frank sent me a lovely implementation of the Zebra Puzzle using Screamer's constraint propagation features:

In contrast to using just the backtracking features of Screamer as demonstrated by the Einstein's Riddle in my last post, this is far more concise and declarative.

Apropos: there's now a Google Group for Screamer.

Syndicated 2010-11-08 11:14:47 from Nikodemus Siivola

Solving Einstein's Riddle with Screamer

I added it as an example to the Screamer manual, seeing as it is a popular demo for this kind of things:

It's completely unoptimized, but still about 20% faster on SBCL than eg. Edi Weitz's solution.

As you can see it's not nearly as declarative as a typical Prolog solution, though: Screamer's backtracking is much more explicit.

Apropos: do go and take a look at the Edi's solution if you've never seen it! In about the same line-count as I use to solve the riddle he first implements a generalized riddle-solver and then solves this riddle with it. "Cool" doesn't even begin to describe it.

Syndicated 2010-11-06 14:50:42 from Nikodemus Siivola

What's Up With Screamer

I was going to delay this for a bit, but seeing as how it's on Reddit right now, I might just as well put this out now.

Screamer? Waz dat? Screamer is a very nifty extension to Common Lisp developed originally by Jeffrey Siskind and David McAllester. It provides for nondeterminism (think Prolog-like search stuff), forward constaint propagation (think solving equations of dependent variables), and allows mixing these with regular Common Lisp seamlessly.

So whaz up den? Mostly by accident I find myself maintaining a copy of it. Since it's mature software, aside from my initial cleanup pass to update it for ANSI compatibility (Screamer is originally for CLtL1/CLtL2) I haven't been doing very much to it.

Or so it was ... until very recently. Now I've started putting together a new manual, and looking into some of the missing features -- and maybe doing more, once I feel comfortable with the codebase.

If you haven't ever checked it out, go take a look. If you have peeked at it before, but couldn't make heads or tails, the new manual might make things easier -- while it is still very much work in progress with great many things missing, I tried to make sure the Overview chapter touches most bases.

If you've been using some version of Screamer, I would like to hear from you. What are you doing with it? What kind of issues have you had with it? Do you have any local extensions or bugfixes you would like to see folded into Screamer itself? Etc. Drop me a line.

In it's current state it is functionally very much the same 3.20 release that has been floating around since 1994, so users of other copies of Screamer should have no issues migrating to it. All planned changes so far are backwards-compatible.

Oh, right. This is probably one of the last Common Lisp related posts on random-state.net: most things like this will in the future appear in the forthcoming Steel Bank Studio blog. There's no blog there yet, and I'll announce it here when there is -- just a heads-up for now.

Syndicated 2010-11-04 12:18:29 from Nikodemus Siivola

Need a Heap?

I needed a heap, and the only one I could find in hurry was cl-heap, which is GPL3 and not thread-safe -- and not very fast either.

So I wrote Pileup, a portable, performant, and thread-safe binary heap for Common Lisp. Right now it does what I needed it for, so further development is likely to be slow -- but give me a holler if you desperately need :ELEMENT-TYPE support in MAKE-HEAP, HEAP-DELETE-IF, or something else.

(No, this ain't rocket science -- which is why I was so baffled about not finding one.)

Syndicated 2010-11-01 17:45:41 from Nikodemus Siivola

ASDF System Names and Package Names

Consider: you have a package called TLD.YOURDOMAIN.FOO -- which you are pretty sure will not clash with anything else.

Then why is your ASDF system called FOO? (If it isn't, then you aren't guilty of this particular sin and can stop reading now.)

In practise either plain FOO would have been fine for both, or the ASDF system is going to clash with something else even if the package doesn't.

Whichever naming style you use, please do be consistent about it. Thank you for listening to this morning's grumpy complaint.

Syndicated 2010-10-29 06:17:06 from Nikodemus Siivola

Tutorial Planning and Quick Replies

Many thanks for everyone who responded to my SBCL+Quicklisp Tutorial Topic survey. (I'm leaving the survey open for a while longer, so it's not too late to say your piece.)

The responses have been quite varied: some people believe that the last thing the world needs is another "Getting Started" or "Common Lisp + Web" tutorial -- and others feel that those are the only ones that really matter. :)

Order of things remains to be seen, but I'm pretty sure I'll cover the ground mentioned in my original post in addition to other topics.

Some of the suggested topics which I have added to my list: how to structure a project, basic IO and external formats, regular expressions, FFI, threads, GUI stuff, OpenGL, ncurses, unit testing, S3, XML, music/sound, and application delivery.

Many more topics were suggested, but these were some of the more popular ones. Maintaining quality of the tutorials is important to me, so it remains to be seen if I will outsource some things, or if I will ask people better versed in a domain outside my experience to review what I have to say.

Many respondents took the opportunity to ask additional questions, so I'll answer a couple of them here:

  • What platforms will you cover?

    For the most part I will try to cover the big three: Linux, OS X, and Windows. Some tutorials might be platform specific, and some might be limited to eg. those platforms where SBCL thread support is up to snuff.

  • Can I help?

    Maybe! I've started writing, and will at some point ask for a few reviewers. Initially at least, however, I will do all of the writing. Once the style and quality of the tutorials is established I may take submissions -- and as mentioned above, I may also end up soliciting them.

  • No, really, how can I help right now?

    Seriously? Ok...

    Report SBCL bugs. Both in some responses to this survey, and in the SBCL User Survey people vaguely mentioned some SBCL bugs. It is not guaranteed that I or other devs know about them unless they are reported -- and even if a bug is reported, it is important to know if it affects several people, or is a critical issue as opposed to an occasional annoyance.

    Contribute to SBCL or other open source Common Lisp projects. There are countless ways. Try out QuickLisp and report any problems. Write documentation patches for existing libraries. Even if you're not a technical wizard, proofreding is always needed, and webpages can be prettified. Or if you or your company is using SBCL commercially, consider buying SBCL support.

Syndicated 2010-10-15 12:37:31 from Nikodemus Siivola

What SBCL + Quicklisp tutorials would you like to see?

With the coming of Quicklisp, the ultimate library manager for Common Lisp, I've decided to write a small series of SBCL + Quicklisp tutorials aimed at relative beginners.

However, I'm not sure if I have a good grasp of the kind of tutorials that are most needed.

My initial plan looks something like this, getting maybe one or two out per week after Quicklisp comes out of beta:

  • Getting Your Environment Up. (SBCL, Quicklisp, Slime, Paredit, and Emacs.)
  • Quick Goofing Around & Hello World.
  • How To Hack Lisp With Slime.
  • Serving a Webpage.
  • Scraping a Webpage.
  • Hooking Up Postgres.
  • Hooking Up SQLite.
  • Drawing Pretty Pictures.
  • Crunching Numbers.
  • From EUC-JP to UTF-8.

So, without further ado, a quick survey:

Syndicated 2010-10-13 08:07:42 from Nikodemus Siivola

31 older entries...

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!