ClassValue perf?

Peter Levart peter.levart at gmail.com
Mon May 30 15:59:54 UTC 2016


Hi Michael,

In the meantime I improved LinearProbeHashtable as I identified a 
weakness in its implementation. In general, if an entry for the same key 
was inserted and removed repeatably, then each such repetition planted a 
"tombstone" at a position of inserted key which means that lookup 
performance for such key deteriorated with each such 
removal/re-insertion until the table was rehashed.

The main trick with LinearProbeHashtable to be able to have lock-free 
lookups is in entries (key/value pairs) that don't move when other 
entries are added or removed from the table. Normally (for example in 
IdentityHashMap that is a similar structure) when an entry is removed 
from linear-probe hash table, entries following it that have their hash 
code point to a point preceeding their position in table, are moved up 
to fill the gap. But such movement is not possible to achieve in 
multithreaded operation without disturbing the lock-free lookup in a way 
that makes it non-functional. So instead of moving the following entries 
up, my implementation plants a "tombstone" key and clears the associated 
value. Such tombstone keys are skipped in lookups, but consider the 
following situation:

hashtable.put(keyX, value1); hastable.remove(keyX);
hashtable.put(keyX, value2); hastable.remove(keyX);
hashtable.put(keyX, value3); hastable.remove(keyX);
hashtable.put(keyX, value4);

// after above modifications and in case they didn't trigger rehashing,
// hashtable.get(keyX) encounters the following state:

i = firstIndex(keyX, table.length);
table[i+0] == Tombstone
table[i+1] == null
table[i+2] == Tombstone
table[i+3] == null
table[i+4] == Tombstone
table[i+5] == null
table[i+6] == keyX
table[i+7] == value4


...so the lookup for keyX must probe 3 tombstones before reaching to the 
matched key. ClassValue usage does not encounter such situations as 
ClassValue.remove actually maps to LinearProbeHashtable.replace(key, 
oldValue, new Removed()) because ClassValue must also track "versions" 
of removals. Where ClassValue calls LinearProbeHashtable.remove is when 
expunging stale keys and each such removed key is never inserted again.

But if LinearProbeHashtable is to be used elsewhere too, we must not 
allow periodic removal/reinsertion of the same key to affect performance 
of lookups.

The solution I discovered was for tombstones to maintain the number of 
consecutive tombstones following the one, so probing can skip an entire 
run of consecutive tombstones in one go. Te above example of repeated 
modifications results in the following state:

i = firstIndex(keyX, table.length);
table[i+0] == Tombstone(skip=3)
table[i+1] == null
table[i+2] == Tombstone(skip=2)
table[i+3] == null
table[i+4] == Tombstone(skip=1)
table[i+5] == null
table[i+6] == keyX
table[i+7] == value4

When lookup encounters a tombstone it can read from it the number of key 
slots to skip. Simple.

Another improvement is in the policy of rehashing. Rehashing is 
triggered just before a new entry is inserted into the table that would 
violate the maximum load factor of 2/3. Tombstones also count as 
occupying the slots, of course, but are removed in rehashing. Rehashing 
can therefore also shrink the table depending on how many tombstones it 
contains. But shrinking it to the minimum length that still satisfies 
the maximum load factor of 2/3 is not the best policy as it can result 
in frequent oscillations around the tipping point that result in 
repeated rehashing. So current strategy reallocates a table in a way 
that guarantees, in worst case, that at least size()/2 entries can get 
inserted into the table before it needs another rehashing, but at the 
same time does not force table to grow any faster when entries are 
inserted only.

I also employed get-acquire/put-release memory ordering semantics 
instead of SC (volatile) in hope that it might improve a bit the 
performance on platforms such as PowerPC or ARM, but this can be changed 
back to SC if anyone gets scared of it :-)

Here's the improved code (only LinearProbeHashtable changed compared to 
webrev.04):

http://cr.openjdk.java.net/~plevart/misc/ClassValue.Alternative2/webrev.04.1/

Here are the benchmark results comparing original JDK9 ClassValue with 
this patch:

http://cr.openjdk.java.net/~plevart/misc/ClassValue.Alternative2/webrev.04.1.bench_results.txt

...which show that it still has a slight (performance of lookup) to big 
(footprint and expunging) advantage over present ClassValue in this 
incarnation too.



Michael, could you re-submit this version into internal testing too? I 
think this modification is essential.


Regards, Peter


On 05/30/2016 10:00 AM, Michael Haupt wrote:
> Hi Peter,
>
> the internal tests look good. I'll assign the issue over to you. Thanks!
>
> Best,
>
> Michael
>
>> Am 26.05.2016 um 14:42 schrieb Michael Haupt 
>> <michael.haupt at oracle.com <mailto:michael.haupt at oracle.com>>:
>>
>> Hi Peter,
>>
>> thank you for this wonderful piece of work.
>>
>>> Am 26.05.2016 um 10:59 schrieb Peter Levart <peter.levart at gmail.com 
>>> <mailto:peter.levart at gmail.com>>:
>>> How does this implementation compare on your hardware, Michael?
>>
>> Results attached. It improves on the unpatched version in all cases, 
>> is in most cases even faster than the "simple solution" (reduce 
>> initial size to 1), and reduces complexity of ClassValue. It passes 
>> all open and closed jli-related tests as well as the Nashorn tests. 
>> Looking really good.
>>
>> Let me run the full internal test suite across platforms.
>>
>> Best,
>>
>> Michael
>
>
> -- 
>
> Oracle <http://www.oracle.com/>
> Dr. Michael Haupt | Principal Member of Technical Staff
> Phone: +49 331 200 7277 | Fax: +49 331 200 7561
> OracleJava Platform Group | LangTools Team | Nashorn
> Oracle Deutschland B.V. & Co. KG | Schiffbauergasse 14 | 14467 
> Potsdam, Germany
>
> ORACLE Deutschland B.V. & Co. KG | Hauptverwaltung: Riesstraße 25, 
> D-80992 München
> Registergericht: Amtsgericht München, HRA 95603
>
> Komplementärin: ORACLE Deutschland Verwaltung B.V. | Hertogswetering 
> 163/167, 3543 AS Utrecht, Niederlande
> Handelsregister der Handelskammer Midden-Nederland, Nr. 30143697
> Geschäftsführer: Alexander van der Ven, Jan Schultheiss, Val Maher
> Green Oracle <http://www.oracle.com/commitment> 	Oracle is committed 
> to developing practices and products that help protect the environment
>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/mlvm-dev/attachments/20160530/5a29df42/attachment.html>


More information about the mlvm-dev mailing list