On 07/11/2014 09:08 PM, Doug Lea wrote:

I've been content to just observe Martin and Peter's nice efforts
on this, but one note:

On 07/11/2014 03:00 PM, Martin Buchholz wrote:
On Wed, Jul 9, 2014 at 3:17 PM, Peter Levart <peter.lev...@gmail.com> wrote:


IMH resizing is arranged so that the table is always 33% ... 66% full (if nothing is ever removed from it) except when capacity reaches 2**29, then
it can be filled up to the top.

avg. # of probes can be taken as a rough estimation of average slow-down, max. # of probes can be taken as a rough estimation of worst-case-operation
slow-down.

So where to draw the line?


Linear probing has long been known to be prone to long worst-case probes, but with recent computer architectures this is offset by its extreme cache
friendliness.

Bear in mind that the number of bits of identityHashCode is less
than 32 on all JVMs I know. It can be as low as 23, which means that
you are sure to see a lot of exact collisions on IHMs with only
tens of millions of elements, and there's nothing much you can
do that will help.

-Doug



We have max. size = 2^29 - 1. This can not change because of serialization backwards compatibility. It could be a little less, but not much. Experiment shows that it is possible to fill IHM up to 99% with not too much CPU effort. If such IHM is serialized, we must be able to deserialize it...

But why do we have to limit table length to 2^30 ? Because it has to be power of 2 and 2^31 is already a negative number...
What would happen if max. table length was 2^30 + 2^29 ...

Here's what we get now:

// max. size = 2^29 - 1
// table length = 2^30
 10% max. size, probes:  0.1 avg.,      9 max.
 20% max. size, probes:  0.1 avg.,     15 max.
 30% max. size, probes:  0.2 avg.,     25 max.
 40% max. size, probes:  0.3 avg.,     38 max.
 50% max. size, probes:  0.5 avg.,     64 max.
 60% max. size, probes:  0.7 avg.,     93 max.
 65% max. size, probes:  0.9 avg.,    147 max.
 70% max. size, probes:  1.2 avg.,    224 max.
 75% max. size, probes:  1.5 avg.,    354 max.
 80% max. size, probes:  2.0 avg.,    441 max.
 85% max. size, probes:  2.8 avg.,    789 max.
 90% max. size, probes:  4.5 avg.,   1869 max.
 91% max. size, probes:  5.1 avg.,   2377 max.
 92% max. size, probes:  5.7 avg.,   3409 max.
 93% max. size, probes:  6.6 avg.,   3804 max.
 94% max. size, probes:  7.8 avg.,   5824 max.
 95% max. size, probes:  9.5 avg.,   7021 max.
 96% max. size, probes: 12.0 avg.,  12607 max.
 97% max. size, probes: 16.2 avg.,  17643 max.
 98% max. size, probes: 24.5 avg.,  34561 max.
 99% max. size, probes: 49.6 avg., 131371 max.
100% ... haven't waited long enough....

If table length was 2^30 + 2^29, we would only fill it up to 66% like always and there would be no slow-down:

// max. size = 2^29 - 1
// table length = 2^30 + 2^29
 10% max. size, probes:  0.0 avg.,      7 max.
 20% max. size, probes:  0.1 avg.,     11 max.
 30% max. size, probes:  0.1 avg.,     13 max.
 40% max. size, probes:  0.2 avg.,     20 max.
 50% max. size, probes:  0.3 avg.,     28 max.
 60% max. size, probes:  0.4 avg.,     40 max.
 65% max. size, probes:  0.4 avg.,     42 max.
 70% max. size, probes:  0.5 avg.,     52 max.
 75% max. size, probes:  0.5 avg.,     65 max.
 80% max. size, probes:  0.6 avg.,     87 max.
 85% max. size, probes:  0.7 avg.,     87 max.
 90% max. size, probes:  0.8 avg.,    128 max.
 91% max. size, probes:  0.8 avg.,    128 max.
 92% max. size, probes:  0.8 avg.,    128 max.
 93% max. size, probes:  0.8 avg.,    129 max.
 94% max. size, probes:  0.9 avg.,    129 max.
 95% max. size, probes:  0.9 avg.,    131 max.
 96% max. size, probes:  0.9 avg.,    150 max.
 97% max. size, probes:  0.9 avg.,    150 max.
 98% max. size, probes:  1.0 avg.,    159 max.
 99% max. size, probes:  1.0 avg.,    159 max.
100% max. size, probes:  1.0 avg.,    159 max.


If we're willing to pay the price of special-casing the non-power-of-2 MAX_CAPACITY = (1 << 29) + (1 << 28), which amounts to approx. 4% of performance:

Original code:

Benchmark                Mode   Samples        Score  Score error Units
j.t.IHMBench.ihm0Put    thrpt        30 25217087.593    50117.867 ops/s
j.t.IHMBench.ihm1Get    thrpt        30 43017677.457   230902.599 ops/s

Patched code:

Benchmark                Mode   Samples        Score  Score error Units
j.t.IHMBench.ihm0Put    thrpt        30 24213091.899   122980.408 ops/s
j.t.IHMBench.ihm1Get    thrpt        30 40754537.027   936380.022 ops/s

Using JMH benchmark:

@State(Scope.Thread)
public class IHMBench {

    static final Object[] keys = new Object[1000_000];
    static {
        for (int i = 0; i < keys.length; i++) {
            keys[i] = new Object();
        }
    }

    static final Object value = new Object();

final Map<Object, Object> map = new IdentityHashMap<Object, Object>(keys.length);

    int i = 0;

    @Benchmark
    public void ihm0Put(Blackhole bh) {
        bh.consume(map.put(keys[i], value));
        int j = i + 1;
        i = j < keys.length ? j : 0;
    }

    @Benchmark
    public void ihm1Get(Blackhole bh) {
        bh.consume(map.get(keys[i]));
        int j = i + 1;
        i = j < keys.length ? j : 0;
    }
}


Then here's a possible solution:

http://cr.openjdk.java.net/~plevart/jdk9-dev/IdentityHashMap/webrev.06/


Is it worth it?

Regards, Peter

Reply via email to