Use descriptor-hash32 only for a mixture of CASE key types

Otherwise, just CHAR-CODE or just SYMBOL-NAME-HASH may be enough.
This commit is contained in:
Douglas Katzman 2025-06-17 00:38:51 +00:00
parent aab89714af
commit 3d5139bef7
5 changed files with 154 additions and 120 deletions

View file

@ -3303,18 +3303,19 @@
(return-from try-perfect-find/position-map conditional))
(when (eq fun-name 'find) ; nothing to do. Wasted some time, no big deal
(return-from try-perfect-find/position-map 'item))) ; transform arg is always named ITEM
(flet ((hash (x) (if (symbolp x) (symbol-name-hash x) (descriptor-hash32 x))))
(binding* ((hashes (map '(simple-array (unsigned-byte 32) (*)) #'hash keys))
(n (length hashes))
(pow2size (power-of-two-ceiling n))
(binding* ((hashfn (prehash-function-for-mph-generator
(minperfhash-key-universe-type keys)))
(hashes (map '(simple-array (unsigned-byte 32) (*)) hashfn keys))
(n (length hashes))
(pow2size (power-of-two-ceiling n))
;; FIXME: I messed up the minimal/non-minimal thing that was
;; trying to simplify the calculation at the expense of a few extra cells.
;; Minimal will always be right.
(minimal t)
(lambda (make-perfect-hash-lambda hashes items minimal) :exit-if-null)
(keyspace-size (if minimal n pow2size))
(domain (sb-xc:make-array keyspace-size :initial-element 0))
(range
(minimal t)
(lambda (make-perfect-hash-lambda hashes items minimal) :exit-if-null)
(keyspace-size (if minimal n pow2size))
(domain (sb-xc:make-array keyspace-size :initial-element 0))
(range
(cond ((eq fun-name 'position)
(sb-xc:make-array keyspace-size
:element-type
@ -3327,9 +3328,9 @@
;; which the user wants (or seems to)
((eq alistp t) domain)
(t (sb-xc:make-array keyspace-size))))
(phashfun (sb-c::compile-perfect-hash lambda hashes)))
(phashfun (sb-c::compile-perfect-hash lambda hashes)))
;; Iteration order doesn't matter here
(maphash (lambda (key val &aux (phash (funcall phashfun (hash key))))
(maphash (lambda (key val &aux (phash (funcall phashfun (funcall hashfn key))))
(cond ((eq alistp t)
(setf (aref domain phash) val)) ; VAL is the (key . val) pair
(t
@ -3375,7 +3376,7 @@
(if minimal
`(if (< phash ,n)
,expr)
expr)))))))))
expr))))))))
(macrolet ((define-find-position (fun-name values-index)
`(deftransform ,fun-name ((item sequence &key

View file

@ -330,36 +330,69 @@
(setf (info :function :compiler-macro-function 'uint32-modularly)
#'sb-c::optimize-for-calc-phash)
;;; Decide what types are in OBJECTS and return {FIXNUM|SYMBOL|CHARACTER|T}.
;;; Perhaps alternatively this could return an OR over types so the consumer
;;; could decide to expand differently in various situations such as
;;; (OR SYMBOL FIXNUM). Because maybe the hash function should be:
;;; (if (symbolp x) (f x) (ldb (byte 32 0) (get-lisp-obj-address)))
;;; which contains exactly 1 type test, and if not symbol, there is no
;;; hazard in extracting low bits of a random descriptor.
;;; On the other hand, that's pretty darn near what happens anyway.
(defun minperfhash-key-universe-type (objects)
(let ((types 0))
(dolist (object objects (case types
(#b001 'fixnum)
(#b010 'symbol)
(#b100 'character)
(0 'nil) ; if OBJECTS is NIL
(t 't)))
(setq types (logior (typecase object
(sb-xc:fixnum #b001)
(symbol #b010)
(character #b100)
(t (return nil)))
types)))))
;;; Construct a form which computes a 32-bit hash from OBJ whose value
;;; should be - but might not be - one of the choices in KEYS.
;;; If it is not, the expression's result should be irrelevant.
;;; (Calling code has to do some kind of "hit" test)
;;; TODO: consider putting in a "miss" label that this code bail out to
;;; if there is an unexpected object type.
;;; The 32-bit hash is then fed into a perfect hash expression.
(defun prehash-expr-for-perfect-hash (obj keys)
(if (notany #'symbolp keys)
`(descriptor-hash32 ,obj)
(multiple-value-bind (guard hashval)
(if (vop-existsp :translate hash-as-if-symbol-name)
(values `(pointerp ,obj) `(hash-as-if-symbol-name ,obj))
;; NON-NULL-SYMBOL-P is the less expensive test as it omits the OR
;; which accepts NIL along with OTHER-POINTER objects.
(values `(,(if (member nil keys) 'symbolp 'non-null-symbol-p) ,obj)
`(symbol-name-hash (truly-the symbol ,obj))))
`(if ,guard
,hashval
,(if (every #'symbolp keys) 0 `(descriptor-hash32 ,obj))))))
(let ((type (minperfhash-key-universe-type keys)))
(ecase type
((t symbol) ; most general hasher or just symbol
(multiple-value-bind (guard hash)
(if (vop-existsp :translate hash-as-if-symbol-name)
(values `(pointerp ,obj) `(hash-as-if-symbol-name ,obj))
;; NON-NULL-SYMBOL-P is the less expensive test as it omits the OR
;; which accepts NIL along with OTHER-POINTER objects.
(values `(,(if (member nil keys) 'symbolp 'non-null-symbol-p) ,obj)
`(symbol-name-hash (truly-the symbol ,obj))))
`(if ,guard ,hash ,(if (eq type 't) `(descriptor-hash32 ,obj) 0))))
(character
`(if (characterp ,obj) (char-code (truly-the character ,obj)) 0))
(fixnum
`(if (fixnump ,obj) (ldb (byte 32 0) (truly-the fixnum ,obj)) 0)))))
;;; This is for use at compile-time, to build the array of uint32_t inputs
;;; to the MPH generator after choosing the manner of prehashing.
(defun prehash-function-for-mph-generator (type)
(case type
((t symbol) ; most general hasher or just symbol
(lambda (x)
(etypecase x
(symbol (symbol-name-hash x))
((or sb-xc:fixnum character) (descriptor-hash32 x)))))
(character #'char-code)
(t (lambda (x) (ldb (byte 32 0) (the sb-xc:fixnum x))))))
;;; The CASE macro can use this predicate to decide whether to expand in a way
;;; that selects a clause via a perfect hash versus the customary expansion
;;; as a sequence of IFs.
(defun perfectly-hashable (objects)
(flet ((hash (x) (if (symbolp x) (symbol-name-hash x) (descriptor-hash32 x))))
(let* ((n (length objects))
(hashes (make-array n :element-type '(unsigned-byte 32))))
(loop for o in objects
for i from 0
do (let ((h (hash o)))
(if h
(setf (aref hashes i) h)
(return-from perfectly-hashable nil))))
(make-perfect-hash-lambda hashes objects))))
(binding* ((type (minperfhash-key-universe-type objects) :exit-if-null)
(hashfn (prehash-function-for-mph-generator type))
(hashes (map '(simple-array (unsigned-byte 32) 1) hashfn objects)))
(make-perfect-hash-lambda hashes objects)))

View file

@ -74,6 +74,40 @@
(let ((b (>> (<< val 28) 30)))
(let ((a (& val #x3)))
(^ a (aref tab b))))))")
(#(A 3C 3F 5B 7B)
"(#\\? #\\{ #\\[ #\\< #\\Newline)"
"((let ((tab #a((4) (unsigned-byte 8) 5 0 0 0)))
(let ((b (& val #x3)))
(let ((a (>> (<< val 25) 30)))
(^ a (aref tab b))))))")
(#(23 27 2B 2C 2D 3A 40 56 76)
"(#\\@ #\\: #\\, #\\' #\\# #\\V #\\v #\\- #\\+)"
"((let ((tab #a((8) (unsigned-byte 8) 0 2 0 0 4 0 10 0)))
(+= val #x1679e37f)
(^= val (>> val 4))
(let ((b (& val #x7)))
(let ((a (>> (u32+ val (<< val 25)) 29)))
(^ a (aref tab b))))))")
(#(24 2F 41 42 43 44 45 46 47 4F 52 53 57 58)
"#(#\\A #\\B #\\C #\\D #\\E #\\F #\\G #\\O #\\R #\\S #\\W #\\X #\\$ #\\/)"
"((let ((tab #a((8) (unsigned-byte 8) 0 5 0 4 12 14 5 10)))
(+= val #xa0fa272d)
(^= val (>> val 4))
(let ((b (& val #x7)))
(let ((a (>> (u32+ val (<< val 26)) 29)))
(^ a (aref tab b))))))")
(#(25 26 28 29 3B 3E 49 54 5F 7C 7E)
"#(#\\I #\\T #\\% #\\& #\\| #\\_ #\\( #\\) #\\; #\\> #\\~)"
"((let ((tab #a((8) (unsigned-byte 8) 11 4 0 2 13 7 0 1)))
(let ((b (& val #x7)))
(let ((a (>> (<< val 25) 29)))
(^ a (aref tab b))))))")
(#(44 45 46 4C 52 53)
"(#\\R #\\L #\\D #\\F #\\S #\\E)"
"((let ((tab #a((4) (unsigned-byte 8) 4 3 0 3)))
(let ((b (& val #x3)))
(let ((a (>> (<< val 27) 30)))
(^ a (aref tab b))))))")
(#(8A 8E 92 96 9A 9E A2 A6 AA AE B2 B6 BA BE C2 C6 CA CE D2 D6)
"(138 210 206 194 190 186 182 178 174 170 166 162 158 154 150 146 142 202 198 214)"
"((let ((tab #a((16) (unsigned-byte 8) 0 13 0 13 0 13 0 13 15 21 3 21 21 11 7 21)))
@ -117,43 +151,9 @@
(#(13E 154 1BE 1D4)
"(318 446 340 468)"
"((& (>> val 6) 3))")
(#(292 F12 FD2 16D2 1ED2)
"(#\\? #\\{ #\\[ #\\< #\\Newline)"
"((let ((tab #a((4) (unsigned-byte 8) 5 0 0 0)))
(let ((b (& (>> val 6) #x3)))
(let ((a (>> (<< val 19) 30)))
(^ a (aref tab b))))))")
(#(8D2 9D2 AD2 B12 B52 E92 1012 1592 1D92)
"(#\\@ #\\: #\\, #\\' #\\# #\\V #\\v #\\- #\\+)"
"((let ((tab #a((8) (unsigned-byte 8) 2 0 3 0 5 7 0 12)))
(+= val #x746675d3)
(^= val (>> val 4))
(let ((b (& (>> val 2) #x7)))
(let ((a (>> (u32+ val (<< val 16)) 29)))
(^ a (aref tab b))))))")
(#(912 BD2 1052 1092 10D2 1112 1152 1192 11D2 13D2 1492 14D2 15D2 1612)
"#(#\\A #\\B #\\C #\\D #\\E #\\F #\\G #\\O #\\R #\\S #\\W #\\X #\\$ #\\/)"
"((let ((tab #a((8) (unsigned-byte 8) 0 0 8 14 11 15 0 4)))
(+= val #x9e67848b)
(^= val (>> val 4))
(let ((b (& (>> val 2) #x7)))
(let ((a (>> (u32+ val (<< val 20)) 29)))
(^ a (aref tab b))))))")
(#(952 992 A12 A52 ED2 F92 1252 1512 17D2 1F12 1F92)
"#(#\\I #\\T #\\% #\\& #\\| #\\_ #\\( #\\) #\\; #\\> #\\~)"
"((let ((tab #a((8) (unsigned-byte 8) 11 4 0 2 13 7 0 1)))
(let ((b (& (>> val 6) #x7)))
(let ((a (>> (<< val 19) 29)))
(^ a (aref tab b))))))")
(#(1000 2000 4000 6000 8000 A000 C000)
"(4096 40960 49152 32768 24576 16384 8192)"
"((& (>> val 13) 7))")
(#(1112 1152 1192 1312 1492 14D2)
"(#\\R #\\L #\\D #\\F #\\S #\\E)"
"((let ((tab #a((4) (unsigned-byte 8) 4 3 0 3)))
(let ((b (& (>> val 6) #x3)))
(let ((a (>> (<< val 21) 30)))
(^ a (aref tab b))))))")
(#(6641B 2D83FFB 3DD2F1F 166E98EA 18F3A192 19485E2B 1FF01E77)
"(:ALLOW-OTHER-KEYS :SLOT-NAMES :METACLASS-CONSTRUCTOR :DD-TYPE :METACLASS-NAME :SUPERCLASS-NAME :CLASS-NAME)"
"((& (^ (>> val 1) (>> val 20)) 7))")

View file

@ -164,6 +164,32 @@
"(8 16 32 64)"
"((let ((b (& (>> val 5) 1)))
(^ (& (>> val 3) 3) b (<< b 1))))")
(#(A 3C 3F 5B 7B)
"(#\\? #\\{ #\\[ #\\< #\\Newline)"
"((let ((tab #a((4) (unsigned-byte 8) 5 0 0 0)))
(let ((b (& val #x3)))
(let ((a (>> (<< val 25) 30)))
(^ a (aref tab b))))))")
(#(23 27 2B 2C 2D 3A 40 56 76)
"(#\\@ #\\: #\\, #\\' #\\# #\\V #\\v #\\- #\\+)"
"((let ((tab #a((8) (unsigned-byte 8) 0 2 0 0 4 0 10 0)))
(+= val #x1679e37f)
(^= val (>> val 4))
(let ((b (& val #x7)))
(let ((a (>> (u32+ val (<< val 25)) 29)))
(^ a (aref tab b))))))")
(#(25 26 28 29 3B 3E 49 54 5F 7C 7E)
"#(#\\I #\\T #\\% #\\& #\\| #\\_ #\\( #\\) #\\; #\\> #\\~)"
"((let ((tab #a((8) (unsigned-byte 8) 11 4 0 2 13 7 0 1)))
(let ((b (& val #x7)))
(let ((a (>> (<< val 25) 29)))
(^ a (aref tab b))))))")
(#(44 45 46 4C 52 53)
"(#\\R #\\L #\\D #\\F #\\S #\\E)"
"((let ((tab #a((4) (unsigned-byte 8) 4 3 0 3)))
(let ((b (& val #x3)))
(let ((a (>> (<< val 27) 30)))
(^ a (aref tab b))))))")
(#(89 8D 91 95 99 9D A1 A5 A9 AD B1 B5 B9 BD C1 C5 C9 CD D1 D5 D9 DD E1 E5)
"(137 221 217 205 201 197 193 189 185 181 177 173 169 165 161 157 153 149 145 141 213 209 229 225)"
"((let ((tab #a((16) (unsigned-byte 8) 31 24 0 13 0 13 0 13 1 12 16 22 16 18 21 22)))
@ -187,32 +213,6 @@
(#(13E 154 1BE 1D4)
"(318 446 340 468)"
"((& (>> val 6) 3))")
(#(14A 78A 7EA B6A F6A)
"(#\\? #\\{ #\\[ #\\< #\\Newline)"
"((let ((tab #a((4) (unsigned-byte 8) 5 0 0 0)))
(let ((b (& (>> val 5) #x3)))
(let ((a (>> (<< val 20) 30)))
(^ a (aref tab b))))))")
(#(46A 4EA 56A 58A 5AA 74A 80A ACA ECA)
"(#\\@ #\\: #\\, #\\' #\\# #\\V #\\v #\\- #\\+)"
"((let ((tab #a((8) (unsigned-byte 8) 13 5 0 4 7 0 6 0)))
(+= val #xeb2f8376)
(^= val (>> val 4))
(let ((b (& (>> val 1) #x7)))
(let ((a (>> (u32+ val (<< val 18)) 29)))
(^ a (aref tab b))))))")
(#(4AA 4CA 50A 52A 76A 7CA 92A A8A BEA F8A FCA)
"#(#\\I #\\T #\\% #\\& #\\| #\\_ #\\( #\\) #\\; #\\> #\\~)"
"((let ((tab #a((8) (unsigned-byte 8) 11 4 0 2 13 7 0 1)))
(let ((b (& (>> val 5) #x7)))
(let ((a (>> (<< val 20) 29)))
(^ a (aref tab b))))))")
(#(88A 8AA 8CA 98A A4A A6A)
"(#\\R #\\L #\\D #\\F #\\S #\\E)"
"((let ((tab #a((4) (unsigned-byte 8) 4 3 0 3)))
(let ((b (& (>> val 5) #x3)))
(let ((a (>> (<< val 22) 30)))
(^ a (aref tab b))))))")
(#(1000 2000 4000 6000 8000 A000 C000)
"(4096 40960 49152 32768 24576 16384 8192)"
"((& (>> val 13) 7))")

View file

@ -212,6 +212,12 @@
"(8 16 32 64)"
"((let ((b (& (>> val 5) 1)))
(^ (& (>> val 3) 3) b (<< b 1))))")
(#(A 3C 3F 5B 7B)
"(#\\? #\\{ #\\[ #\\< #\\Newline)"
"((let ((tab #a((4) (unsigned-byte 8) 5 0 0 0)))
(let ((b (& val #x3)))
(let ((a (>> (<< val 25) 30)))
(^ a (aref tab b))))))")
(#(C D E 1C 1D 1E)
"(28 12 30 29 14 13)"
"((let ((tab #a((4) (unsigned-byte 8) 4 0 2 4)))
@ -235,6 +241,26 @@
(let ((b (& val #x3)))
(let ((a (>> (<< val 26) 30)))
(^ a (aref tab b))))))")
(#(23 27 2B 2C 2D 3A 40 56 76)
"(#\\@ #\\: #\\, #\\' #\\# #\\V #\\v #\\- #\\+)"
"((let ((tab #a((8) (unsigned-byte 8) 0 2 0 0 4 0 10 0)))
(+= val #x1679e37f)
(^= val (>> val 4))
(let ((b (& val #x7)))
(let ((a (>> (u32+ val (<< val 25)) 29)))
(^ a (aref tab b))))))")
(#(25 26 28 29 3B 3E 49 54 5F 7C 7E)
"#(#\\I #\\T #\\% #\\& #\\| #\\_ #\\( #\\) #\\; #\\> #\\~)"
"((let ((tab #a((8) (unsigned-byte 8) 11 4 0 2 13 7 0 1)))
(let ((b (& val #x7)))
(let ((a (>> (<< val 25) 29)))
(^ a (aref tab b))))))")
(#(44 45 46 4C 52 53)
"(#\\R #\\L #\\D #\\F #\\S #\\E)"
"((let ((tab #a((4) (unsigned-byte 8) 4 3 0 3)))
(let ((b (& val #x3)))
(let ((a (>> (<< val 27) 30)))
(^ a (aref tab b))))))")
(#(64 65 66 67 F0 F2 F3)
"(243 242 240 103 102 101 100)"
"((& (^ val (>> val 3)) 7))")
@ -261,35 +287,9 @@
(#(13E 154 1BE 1D4)
"(318 446 340 468)"
"((& (>> val 6) 3))")
(#(528 1E28 1FA8 2DA8 3DA8)
"(#\\? #\\{ #\\[ #\\< #\\Newline)"
"((let ((tab #a((4) (unsigned-byte 8) 5 0 0 0)))
(let ((b (& (>> val 7) #x3)))
(let ((a (>> (<< val 18) 30)))
(^ a (aref tab b))))))")
(#(1000 2000 4000 6000 8000 A000 C000)
"(4096 40960 49152 32768 24576 16384 8192)"
"((& (>> val 13) 7))")
(#(11A8 13A8 15A8 1628 16A8 1D28 2028 2B28 3B28)
"(#\\@ #\\: #\\, #\\' #\\# #\\V #\\v #\\- #\\+)"
"((let ((tab #a((8) (unsigned-byte 8) 5 4 11 2 3 0 1 6)))
(+= val #xebfb0b66)
(^= val (>> val 4))
(let ((b (& (>> val 7) #x7)))
(let ((a (>> (u32+ val (<< val 13)) 29)))
(^ a (aref tab b))))))")
(#(12A8 1328 1428 14A8 1DA8 1F28 24A8 2A28 2FA8 3E28 3F28)
"#(#\\I #\\T #\\% #\\& #\\| #\\_ #\\( #\\) #\\; #\\> #\\~)"
"((let ((tab #a((8) (unsigned-byte 8) 11 4 0 2 13 7 0 1)))
(let ((b (& (>> val 7) #x7)))
(let ((a (>> (<< val 18) 29)))
(^ a (aref tab b))))))")
(#(2228 22A8 2328 2628 2928 29A8)
"(#\\R #\\L #\\D #\\F #\\S #\\E)"
"((let ((tab #a((4) (unsigned-byte 8) 4 3 0 3)))
(let ((b (& (>> val 7) #x3)))
(let ((a (>> (<< val 20) 30)))
(^ a (aref tab b))))))")
(#(D807 DA10 DA20 DA21 DCE8 DE82 DE83)
"(55303 56963 56962 56552 55841 55840 55824)"
"((let ((tab #a((4) (unsigned-byte 8) 6 0 2 7)))