RFR: [6904367]: (coll) IdentityHashMap is resized before exceeding the expected maximum size
Peter Levart
peter.levart at gmail.com
Sat Jul 12 15:47:34 UTC 2014
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.levart at 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
More information about the core-libs-dev
mailing list