Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding redundant place calculations with setf

Tags:

common-lisp

The Context

I've found in my limited experience with Common Lisp, it's not uncommon to have some code like this

(setf (gethash key table)
      (my-transformation (gethash key table)))

where you read from a setfable place, perform some computation on the value stored there, then write to the same place. The thing I don't like about this is that the place will be computed twice, but the place will (hopefully!) be the same both times. If the place computation is expensive, we're doing as much as twice as much work as we need to.

The Question

Is it possible to eliminate the need for the double computation? That is, is it possible (desirable?) to write a macro setf-inplace:

(setf-inplace (gethash key table) #'my-transformation)

conceptually equivalent (ignoring for the moment multiple-values weirdness) to but potentially much faster than the original code, preferably without relying on implementation details?

I know I may be putting the cart in front of the horse in this particular case, since SBCL caches the lookup for gethash, but it seems to me that other setfable places, like (assoc key my-alist), may not be so easy to cache—except, of course, via some mechanism like the setf-inplace above.

like image 897
Stuart Olsen Avatar asked Oct 20 '22 13:10

Stuart Olsen


1 Answers

(setf (gethash key table)
      (my-transformation (gethash key table)))

Let's say the transformation is 1+. Then above is

(setf (gethash key table)
      (1+ (gethash key table)))

Which in Common Lisp has a macro:

(incf (gethash key table))

Clozure CL expands this to:

(LET* ((#:G69020 KEY)
       (#:G69021 TABLE)
       (#:G69022 1)
       (#:G69019 (+ (GETHASH #:G69020 #:G69021) #:G69022)))
  (DECLARE (TYPE BIT #:G69022) (TYPE T #:G69019))
  (CCL::PUTHASH #:G69020 #:G69021 #:G69019))

Now remember that the setter for a place is found at compile time and that the setter operation of a place is different from the getter. Not only does the setter something different by updating something, but it also may do arbitrary other things like setting up a transaction, acquiring a lock, ... Thus getting and setting are not the same/similar operation twice.

Common Lisp has no physical data structure which represents a place. A place is an abstract idea which combines a setter with a given getter. The setter can be arbitrary complex code.

So, in above code - which calculations are redundant?

Still you say what about optimizing access for the primitive case:

(incf (car (very-long-access-chain data)))

You would have to write by hand:

(let ((cons-cell (very-long-access-chain data)))
  (incf (car cons-cell))

Which is basically what Lisp does anyway. See: CLHS 5.1.1.1 Evaluation of Subforms to Places

like image 151
Rainer Joswig Avatar answered Oct 23 '22 23:10

Rainer Joswig