faster SLOT-VALUE &co with variable slot names
authorNikodemus Siivola <nikodemus@random-state.net>
Mon, 27 Aug 2007 15:13:27 +0000 (15:13 +0000)
committerNikodemus Siivola <nikodemus@random-state.net>
Mon, 27 Aug 2007 15:13:27 +0000 (15:13 +0000)
* Cache the slot-location and typecheckfun in the class-slots-vector
  in addition to the slot-definition for STANDARD-CLASS and
  FUNCALLABLE-STANDARD-CLASS.

* New function CHECK-OBSOLETE-INSTANCE/CLASS-OF, which combines the
  two. Faster then calling both separately, since both need to grab
  the wrapper -- used by SLOT-VALUE &co.

* Unoptimized SLOT-VALUE, (SETF SLOT-VALUE), SLOT-BOUNDP, and
  SLOT-MAKUNBOUND can now directly access instance and class slots the
  typecheckfun in normal cases, giving upto 20-25% performance boost
  for these functions.

* Obsolete-instance protocol tests using variable slot-names.

NEWS
src/pcl/braid.lisp
src/pcl/slots-boot.lisp
src/pcl/slots.lisp
src/pcl/std-class.lisp
src/pcl/wrapper.lisp
tests/clos.impure.lisp
version.lisp-expr

diff --git a/NEWS b/NEWS
index fc41d57..01c35ab 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,9 @@
 ;;;; -*- coding: utf-8; -*-
 changes in sbcl-1.0.10 relative to sbcl-1.0.9:
+  * optimization: SLOT-VALUE and (SETF SLOT-VALUE) are now >20% faster
+    for variable slot names, when the class of the instance is
+    an instance of STANDARD-CLASS or FUNCALLABLE-STANDARD-CLASS, and not
+    any of their subclasses.
   * optimization: member type construction is now O(N) instead
     of O(N^2).
   * optimization: UNION and NUNION are now O(N+M) for large
