source: branches/0.25.x/abcl/src/org/armedbear/lisp/HashTable.java

Last change on this file was 12971, checked in by ehuelsmann, 15 years ago

Small performance improvement for non-EQ hash tables;
don't use comparator when key and HashEntry?.key are the
same object. Along the same lines, compare the keys *only*
if the hash values are equal.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 13.6 KB
Line 
1/*
2 * HashTable.java
3 *
4 * Copyright (C) 2002-2007 Peter Graves
5 * Copyright (C) 2010 Erik Huelsmann
6 * $Id: HashTable.java 12971 2010-10-10 15:48:48Z ehuelsmann $
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 * As a special exception, the copyright holders of this library give you
23 * permission to link this library with independent modules to produce an
24 * executable, regardless of the license terms of these independent
25 * modules, and to copy and distribute the resulting executable under
26 * terms of your choice, provided that you also meet, for each linked
27 * independent module, the terms and conditions of the license of that
28 * module.  An independent module is a module which is not derived from
29 * or based on this library.  If you modify this library, you may extend
30 * this exception to your version of the library, but you are not
31 * obligated to do so.  If you do not wish to do so, delete this
32 * exception statement from your version.
33 */
34package org.armedbear.lisp;
35
36import java.util.concurrent.locks.ReentrantLock;
37import static org.armedbear.lisp.Lisp.*;
38
39public class HashTable extends LispObject {
40
41    protected static final float loadFactor = 0.75f;
42    protected final LispObject rehashSize;
43    protected final LispObject rehashThreshold;
44    // The rounded product of the capacity and the load factor. When the number
45    // of elements exceeds the threshold, the implementation calls rehash().
46    protected int threshold;
47    // Array containing the actual key-value mappings.
48    @SuppressWarnings("VolatileArrayField")
49    protected volatile HashEntry[] buckets;
50    // The number of key-value pairs.
51    protected volatile int count;
52    final Comparator comparator;
53    final private ReentrantLock lock = new ReentrantLock();
54
55    protected HashTable(Comparator c, int size, LispObject rehashSize,
56            LispObject rehashThreshold) {
57        this.rehashSize = rehashSize;
58        this.rehashThreshold = rehashThreshold;
59        buckets = new HashEntry[size];
60        threshold = (int) (size * loadFactor);
61        comparator = c;
62    }
63
64    protected static int calculateInitialCapacity(int size) {
65        int capacity = 1;
66        while (capacity < size) {
67            capacity <<= 1;
68        }
69        return capacity;
70    }
71
72    public static HashTable newEqHashTable(int size, LispObject rehashSize,
73            LispObject rehashThreshold) {
74        return new HashTable(new Comparator(), size, rehashSize, rehashThreshold);
75    }
76
77    public static HashTable newEqlHashTable(int size, LispObject rehashSize,
78            LispObject rehashThreshold) {
79        return new HashTable(new EqlComparator(), size, rehashSize, rehashThreshold);
80    }
81
82    public static HashTable newEqualHashTable(int size, LispObject rehashSize,
83            LispObject rehashThreshold) {
84        return new HashTable(new EqualComparator(), size, rehashSize, rehashThreshold);
85    }
86
87    public static LispObject newEqualpHashTable(int size, LispObject rehashSize,
88            LispObject rehashThreshold) {
89        return new HashTable(new EqualpComparator(), size, rehashSize, rehashThreshold);
90    }
91
92    public final LispObject getRehashSize() {
93        return rehashSize;
94    }
95
96    public final LispObject getRehashThreshold() {
97        return rehashThreshold;
98    }
99
100    public int getSize() {
101        return buckets.length;
102    }
103
104    public int getCount() {
105        return count;
106    }
107
108    @Override
109    public LispObject typeOf() {
110        return Symbol.HASH_TABLE;
111    }
112
113    @Override
114    public LispObject classOf() {
115        return BuiltInClass.HASH_TABLE;
116    }
117
118    @Override
119    public LispObject typep(LispObject type) {
120        if (type == Symbol.HASH_TABLE) {
121            return T;
122        }
123        if (type == BuiltInClass.HASH_TABLE) {
124            return T;
125        }
126        return super.typep(type);
127    }
128
129    @Override
130    public boolean equalp(LispObject obj) {
131        if (this == obj) {
132            return true;
133        }
134        if (obj instanceof HashTable) {
135            HashTable ht = (HashTable) obj;
136            if (count != ht.count) {
137                return false;
138            }
139            if (getTest() != ht.getTest()) {
140                return false;
141            }
142            LispObject entries = ENTRIES();
143            while (entries != NIL) {
144                LispObject entry = entries.car();
145                LispObject key = entry.car();
146                LispObject value = entry.cdr();
147                if (!value.equalp(ht.get(key))) {
148                    return false;
149                }
150                entries = entries.cdr();
151            }
152            return true;
153        }
154        return false;
155    }
156
157    @Override
158    public LispObject getParts() {
159        // No need to take out a read lock, for the same reason as MAPHASH
160        HashEntry[] b = buckets;
161        LispObject parts = NIL;
162        for (int i = 0; i < b.length; i++) {
163            HashEntry e = b[i];
164            while (e != null) {
165                parts = parts.push(new Cons("KEY [bucket " + i + "]", e.key));
166                parts = parts.push(new Cons("VALUE", e.value));
167                e = e.next;
168            }
169        }
170        return parts.nreverse();
171    }
172
173    public void clear() {
174        lock.lock();
175        try {
176            buckets = new HashEntry[buckets.length];
177            count = 0;
178        } finally {
179            lock.unlock();
180        }
181    }
182
183    // gethash key hash-table &optional default => value, present-p
184    public LispObject gethash(LispObject key) {
185        LispObject value = get(key);
186        final LispObject presentp;
187        if (value == null) {
188            value = presentp = NIL;
189        } else {
190            presentp = T;
191        }
192        return LispThread.currentThread().setValues(value, presentp);
193    }
194
195    // gethash key hash-table &optional default => value, present-p
196    public LispObject gethash(LispObject key, LispObject defaultValue) {
197        LispObject value = get(key);
198        final LispObject presentp;
199        if (value == null) {
200            value = defaultValue;
201            presentp = NIL;
202        } else {
203            presentp = T;
204        }
205        return LispThread.currentThread().setValues(value, presentp);
206    }
207
208    public LispObject gethash1(LispObject key) {
209        final LispObject value = get(key);
210        return value != null ? value : NIL;
211    }
212
213    public LispObject puthash(LispObject key, LispObject newValue) {
214        put(key, newValue);
215        return newValue;
216    }
217
218    // remhash key hash-table => generalized-boolean
219    public LispObject remhash(LispObject key) {
220        // A value in a Lisp hash table can never be null, so...
221        return remove(key) != null ? T : NIL;
222    }
223
224    @Override
225    public String writeToString() {
226        if (Symbol.PRINT_READABLY.symbolValue(LispThread.currentThread()) != NIL) {
227            error(new PrintNotReadable(list(Keyword.OBJECT, this)));
228            return null; // Not reached.
229        }
230        StringBuilder sb = new StringBuilder(getTest().writeToString());
231        sb.append(' ');
232        sb.append(Symbol.HASH_TABLE.writeToString());
233        sb.append(' ');
234        sb.append(count);
235        if (count == 1) {
236            sb.append(" entry");
237        } else {
238            sb.append(" entries");
239        }
240        sb.append(", ");
241        sb.append(buckets.length);
242        sb.append(" buckets");
243        return unreadableString(sb.toString());
244    }
245
246    public Symbol getTest() {
247        return comparator.getTest();
248    }
249
250    protected HashEntry getEntry(LispObject key) {
251        HashEntry[] b = buckets;
252        int hash = comparator.hash(key);
253        HashEntry e = b[hash & (b.length - 1)];
254        while (e != null) {
255            if (hash == e.hash &&
256                    (key == e.key || comparator.keysEqual(key, e.key))) {
257                return e;
258            }
259            e = e.next;
260        }
261        return null;
262    }
263
264    public LispObject get(LispObject key) {
265        HashEntry e = getEntry(key);
266        LispObject v = (e == null) ? null : e.value;
267
268        if (e == null || v != null) {
269            return v;
270        }
271
272        lock.lock();
273        try {
274            return e.value;
275        } finally {
276            lock.unlock();
277        }
278    }
279
280    public void put(LispObject key, LispObject value) {
281        lock.lock();
282        try {
283            HashEntry e = getEntry(key);
284            if (e != null) {
285                e.value = value;
286            } else {
287                // Not found. We need to add a new entry.
288                if (++count > threshold) {
289                    rehash();
290                }
291
292                int hash = comparator.hash(key);
293                int index = hash & (buckets.length - 1);
294                buckets[index] = new HashEntry(key, hash, value, buckets[index]);
295            }
296        } finally {
297            lock.unlock();
298        }
299    }
300
301    public LispObject remove(LispObject key) {
302        lock.lock();
303        try {
304            int index = comparator.hash(key) & (buckets.length - 1);
305
306            HashEntry e = buckets[index];
307            HashEntry last = null;
308            while (e != null) {
309                if (comparator.keysEqual(key, e.key)) {
310                    if (last == null) {
311                        buckets[index] = e.next;
312                    } else {
313                        last.next = e.next;
314                    }
315                    --count;
316                    return e.value;
317                }
318                last = e;
319                e = e.next;
320            }
321            return null;
322        } finally {
323            lock.unlock();
324        }
325    }
326
327    protected void rehash() {
328        lock.lock();
329        try {
330            final int newCapacity = buckets.length * 2;
331            threshold = (int) (newCapacity * loadFactor);
332            int mask = newCapacity - 1;
333            HashEntry[] newBuckets = new HashEntry[newCapacity];
334
335            for (int i = buckets.length; i-- > 0;) {
336                HashEntry e = buckets[i];
337                while (e != null) {
338                    final int index = comparator.hash(e.key) & mask;
339                    newBuckets[index] = new HashEntry(e.key, e.hash, e.value,
340                            newBuckets[index]);
341                    e = e.next;
342                }
343            }
344            buckets = newBuckets;
345        } finally {
346            lock.unlock();
347        }
348    }
349
350    // Returns a list of (key . value) pairs.
351    public LispObject ENTRIES() {
352        // No need to take out a read lock, for the same reason as MAPHASH
353        HashEntry[] b = buckets;
354        LispObject list = NIL;
355        for (int i = b.length; i-- > 0;) {
356            HashEntry e = b[i];
357            while (e != null) {
358                list = new Cons(new Cons(e.key, e.value), list);
359                e = e.next;
360            }
361        }
362        return list;
363    }
364
365    public LispObject MAPHASH(LispObject function) {
366        // Don't take out a read lock: it can't be upgraded to a write
367        // lock, which would block the scenario where put() is called to
368        // set the value of the current entry
369
370        HashEntry[] b = buckets;
371        for (int i = b.length; i-- > 0;) {
372            HashEntry e = b[i];
373            while (e != null) {
374                function.execute(e.key, e.value);
375                e = e.next;
376            }
377        }
378        return NIL;
379    }
380
381    protected static class Comparator {
382
383        Symbol getTest() {
384            return Symbol.EQ;
385        }
386
387        boolean keysEqual(LispObject key1, LispObject key2) {
388            return key1 == key2;
389        }
390
391        int hash(LispObject key) {
392            return key.sxhash();
393        }
394    }
395
396    protected static class EqlComparator extends Comparator {
397
398        @Override
399        Symbol getTest() {
400            return Symbol.EQL;
401        }
402
403        @Override
404        boolean keysEqual(LispObject key1, LispObject key2) {
405            return key1.eql(key2);
406        }
407    }
408
409    protected static class EqualComparator extends Comparator {
410
411        @Override
412        Symbol getTest() {
413            return Symbol.EQUAL;
414        }
415
416        @Override
417        boolean keysEqual(LispObject key1, LispObject key2) {
418            return key1.equal(key2);
419        }
420    }
421
422    protected static class EqualpComparator extends Comparator {
423
424        @Override
425        Symbol getTest() {
426            return Symbol.EQUALP;
427        }
428
429        @Override
430        boolean keysEqual(LispObject key1, LispObject key2) {
431            return key1.equalp(key2);
432        }
433
434        @Override
435        int hash(LispObject key) {
436            return key.psxhash();
437        }
438    }
439
440    protected static class HashEntry {
441
442        LispObject key;
443        int hash;
444        volatile LispObject value;
445        HashEntry next;
446
447        HashEntry(LispObject key, int hash, LispObject value, HashEntry next) {
448            this.key = key;
449            this.hash = hash;
450            this.value = value;
451            this.next = next;
452        }
453    }
454
455    // For EQUALP hash tables.
456    @Override
457    public int psxhash() {
458        long result = 2062775257; // Chosen at random.
459        result = mix(result, count);
460        result = mix(result, getTest().sxhash());
461        return (int) (result & 0x7fffffff);
462    }
463}
Note: See TracBrowser for help on using the repository browser.