Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-17 Thread Joshua Cao
On Fri, 12 Jan 2024 08:51:16 GMT, Volker Simonis  wrote:

>>> tryPresize(int size) is doing and if its size argument is supposed to 
>>> contain the additional number of elements which will be inserted into the 
>>> hash map or if it is a hint for the new total size of the hash map
>> 
>> Argument `size` for `tryPresize()` is a hint for the **new total size** of 
>> the hash map. If the size is too small compared to the current size of the 
>> map, there will be no resizing.
>
> Thanks. In that case, calling `tryPresize()` with `size < this.size()` 
> doesn't make sense and we should call it with `this.size() + m.size()`.

Thanks. Just committed this change. Also added a new benchmark for `putAll()` 
and the results in the top post.

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17116#discussion_r1456490539


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-12 Thread Volker Simonis
On Thu, 11 Jan 2024 19:21:00 GMT, Joshua Cao  wrote:

>>> We don't need to compute max() here. 
>>> [tryPresize()](https://github.com/openjdk/jdk/blob/8a4dc79e1a40e7115e2971af81623b6b0368f41c/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java#L2397)
>>>  does that already.
>> 
>> The code you reference is only executed if `table == null` but in this case 
>> we know it is not `null` because we only call `tryPresize()` if `table != 
>> null`.
>> 
>> In general, it's hard fro me to understand what `tryPresize(int size)` is 
>> doing and if its `size` argument is supposed to contain the additional 
>> number of elements which will be inserted into the hash map or if it is a 
>> hint for the new total size of the hash map. Can you explain?
>
>> tryPresize(int size) is doing and if its size argument is supposed to 
>> contain the additional number of elements which will be inserted into the 
>> hash map or if it is a hint for the new total size of the hash map
> 
> Argument `size` for `tryPresize()` is a hint for the **new total size** of 
> the hash map. If the size is too small compared to the current size of the 
> map, there will be no resizing.

Thanks. In that case, calling `tryPresize()` with `size < this.size()` doesn't 
make sense and we should call it with `this.size() + m.size()`.

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17116#discussion_r1450063503


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-11 Thread Joshua Cao
On Thu, 11 Jan 2024 09:40:07 GMT, Volker Simonis  wrote:

> tryPresize(int size) is doing and if its size argument is supposed to contain 
> the additional number of elements which will be inserted into the hash map or 
> if it is a hint for the new total size of the hash map

Argument `size` for `tryPresize()` is a hint for the **new total size** of the 
hash map. If the size is too small compared to the current size of the map, 
there will be no resizing.

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17116#discussion_r1449296202


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-11 Thread Volker Simonis
On Mon, 8 Jan 2024 20:27:59 GMT, Joshua Cao  wrote:

> We don't need to compute max() here. 
> [tryPresize()](https://github.com/openjdk/jdk/blob/8a4dc79e1a40e7115e2971af81623b6b0368f41c/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java#L2397)
>  does that already.

The code you reference is only executed if `table == null` but in this case we 
know it is not `null` because we only call `tryPresize()` if `table != null`.

In general, it's hard fro me to understand what `tryPresize(int size)` is doing 
and if its `size` argument is supposed to contain the additional number of 
elements which will be inserted into the hash map or if it is a hint for the 
new total size of the hash map. Can you explain?

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17116#discussion_r1448566797


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-08 Thread Joshua Cao
On Mon, 8 Jan 2024 15:59:17 GMT, Volker Simonis  wrote:

> Shouldn't this be something like tryPresize(this.size() + m.size()) to 
> accommodate for the worst case where there are no common keys in this map and 
> m?

Its a good callout. Not sure if it is a miss, or intentionally conservative.

> But then shouldn't we use at least max(this.size(), m.size)?

We don't need to compute `max()` here. 
[tryPresize()](https://github.com/openjdk/jdk/blob/8a4dc79e1a40e7115e2971af81623b6b0368f41c/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java#L2397)
 does that already. Although this does bring to my attention that maybe there 
is the same symptom of "extra unnecessary work" when merging in a smaller map. 
If the new size is smaller, we should just be able to exit out of 
`tryPresize()` without having to call the expensive `transfer()`. We would need 
to run a separate experiment for it.

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17116#discussion_r1445292956


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-08 Thread Volker Simonis
On Wed, 3 Jan 2024 21:29:57 GMT, Joshua Cao  wrote:

>> ConcurrentHashMap's copy constructor calls `putAll()` -> `tryPresize()` -> 
>> `transfer()`. When coming from the copy constructor, the Map is empty, so 
>> there is nothing to transfer. But `transfer()` will still copy all the empty 
>> nodes from the old table to the new table.
>> 
>> This patch avoids this work by only calling `tryPresize()` if the table is 
>> already initialized. If `table` is null, the initialization is deferred to 
>> `putVal()`, which calls `initTable()`.
>> 
>> ---
>> 
>> ### JMH results for testCopyConstructor
>> 
>> before patch:
>> 
>> 
>> Result "org.openjdk.bench.java.util.concurrent.Maps.testCopyConstructor":
>>   937395.686 ±(99.9%) 99074.324 ns/op [Average]
>>   (min, avg, max) = (825732.550, 937395.686, 1072024.041), stdev = 92674.184
>>   CI (99.9%): [838321.362, 1036470.010] (assumes normal distribution)
>> 
>> 
>> after patch:
>> 
>> 
>> Result "org.openjdk.bench.java.util.concurrent.Maps.testCopyConstructor":
>>   620871.469 ±(99.9%) 59195.406 ns/op [Average]
>>   (min, avg, max) = (545304.633, 620871.469, 689013.573), stdev = 55371.419
>>   CI (99.9%): [561676.063, 680066.875] (assumes normal distribution)
>> 
>> 
>> Average time is decreased by about 33%.
>
> Joshua Cao has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   Cleanup benchmark

src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java line 
1088:

> 1086: public void putAll(Map m) {
> 1087: if (table != null) {
> 1088: tryPresize(m.size());

Shouldn't this be something like `tryPresize(this.size() + m.size())` to 
accommodate for the worst case where there are no common keys in `this` map and 
`m`?

Or do we intentionally try to be  conservative with memory usage? But then 
shouldn't we use at least `max(this.size(), m.size)`?

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17116#discussion_r1444898430


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-04 Thread Aleksey Shipilev
On Wed, 3 Jan 2024 21:29:57 GMT, Joshua Cao  wrote:

>> ConcurrentHashMap's copy constructor calls `putAll()` -> `tryPresize()` -> 
>> `transfer()`. When coming from the copy constructor, the Map is empty, so 
>> there is nothing to transfer. But `transfer()` will still copy all the empty 
>> nodes from the old table to the new table.
>> 
>> This patch avoids this work by only calling `tryPresize()` if the table is 
>> already initialized. If `table` is null, the initialization is deferred to 
>> `putVal()`, which calls `initTable()`.
>> 
>> ---
>> 
>> ### JMH results for testCopyConstructor
>> 
>> before patch:
>> 
>> 
>> Result "org.openjdk.bench.java.util.concurrent.Maps.testCopyConstructor":
>>   937395.686 ±(99.9%) 99074.324 ns/op [Average]
>>   (min, avg, max) = (825732.550, 937395.686, 1072024.041), stdev = 92674.184
>>   CI (99.9%): [838321.362, 1036470.010] (assumes normal distribution)
>> 
>> 
>> after patch:
>> 
>> 
>> Result "org.openjdk.bench.java.util.concurrent.Maps.testCopyConstructor":
>>   620871.469 ±(99.9%) 59195.406 ns/op [Average]
>>   (min, avg, max) = (545304.633, 620871.469, 689013.573), stdev = 55371.419
>>   CI (99.9%): [561676.063, 680066.875] (assumes normal distribution)
>> 
>> 
>> Average time is decreased by about 33%.
>
> Joshua Cao has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   Cleanup benchmark

I am good this this version. I would like a second Reviewer to ack this too.

-

Marked as reviewed by shade (Reviewer).

PR Review: https://git.openjdk.org/jdk/pull/17116#pullrequestreview-1803734314


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-03 Thread Joshua Cao
On Wed, 3 Jan 2024 19:52:34 GMT, Joshua Cao  wrote:

>> test/micro/org/openjdk/bench/java/util/concurrent/Maps.java line 122:
>> 
>>> 120: public void testCopyConstructor() {
>>> 121: ConcurrentHashMap clone = new 
>>> ConcurrentHashMap<>(staticMap);
>>> 122: dummy = clone;
>> 
>> Is this for preventing dead-code elimination? If so, just do:
>> 
>> 
>>   return new ConcurrentHashMap<>(staticMap);
>
> I tried this. I don't think its working. When I just return the map, we lose 
> the improvements in performance, and the benchmark overall just runs much 
> faster. I'm guessing the map got DCE'ed

Scratch that. Was my mistake. Was running the wrong test.

I'm renaming `testCopyConstructor` to `testConcurrentHashMapCopyConstructor`, 
since the file name + test don't actually explicitly say its for 
ConcurrentHashMap.

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17116#discussion_r1440968465


Re: RFR: 8322149: ConcurrentHashMap copy constructor should not transfer from old table on presizing [v2]

2024-01-03 Thread Joshua Cao
> ConcurrentHashMap's copy constructor calls `putAll()` -> `tryPresize()` -> 
> `transfer()`. When coming from the copy constructor, the Map is empty, so 
> there is nothing to transfer. But `transfer()` will still copy all the empty 
> nodes from the old table to the new table.
> 
> This patch avoids this work by only calling `tryPresize()` if the table is 
> already initialized. If `table` is null, the initialization is deferred to 
> `putVal()`, which calls `initTable()`.
> 
> ---
> 
> ### JMH results for testCopyConstructor
> 
> before patch:
> 
> 
> Result "org.openjdk.bench.java.util.concurrent.Maps.testCopyConstructor":
>   937395.686 ±(99.9%) 99074.324 ns/op [Average]
>   (min, avg, max) = (825732.550, 937395.686, 1072024.041), stdev = 92674.184
>   CI (99.9%): [838321.362, 1036470.010] (assumes normal distribution)
> 
> 
> after patch:
> 
> 
> Result "org.openjdk.bench.java.util.concurrent.Maps.testCopyConstructor":
>   620871.469 ±(99.9%) 59195.406 ns/op [Average]
>   (min, avg, max) = (545304.633, 620871.469, 689013.573), stdev = 55371.419
>   CI (99.9%): [561676.063, 680066.875] (assumes normal distribution)
> 
> 
> Average time is decreased by about 33%.

Joshua Cao has updated the pull request incrementally with one additional 
commit since the last revision:

  Cleanup benchmark

-

Changes:
  - all: https://git.openjdk.org/jdk/pull/17116/files
  - new: https://git.openjdk.org/jdk/pull/17116/files/93b9072d..3632649c

Webrevs:
 - full: https://webrevs.openjdk.org/?repo=jdk&pr=17116&range=01
 - incr: https://webrevs.openjdk.org/?repo=jdk&pr=17116&range=00-01

  Stats: 12 lines in 2 files changed: 2 ins; 7 del; 3 mod
  Patch: https://git.openjdk.org/jdk/pull/17116.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/17116/head:pull/17116

PR: https://git.openjdk.org/jdk/pull/17116