index 5fcad4d..3ae9be4 100644 (file)
                                  slot-class))
       (set-slot 'direct-slots direct-slots)
       (set-slot 'slots slots)
-      (set-slot 'slot-vector (make-slot-vector slots)))
+      (set-slot 'slot-vector (make-slot-vector class slots)))
 
     ;; For all direct superclasses SUPER of CLASS, make sure CLASS is
     ;; a direct subclass of SUPER.  Note that METACLASS-NAME doesn't
index 9f416cf..36b70d9 100644 (file)
 ;;; requires a GF call (for SLOT-DEFINITION-NAME) for each slot in
 ;;; list up to the desired one.
 ;;;
-;;; As of 1.0.7.26 SBCL hashes the effective slot definitions into a
-;;; simple-vector, with bucket chains made out of plists keyed by the
-;;; slot names. This fixes gives O(1) performance, and avoid the GF
-;;; calls.
+;;; Current SBCL hashes the effective slot definitions, and some
+;;; information pulled out from them into a simple-vector, with bucket
+;;; chains made out of plists keyed by the slot names. This fixes
+;;; gives O(1) performance, and avoid the GF calls.
 ;;;
 ;;; MAKE-SLOT-VECTOR constructs the hashed vector out of a list of
-;;; effective slot definitions, and FIND-SLOT-DEFINITION knows how to
-;;; look up slots in that vector.
+;;; effective slot definitions and the class they pertain to, and
+;;; FIND-SLOT-DEFINITION knows how to look up slots in that vector.
 ;;;
 ;;; The only bit of cleverness in the implementation is to make the
 ;;; vectors fairly tight, but always longer then 0 elements:
 ;;; -- As long as the vector always has a length > 0
 ;;;    FIND-SLOT-DEFINITION doesn't need to handle the rare case of an
 ;;;    empty vector separately: it just returns a NIL.
+;;;
+;;; In addition to the slot-definition we also store the slot-location
+;;; and type-check function for instances of standard metaclasses, so
+;;; that SLOT-VALUE &co using variable slot names can get at them
+;;; without additional GF calls.
+;;;
+;;; Notes:
+;;;   It would be probably better to store the vector in wrapper
+;;;   instead: one less memory indirection, one less CLOS slot
+;;;   access to get at it.
+;;;
+;;;   It would also be nice to have STANDARD-INSTANCE-STRUCTURE-P
+;;;   generic instead of checking versus STANDARD-CLASS and
+;;;   FUNCALLABLE-STANDARD-CLASS.
 
 (defun find-slot-definition (class slot-name)
   (declare (symbol slot-name))
       (let ((key (car plist)))
         (setf plist (cdr plist))
         (when (eq key slot-name)
+          (return (cddar plist)))))))
+
+(defun find-slot-cell (class slot-name)
+  (declare (symbol slot-name))
+  (let* ((vector (class-slot-vector class))
+         (index (rem (sxhash slot-name) (length vector))))
+    (declare (simple-vector vector) (index index)
+             (optimize (sb-c::insert-array-bounds-checks 0)))
+    (do ((plist (the list (svref vector index)) (cdr plist)))
+        ((not (consp plist)))
+      (let ((key (car plist)))
+        (setf plist (cdr plist))
+        (when (eq key slot-name)
           (return (car plist)))))))
 
-(defun make-slot-vector (slots)
+(defun make-slot-vector (class slots)
   (let* ((n (+ (length slots) 2))
-         (vector (make-array n :initial-element nil)))
+         (vector (make-array n :initial-element nil))
+         (save-slot-location-p
+          (when (eq 'complete *boot-state*)
+            (let ((metaclass (class-of class)))
+              (or (eq metaclass *the-class-standard-class*)
+                  (eq metaclass *the-class-funcallable-standard-class*)))))
+         (save-type-check-function-p (and save-slot-location-p (safe-p class))))
     (flet ((add-to-vector (name slot)
              (declare (symbol name)
                       (optimize (sb-c::insert-array-bounds-checks 0)))
-             (setf (svref vector (rem (sxhash name) n))
-                   (list* name slot (svref vector (rem (sxhash name) n))))))
+             (let ((index (rem (sxhash name) n)))
+               (setf (svref vector index)
+                     (list* name (list* (if save-slot-location-p
+                                            (slot-definition-location slot)
+                                            ;; T tells SLOT-VALUE & SET-SLOT-VALUE
+                                            ;; that this is a non-standard class.
+                                            t)
+                                        (when save-type-check-function-p
+                                          (slot-definition-type-check-function slot))
+                                        slot)
+                            (svref vector index))))))
       (if (eq 'complete *boot-state*)
          (dolist (slot slots)
            (add-to-vector (slot-definition-name slot) slot))
index 0dcb87e..7fa3013 100644 (file)
          (t
           (error "unrecognized instance type")))))
 \f
-;;;; SLOT-VALUE, (SETF SLOT-VALUE), SLOT-BOUNDP
+;;;; STANDARD-INSTANCE-ACCESS
+
+(declaim (inline standard-instance-access (setf standard-instance-access)
+                 funcallable-standard-instance-access
+                 (setf funcallable-standard-instance-access)))
+
+(defun standard-instance-access (instance location)
+  (clos-slots-ref (std-instance-slots instance) location))
+
+(defun (setf standard-instance-access) (new-value instance location)
+  (setf (clos-slots-ref (std-instance-slots instance) location) new-value))
+
+(defun funcallable-standard-instance-access (instance location)
+  (clos-slots-ref (fsc-instance-slots instance) location))
+
+(defun (setf funcallable-standard-instance-access) (new-value instance location)
+  (setf (clos-slots-ref (fsc-instance-slots instance) location) new-value))
+\f
+;;;; SLOT-VALUE, (SETF SLOT-VALUE), SLOT-BOUNDP, SLOT-MAKUNBOUND
 
 (declaim (ftype (sfunction (t symbol) t) slot-value))
 (defun slot-value (object slot-name)
-  (let* ((class (class-of object))
-         (slot-definition (find-slot-definition class slot-name)))
-    (if (null slot-definition)
-        (values (slot-missing class object slot-name 'slot-value))
-        (slot-value-using-class class object slot-definition))))
+  (let* ((class (check-obsolete-instance/class-of object))
+         (cell (find-slot-cell class slot-name))
+         (location (car cell))
+         (value
+          (cond ((fixnump location)
+                 (if (std-instance-p object)
+                     (standard-instance-access object location)
+                     (funcallable-standard-instance-access object location)))
+                ((consp location)
+                 (cdr location))
+                ((eq t location)
+                 (return-from slot-value
+                   (slot-value-using-class class object (cddr cell))))
+                ((not cell)
+                 (return-from slot-value
+                   (values (slot-missing class object slot-name 'slot-value))))
+                (t
+                 (bug "Bogus slot cell in SLOT-VALUE: ~S" cell)))))
+    (if (eq +slot-unbound+ value)
+        (slot-unbound class object slot-name)
+        value)))
 
 (define-compiler-macro slot-value (&whole form object slot-name
                                    &environment env)
       form))
 
 (defun set-slot-value (object slot-name new-value)
-  (let* ((class (class-of object))
-         (slot-definition (find-slot-definition class slot-name)))
-    (if (null slot-definition)
-        (progn (slot-missing class object slot-name 'setf new-value)
-               new-value)
-        (setf (slot-value-using-class class object slot-definition)
-              new-value))))
+  (let* ((class (check-obsolete-instance/class-of object))
+         (cell (find-slot-cell class slot-name))
+         (location (car cell))
+         (type-check-function (cadr cell)))
+    (when type-check-function
+      (funcall (the function type-check-function) new-value))
+    (cond ((fixnump location)
+           (if (std-instance-p object)
+               (setf (standard-instance-access object location) new-value)
+               (setf (funcallable-standard-instance-access object location)
+                     new-value)))
+          ((consp location)
+           (setf (cdr location) new-value))
+          ((eq t location)
+           (setf (slot-value-using-class class object (cddr cell)) new-value))
+          ((not cell)
+           (slot-missing class object slot-name 'setf new-value))
+          (t
+           (bug "Bogus slot-cell in SET-SLOT-VALUE: ~S" cell))))
+  new-value)
 
 ;;; A version of SET-SLOT-VALUE for use in safe code, where we want to
 ;;; check types when writing to slots:
       form))
 
 (defun slot-boundp (object slot-name)
-  (let* ((class (class-of object))
-         (slot-definition (find-slot-definition class slot-name)))
-    (if (null slot-definition)
-        (not (not (slot-missing class object slot-name 'slot-boundp)))
-        (slot-boundp-using-class class object slot-definition))))
-
-(setf (gdefinition 'slot-boundp-normal) #'slot-boundp)
+  (let* ((class (check-obsolete-instance/class-of object))
+         (cell (find-slot-cell class slot-name))
+         (location (car cell))
+         (value
+          (cond ((fixnump location)
+                 (if (std-instance-p object)
+                     (standard-instance-access object location)
+                     (funcallable-standard-instance-access object location)))
+                ((consp location)
+                 (cdr location))
+                ((eq t location)
+                 (return-from slot-boundp
+                   (slot-boundp-using-class class object (cddr cell))))
+                ((not cell)
+                 (return-from slot-boundp
+                   (and (slot-missing class object slot-name 'slot-boundp) t)))
+                (t
+                 (bug "Bogus slot cell in SLOT-VALUE: ~S" cell)))))
+    (not (eq +slot-unbound+ value))))
 
 (define-compiler-macro slot-boundp (&whole form object slot-name
                                     &environment env)
       form))
 
 (defun slot-makunbound (object slot-name)
-  (let* ((class (class-of object))
-         (slot-definition (find-slot-definition class slot-name)))
-    (if (null slot-definition)
-        (slot-missing class object slot-name 'slot-makunbound)
-        (slot-makunbound-using-class class object slot-definition))
-    object))
+  (let* ((class (check-obsolete-instance/class-of object))
+         (cell (find-slot-cell class slot-name))
+         (location (car cell)))
+    (cond ((fixnump location)
+           (if (std-instance-p object)
+               (setf (standard-instance-access object location) +slot-unbound+)
+               (setf (funcallable-standard-instance-access object location)
+                     +slot-unbound+)))
+          ((consp location)
+           (setf (cdr location) +slot-unbound+))
+          ((eq t location)
+           (slot-makunbound-using-class class object (cddr cell)))
+          ((not cell)
+           (slot-missing class object slot-name 'slot-makunbound))
+          (t
+           (bug "Bogus slot-cell in SLOT-MAKUNBOUND: ~S" cell))))
+  object)
 
 (defun slot-exists-p (object slot-name)
   (let ((class (class-of object)))
   (if (slot-boundp object slot-name)
       (slot-value object slot-name)
       default))
-\f
-(declaim (inline standard-instance-access (setf standard-instance-access)
-                 funcallable-standard-instance-access
-                 (setf funcallable-standard-instance-access)))
-
-(defun standard-instance-access (instance location)
-  (clos-slots-ref (std-instance-slots instance) location))
-
-(defun (setf standard-instance-access) (new-value instance location)
-  (setf (clos-slots-ref (std-instance-slots instance) location) new-value))
-
-(defun funcallable-standard-instance-access (instance location)
-  (clos-slots-ref (fsc-instance-slots instance) location))
-
-(defun (setf funcallable-standard-instance-access) (new-value instance location)
-  (setf (clos-slots-ref (fsc-instance-slots instance) location) new-value))
 
 (defmethod slot-value-using-class ((class std-class)
                                    (object standard-object)
                                    (slotd standard-effective-slot-definition))
+  ;; FIXME: Do we need this? SLOT-VALUE checks for obsolete
+  ;; instances. Are users allowed to call this directly?
   (check-obsolete-instance object)
   (let* ((location (slot-definition-location slotd))
          (value
            (new-value (class std-class)
                       (object standard-object)
                       (slotd standard-effective-slot-definition))
+  ;; FIXME: Do we need this? SET-SLOT-VALUE checks for obsolete
+  ;; instances. Are users allowed to call this directly?
   (check-obsolete-instance object)
   (let ((location (slot-definition-location slotd))
         (type-check-function
            ((class std-class)
             (object standard-object)
             (slotd standard-effective-slot-definition))
+  ;; FIXME: Do we need this? SLOT-BOUNDP checks for obsolete
+  ;; instances. Are users allowed to call this directly?
   (check-obsolete-instance object)
   (let* ((location (slot-definition-location slotd))
          (value
index cdffa52..fcb86c1 100644 (file)
       (add-direct-subclasses class direct-superclasses)
       (let ((slots (compute-slots class)))
         (setf (slot-value class 'slots) slots
-              (slot-value class 'slot-vector) (make-slot-vector slots)))))
+              (slot-value class 'slot-vector) (make-slot-vector class slots)))))
   ;; Comment from Gerd's PCL, 2003-05-15:
   ;;
   ;; We don't ADD-SLOT-ACCESSORS here because we don't want to
     (setf (slot-value class 'cpl-available-p) t)
     (let ((slots (compute-slots class)))
       (setf (slot-value class 'slots) slots
-            (slot-value class 'slot-vector) (make-slot-vector slots)))
+            (slot-value class 'slot-vector) (make-slot-vector class slots)))
     (let ((lclass (find-classoid (class-name class))))
       (setf (classoid-pcl-class lclass) class)
       (setf (slot-value class 'wrapper) (classoid-layout lclass)))
 
       (update-lisp-class-layout class nwrapper)
       (setf (slot-value class 'slots) eslotds
-            (slot-value class 'slot-vector) (make-slot-vector eslotds)
+            (slot-value class 'slot-vector) (make-slot-vector class eslotds)
             (wrapper-instance-slots-layout nwrapper) nlayout
             (wrapper-class-slots nwrapper) nwrapper-class-slots
             (layout-length nwrapper) nslots
index 1e8b2f8..ae181da 100644 (file)
 (defun check-obsolete-instance (instance)
   (when (invalid-wrapper-p (layout-of instance))
     (check-wrapper-validity instance)))
+
+(defun check-obsolete-instance/class-of (instance)
+  (let ((wrapper (wrapper-of instance)))
+    (when (invalid-wrapper-p wrapper)
+      (check-wrapper-validity instance))
+    (wrapper-class* wrapper)))
 \f
 ;;;  NIL: means nothing so far, no actual arg info has NILs in the
 ;;;  metatype.
index d9155ef..e923afc 100644 (file)
 (assert (= (slot-value *yao-super* 'obs) 3))
 (assert (= (slot-value *yao-sub* 'obs) 3))
 
+;;; one more MIO test: variable slot names
+(defclass mio () ((x :initform 42)))
+(defvar *mio-slot* 'x)
+(defparameter *mio-counter* 0)
+(defmethod update-instance-for-redefined-class ((instance mio) new old plist &key)
+  (incf *mio-counter*))
+
+(let ((x (make-instance 'mio)))
+  (make-instances-obsolete 'mio)
+  (slot-value x *mio-slot*))
+
+(let ((x (make-instance 'mio)))
+  (make-instances-obsolete 'mio)
+  (setf (slot-value x *mio-slot*) 13))
+
+(let ((x (make-instance 'mio)))
+  (make-instances-obsolete 'mio)
+  (slot-boundp x *mio-slot*))
+
+(let ((x (make-instance 'mio)))
+  (make-instances-obsolete 'mio)
+  (slot-makunbound x *mio-slot*))
+
+(assert (= 4 *mio-counter*))
+
 ;;; shared -> local slot transfers of inherited slots, reported by
 ;;; Bruno Haible
 (let (i)
index d931dd8..ee9843f 100644 (file)
@@ -17,4 +17,4 @@
 ;;; checkins which aren't released. (And occasionally for internal
 ;;; versions, especially for internal versions off the main CVS
 ;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
-"1.0.9.1"
+"1.0.9.2"