RFR: 8342382: Implementation of JEP G1: Improve Application Throughput with a More Efficient Write-Barrier [v9]

Ivan Walulya iwalulya at openjdk.org
Tue Mar 4 11:19:59 UTC 2025


On Tue, 4 Mar 2025 09:57:56 GMT, Thomas Schatzl <tschatzl at openjdk.org> wrote:

>> Hi all,
>> 
>>   please review this change that implements (currently Draft) JEP: G1: Improve Application Throughput with a More Efficient Write-Barrier.
>> 
>> The reason for posting this early is that this is a large change, and the JEP process is already taking very long with no end in sight but we would like to have this ready by JDK 25.
>> 
>> ### Current situation
>> 
>> With this change, G1 will reduce the post write barrier to much more resemble Parallel GC's as described in the JEP. The reason is that G1 lacks in throughput compared to Parallel/Serial GC due to larger barrier.
>> 
>> The main reason for the current barrier is how g1 implements concurrent refinement:
>> * g1 tracks dirtied cards using sets (dirty card queue set - dcqs) of buffers (dirty card queues - dcq) containing the location of dirtied cards. Refinement threads pick up their contents to re-refine. The barrier needs to enqueue card locations.
>> * For correctness dirty card updates requires fine-grained synchronization between mutator and refinement threads,
>> * Finally there is generic code to avoid dirtying cards altogether (filters), to avoid executing the synchronization and the enqueuing as much as possible.
>> 
>> These tasks require the current barrier to look as follows for an assignment `x.a = y` in pseudo code:
>> 
>> 
>> // Filtering
>> if (region(@x.a) == region(y)) goto done; // same region check
>> if (y == null) goto done;     // null value check
>> if (card(@x.a) == young_card) goto done;  // write to young gen check
>> StoreLoad;                // synchronize
>> if (card(@x.a) == dirty_card) goto done;
>> 
>> *card(@x.a) = dirty
>> 
>> // Card tracking
>> enqueue(card-address(@x.a)) into thread-local-dcq;
>> if (thread-local-dcq is not full) goto done;
>> 
>> call runtime to move thread-local-dcq into dcqs
>> 
>> done:
>> 
>> 
>> Overall this post-write barrier alone is in the range of 40-50 total instructions, compared to three or four(!) for parallel and serial gc.
>> 
>> The large size of the inlined barrier not only has a large code footprint, but also prevents some compiler optimizations like loop unrolling or inlining.
>> 
>> There are several papers showing that this barrier alone can decrease throughput by 10-20% ([Yang12](https://dl.acm.org/doi/10.1145/2426642.2259004)), which is corroborated by some benchmarks (see links).
>> 
>> The main idea for this change is to not use fine-grained synchronization between refinement and mutator threads, but coarse grained based on atomically switching c...
>
> Thomas Schatzl has updated the pull request incrementally with one additional commit since the last revision:
> 
>   * iwalulya review 2
>     * G1ConcurrentRefineWorkState -> G1ConcurrentRefineSweepState
>     * some additional documentation

src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp line 108:

> 106: 
> 107: void G1ConcurrentRefineThreadControl::control_thread_do(ThreadClosure* tc) {
> 108:   if (_control_thread != nullptr) {

maybe maintain using `if (max_num_threads() > 0)` as used in `G1ConcurrentRefineThreadControl::initialize`, so that it is clear that setting `G1ConcRefinementThreads=0` effectively turns off concurrent refinement.

src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp line 354:

> 352:       if (!r->is_free()) {
> 353:         // Need to scan all parts of non-free regions, so reset the claim.
> 354:         // No need for synchronization: we are only interested about regions

s/about/in

src/hotspot/share/gc/g1/g1OopClosures.hpp line 205:

> 203:   G1CollectedHeap* _g1h;
> 204:   uint _worker_id;
> 205:   bool _has_to_cset_ref;

Similar to `_cards_refer_to_cset` , do you mind renaming `_has_to_cset_ref`  and `_has_to_old_ref`  to `_has_ref_to_cset` and `_has_ref_to_old`

src/hotspot/share/gc/g1/g1Policy.hpp line 105:

> 103:   uint _free_regions_at_end_of_collection;
> 104: 
> 105:   size_t _pending_cards_from_gc;

A comment on the variable would be nice, especially on how it is set/reset both at end of GC and by refinement. Also the `_to_collection_set_cards` below could use a comment

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/23739#discussion_r1979077904
PR Review Comment: https://git.openjdk.org/jdk/pull/23739#discussion_r1979102189
PR Review Comment: https://git.openjdk.org/jdk/pull/23739#discussion_r1979212854
PR Review Comment: https://git.openjdk.org/jdk/pull/23739#discussion_r1979155941


More information about the core-libs-dev mailing list