New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8231672: Simplify the reference processing parallelization framework #2782
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use references in some places where they can replace pointers. I could go much further here, but I did not
want to bloat the pull request, maybe later change all closures to be references as well? Should I create
enhancements for this?
If necessary, generally do such changes (vs. the reference processing refactoring) in extra CRs. Changing pointers to references seems completely unrelated.
Fwiw, my personal opinion is to not do that at all for code that has lots of connections with existing code as this results in an awkward mixture in these areas between old an new code which only makes things hard to read (and adds another question when interfacing that code, do I need a reference or a pointer now?), but others may have other opinions.
Also it generally hides the information at the call site whether something is actually passed as reference or as value (at least for classes/structs). Of course, as usual there is no hard rule, and I am good if others think it's the way to go.
G1CMRefProcClosureContext( | ||
uint max_workers, | ||
G1CollectedHeap& g1h, | ||
G1ConcurrentMark& cm) | ||
: _max_workers(max_workers), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please adopt a constructor formatting style already present in the file at least. Please do not add new ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will move uint max_workers
to the first line and align the constructor arguments to the first parameter, but leave the rest as is. I will try to use that style later as well.
if (_tm == ThreadModel::Single) { | ||
return ::new (&_serial_keep_alive) G1CopyingKeepAliveClosure(&_g1h, _pss.state_for_worker(0)); | ||
} | ||
return ::new (&_keep_alive[worker_id]) G1CopyingKeepAliveClosure(&_g1h, _pss.state_for_worker(worker_id)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of prefer the symmetric
if (something) { return a; } else { return b; }
style than this "dangling" return as the returns are on the same indentation level. Feel free to ignore in multiple places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will fix that.
G1CMRefProcTaskExecutor par_task_executor(_g1h, this, | ||
_g1h->workers(), active_workers); | ||
AbstractRefProcTaskExecutor* executor = (processing_is_mt ? &par_task_executor : NULL); | ||
// reference processing context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial capitalization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just remove the comment, it only add noise.
@@ -230,6 +229,39 @@ void G1FullCollector::update_attribute_table(HeapRegion* hr) { | |||
} | |||
} | |||
|
|||
class G1FullRefProcClosureContext : public AbstractClosureContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add "GC" after "Full" because this naming begs the question what a "partial" context would look like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will do that.
assert(worker_id < _max_workers, "sanity"); | ||
return ::new (&_keep_alive[index(worker_id, _tm)]) G1CMKeepAliveAndDrainClosure(&_cm, _cm.task(index(worker_id, _tm)), _tm == ThreadModel::Single ); | ||
} | ||
VoidClosure* complete_gc(uint worker_id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent spacing between methods, sometimes there is a newline, sometimes not. I would prefer the additional newline. This is also the case in the other file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will do that.
ParCompactionManager* cm = ParCompactionManager::gc_thread_compaction_manager(worker_id); | ||
return ::new (&_keep_alive[worker_id]) PCMarkAndPushClosure(cm); | ||
} | ||
VoidClosure* complete_gc(uint worker_id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline between method definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will do that.
~G1FullRefProcClosureContext() { | ||
FREE_C_HEAP_ARRAY(G1FullKeepAliveClosure, _keep_alive); | ||
} | ||
BoolObjectClosure* is_alive(uint worker_id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newlines between definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will do that.
WorkGang* _workers; | ||
class G1STWRefProcClosureContext : public AbstractClosureContext { | ||
uint _max_workers; | ||
uint _queues; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid lining up member names. That is hard to read. Group them by functionality/contents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, will remove all spaces.
{ | ||
g1h->ref_processor_stw()->set_active_mt_degree(workers->active_workers()); | ||
G1STWRefProcClosureContext( | ||
uint max_workers, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter indentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will fix.
} | ||
|
||
BoolObjectClosure* is_alive(uint worker_id) { return &_is_alive; } | ||
OopClosure* keep_alive(uint worker_id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline between definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix.
I have tried to fix the formatting mess (sorry) and renaming of classes. I hope this looks better. |
@lkorinth this pull request can not be integrated into git checkout _8231672
git fetch https://git.openjdk.java.net/jdk master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the code looks better now. Thanks.
@@ -180,55 +180,64 @@ class PSKeepAliveClosure: public OopClosure { | |||
class PSEvacuateFollowersClosure: public VoidClosure { | |||
private: | |||
PSPromotionManager* _promotion_manager; | |||
TaskTerminator* _maybe_terminator; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really seeing the advantage: if you want to make sure that something isn't nullptr
when it's used, put an assert before the use, then it should be clear (and runtime-checked) that it's not valid; even if it were nullptr
, testing should have found (i.e. exercised that path) that anyway and failed fast (by dereferencing that null value).
A reviewer needs to look if values may actually be nullptr
in that code path anyway regardless of the name.
Maybe someone else likes this better though, so no opinion here.
@lkorinth This change now passes all automated pre-integration checks. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been no new commits pushed to the
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Context" suffix isn't suggestive of what it does for me. "context" suggests scope or surroundings. I think "Group" would be better; as this is a collection of inter-related things. I thought of some others, but many are already overused while others were way to obscure. Another possibility is "xxxClosureContext" => "xxxClosures", though perhaps that pluralization is too subtle.
I think the life-cycle management of the closures by the context objects is a problem. I'm guessing this is to lazily construct the closures? But as used this may be constructing closures over storage containing an already constructed closure, without calling the destructor. This is all UB if the destructors have any relevant side effects (3.8/4). Is that guaranteed for all such closures? I'm not sure I like that requirement on the closure types, and certainly not without some documentation making that explicit. We can't use is_trivial_destructor<> as a safety net since these closures have non-trivial destructors (they are virtual).
Lazy construction and as-needed destruction could be done via having a parallel set of is_constructed flags. Context destructor cleans up accordingly.
Or maybe have a high-water construction tracker and have prepare_run_task do construction to advance that high-water mark as needed. Again, Context destructor cleans up accordingly.
In the various context classes, I think the virtual overrides should be explicitly marked "virtual", as that's usually made explicit in our code. (And I've finally sent out JDK-8254050.)
I think there's confusion over determining the number of workers to use. There are various places that are using active_workers() where total_workers() might be better, for the same reasons as in
https://bugs.openjdk.java.net/browse/JDK-8258985
But I think that same confusion exists in the old code, so I'm okay with treating this as a pre-existing issue for a different change.
virtual OopClosure* keep_alive(uint worker_id) = 0; | ||
virtual VoidClosure* complete_gc(uint worker_id) = 0; | ||
virtual void prepare_run_task(uint queue_count, ThreadModel tm, bool marks_oops_alive) = 0; | ||
uint index(uint id, ThreadModel tm) { return (tm==ThreadModel::Single)?0:id; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like more spaces around operators and punctuation in that value expression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code was updated because Thomas also found it, are you okay with the current code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, updated code is fine.
|
||
class AbstractClosureContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an overly general name. This is related to reference
processing I think (not going to be used elsewhere), so I think should be
named accordingly. ClosureContext also seems odd. Maybe something
like AbstractRefProcOperations? Handlers (slight bleh)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the current name. This was updated to AbstractRefProcClosureContext earlier. I will update to AbstractRefProcOperations if you prefer that. I think your suggestion is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the Operations suffix better too.
OopClosure& keep_alive, | ||
VoidClosure& complete_gc) { | ||
virtual void work(uint worker_id) { | ||
ResourceMark rm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a number of places like here where a ResourceMark has been introduced. I've not been able to figure out what these are for. And that makes me wonder whether they are in the right place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ResourceMark
s was used in some of the previous nested closures. See removal of G1STWRefProcTaskProxy
and G1CMRefProcTaskProxy
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and I'm not sure why any of them exist, other than some paranoia that there might be some not properly scoped resource allocations somewhere inside that we want to squash on the way out of the thread. That is, these seem like just hiding potential bugs. But you probably don't want to chase down any actual bugs of that sort as part of this change, so okay, leave them.
@@ -2023,7 +2023,7 @@ static void mark_from_roots_work(ParallelRootType::Value root_type, uint worker_ | |||
cm->follow_marking_stacks(); | |||
} | |||
|
|||
static void steal_marking_work(TaskTerminator& terminator, uint worker_id) { | |||
void steal_marking_work(TaskTerminator& terminator, uint worker_id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this needs to be made non-file-scoped then it should be moved into some relevant class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, never mind, the minimal diff confused me about the context. We're in class scope here, not at namespace scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind, I was confused by the minimal diff. This is already at class scope, not at file scope.
|
||
virtual void do_void() { | ||
assert(_promotion_manager != NULL, "Sanity"); | ||
assert(_promotion_manager != nullptr, "Sanity"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer this assert was in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I further prefer not having an assert and instead using a reference. I also fear doing unrelated changes will be rejected. This is no new code of mine, it is added so that nullptr is used consistently after push-back from Thomas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you prefer?
- keep as is now.
- move to constructor.
- use a reference instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably move it to the constructor, but I don't feel strongly about it.
VoidClosure& complete_gc) = 0; | ||
|
||
bool marks_oops_alive() const { return _marks_oops_alive; } | ||
virtual BoolObjectClosure* is_alive(uint worker_id) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing destructor for base class - should be public and virtual or non-public and non-virtual. As these seem to always be stack allocated, a protected default destructor would be appropriate.
|
||
class AbstractRefProcClosureContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these are always stack allocated, so derive from StackObj? (To at least signal intent.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, will change.
Hi, and sorry for the long silence from me. I got feedback from Albert on improving the structure of this change and to use proxy classes instead of ClosureContexts. This will solve the problem with heap allocated closures and also improve readability IMO. The change is big, so I will not push it to this review thread before you agree with that it is the right way to go, but you can see the change here: lkorinth@5cdddac If you agree with me, I will push that change to this review. |
So in addition to using proxy classes instead of ClosureContexts, I have also fixed an earlier mistake of mine when getting the ParCompactionManager and PSPromotionManager in the VM thread I will no longer use the index of worker_id and instead use the dedicated manager for the VM thread. |
marks_oops_alive ? "true" : "false"); | ||
|
||
proxy_task.prepare_run_task(task, num_queues(), processing_is_mt() ? RefProcThreadModel::Multi : RefProcThreadModel::Single, marks_oops_alive); | ||
if (gang != NULL && processing_is_mt()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since gang
is only used in thin branch, moving it inside if
might be better. Besides, the assertion on L779 could be replaced by the code below, IMO.
if (processing_is_mt()) {
WorkGang* gang = Universe::heap()->safepoint_workers();
assert(gang != nullptr, "...");
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes!
@@ -38,14 +39,15 @@ class ParallelCompactData; | |||
class ParMarkBitMap; | |||
|
|||
class ParCompactionManager : public CHeapObj<mtGC> { | |||
friend class CompactionWithStealingTask; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CompactionWithStealingTask
and UpdateAndFillClosure
are dead code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove them.
|
||
// Abstract reference processing task to execute. | ||
class AbstractRefProcTaskExecutor::ProcessTask { | ||
class RefProcTask : StackObj { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe put some comments here, explaining what RefProcTask
and RefProcProxyTask
are, their interaction/relation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to do that.
uint index(uint id) const { | ||
return (_tm == RefProcThreadModel::Single) ? 0 : id; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is only used by G1; I wonder if it's possible to not include it in the API of RefProcProxyTask
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could --- and maybe should --- but then I need to supply _tm
as an argument, further I need to find a logical place to put the function and the declaration, and if I add it to referenceProcessor.?pp
I have not made it less generic. If I add it to a G1 file, it seems to me arbitrary which to use. If you find a nice place to put it, I will move it, otherwise I will keep it where it is. I will not create a new file for the function and would in that case prefer to inline expand the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the body of uint index(uint id) const
is just one line, I was thinking the caller can just inline it. Taking G1FullGCRefProcProxyTask
as an example.
before
G1FullKeepAliveClosure keep_alive(_collector.marker(index(worker_id)));
G1FollowStackClosure* complete_gc = _collector.marker(index(worker_id))->stack_closure();
after
auto index = _tm == RefProcThreadModel::Single ? 0 : worker_id;
auto marker = _collector.marker(index);
G1FullKeepAliveClosure keep_alive(marker);
G1FollowStackClosure* complete_gc = marker->stack_closure();
My concern with including uint index(uint id) const
in the API of RefProcProxyTask
is that its name/implementation is too generic. In the context of RefProcProxyTask
, it's not obvious what id
is, what index
should return, and why (_tm == RefProcThreadModel::Single) ? 0 : id;
is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I inlined it.
RefProcTotalPhaseTimesTracker tt(RefPhase1, phase_times, this); | ||
process_soft_ref_reconsider(is_alive, keep_alive, complete_gc, | ||
task_executor, phase_times); | ||
RefProcTotalPhaseTimesTracker tt(RefPhase1, &phase_times, this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RefProcTotalPhaseTimesTracker
doesn't really use this
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Pre-existing, but I will clean it up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the revision.
/integrate |
@lkorinth Since your change was applied there have been 18 commits pushed to the
Your commit was automatically rebased without conflicts. Pushed as commit 6ef46ce. |
With the change of parallel gc to use the same workgang implementation that other gcs uses, it is possible to remove the abstraction used to hide different worker thread management implementations (TaskExecutor).
At the same time as removing the TaskExecutor, also remove part of the special casing (mostly code duplication), for single threaded execution.
I consider the new method
prepare_run_task
that modifies the state of the context a step backwards from what was. However, I think removing the Executor with its proxy-tasks and removing separate code paths for serial execution makes the change worth this step back. We also removes ~270 lines of code.Some comments:
I use references in some places where they can replace pointers. I could go much further here, but I did not want to bloat the pull request, maybe later change all closures to be references as well? Should I create enhancements for this?
I added an enum class ThreadModel instead of using a boolean, this could also be used in many more places. I dislike sending lots of bools with a comment like
true /* _is_mt */
. It also adds type safety if a method takes many bools. However, with this limited change, and not many hard-coded bools, it feels a bit overkill and I am willing to remove the enum, what do you think?Testing:
hotspot_gc and tier 1-3 has passed earlier versions before minor cleanups. I intend to re-run tests after review changes.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.java.net/jdk pull/2782/head:pull/2782
$ git checkout pull/2782
Update a local copy of the PR:
$ git checkout pull/2782
$ git pull https://git.openjdk.java.net/jdk pull/2782/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 2782
View PR using the GUI difftool:
$ git pr show -t 2782
Using diff file
Download this PR as a diff file:
https://git.openjdk.java.net/jdk/pull/2782.diff