Taoufik Dachraoui
6/11/2015 9:03:00 PM
On Wednesday, June 10, 2015 at 8:25:29 PM UTC+2, Taoufik Dachraoui wrote:
> Hi
>
> Sometime ago (2012) I implemented a hashtable algorithm using a tree
>
> it is fast (and faster than ccl hashtables when using make-hash-table in a loop)
>
> For example, parsing an arithmetic expression with 1,000,000
> integers:
> 1. using ccl hashtable
> ? (test-calc 1000000)
> (CALC8 (GEN-EXPR N))
> took 120,577 milliseconds (120.577 seconds) to run.
> 16,112 milliseconds ( 16.112 seconds, 13.36%) of which was spent in GC.
> During that period, and with 2 available CPU cores,
> 73,890 milliseconds ( 73.890 seconds) were spent in user mode
> 46,282 milliseconds ( 46.282 seconds) were spent in system mode
> 6,768,400,432 bytes of memory allocated.
> 7,280 minor page faults, 0 major page faults, 0 swaps.
> (T 6953416 999999)
>
> 2. using my hashtable tree based implementation
> ? (test-calc 1000000)
> (CALC8 (GEN-EXPR N))
> took 36,012 milliseconds (36.012 seconds) to run.
> 6,053 milliseconds ( 6.053 seconds, 16.81%) of which was spent in GC.
> During that period, and with 2 available CPU cores,
> 35,170 milliseconds (35.170 seconds) were spent in user mode
> 645 milliseconds ( 0.645 seconds) were spent in system mode
> 2,677,967,216 bytes of memory allocated.
> 7,072 minor page faults, 0 major page faults, 0 swaps.
> (T 6950988 999999)
>
>
> With the tree based hashtable the CACL8 is 3 times faster
>
> I would like to add the tree base hashtable to quicklisp (if possible), so anyone can use it
> and improve it (the locking for multithreaded use is not yet implemented, we need to implement
> #_bitlock and #_bitfree for maximum performance)
>
> how to add the tree based hashtable to quicklisp? if someone can help i appreciate it
> very much
>
> Kind regards
> Taoufik
Hi
The following is an example of using the tree-based hashtable implementation, and showing the average length of the collision lists (also the performance compared to CL hashtable)
;;; gen-obj returns a random list of integers
? (gen-obj)
(665 42 280 313 827 368 38 574 563 715 683 633 218 50 760 834 641 359 469 651 333 114 979 36 478 573 54 372 865 286 174 611 585 399 943 490 332 517 338 645 4 675 663 901 692 385 971 397 322 158 879 264 567 681 72 928 731 748 212 251 208 350 868 99 340 89 577 903 238 506 411 44 516 94 866 331 570 650 179)
? (gen-obj)
(229 963 766 708 931 392 987 567 807 404 576 242 969 22 841 227 221 676 163 141 342 945 153 912 20 741 246 60 427 360 412 423)
? (gen-obj)
(283)
? (setf h (make-hashtable))
T
? (time (dotimes (i 100000) (setf (getkey (gen-obj) h) t)))
(DOTIMES (I 100000) (SETF (GETKEY (GEN-OBJ) H) T))
took 2,423 milliseconds (2.423 seconds) to run.
1,792 milliseconds (1.792 seconds, 73.96%) of which was spent in GC.
During that period, and with 2 available CPU cores,
2,360 milliseconds (2.360 seconds) were spent in user mode
54 milliseconds (0.054 seconds) were spent in system mode
56,386,160 bytes of memory allocated.
10,821 minor page faults, 0 major page faults, 0 swaps.
NIL
? (length (hashvalues h))
100000
? (length (hashvalues *hashcodes*)) ; different number of hash codes used to create 100000 keys
99402
? (* 1.0 *average-collisions*)
1.0002214
? (setf h (make-hash-table))
#<HASH-TABLE :TEST EQL size 0/60 #x12A3441E>
? (time (dotimes (i 100000) (setf (gethash (gen-obj) h) t)))
(DOTIMES (I 100000) (SETF (GETHASH (GEN-OBJ) H) T))
took 4,225 milliseconds (4.225 seconds) to run.
1,764 milliseconds (1.764 seconds, 41.75%) of which was spent in GC.
During that period, and with 2 available CPU cores,
4,153 milliseconds (4.153 seconds) were spent in user mode
70 milliseconds (0.070 seconds) were spent in system mode
146,951,992 bytes of memory allocated.
6,340 minor page faults, 0 major page faults, 0 swaps.
Do not know how to determine the average length of collision lists in CL HT
- collision lists are of length 1 in average
- tree-based implementation is faster than CL hashtable
- maphash is faster than hashvalues (I need to optimize it further)
? (time (progn (hashvalues h) t))
(PROGN (HASHVALUES H) T)
took 41 milliseconds (0.041 seconds) to run.
24 milliseconds (0.024 seconds, 58.54%) of which was spent in GC.
During that period, and with 2 available CPU cores,
39 milliseconds (0.039 seconds) were spent in user mode
2 milliseconds (0.002 seconds) were spent in system mode
1,946,304 bytes of memory allocated.
282 minor page faults, 0 major page faults, 0 swaps.
T
? (time (maphash #'(lambda (k v) (declare (ignore k v))) h))
(MAPHASH #'(LAMBDA (K V) (DECLARE (IGNORE K V))) H)
took 13 milliseconds (0.013 seconds) to run.
8 milliseconds (0.008 seconds, 61.54%) of which was spent in GC.
During that period, and with 2 available CPU cores,
12 milliseconds (0.012 seconds) were spent in user mode
0 milliseconds (0.000 seconds) were spent in system mode
800,016 bytes of memory allocated.
218 minor page faults, 0 major page faults, 0 swaps.
The performance difference is even greater if you use make-hash-table within a loop
But, I should say that the implementation is very basic, it may not be comparable to cl ht;
I wish to implement weak-hash-tables in near future (I do not have any idea how to do it for now)
-Taoufik