8265842: G1: Introduce API to run multiple separate tasks in a single gangtask
Reviewed-by: lkorinth, ayang, sjohanss, iwalulya
This commit is contained in:
parent
294347b59b
commit
c76ce28754
109
src/hotspot/share/gc/g1/g1BatchedGangTask.cpp
Normal file
109
src/hotspot/share/gc/g1/g1BatchedGangTask.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
|
||||
#include "gc/g1/g1BatchedGangTask.hpp"
|
||||
#include "gc/g1/g1CollectedHeap.inline.hpp"
|
||||
#include "gc/g1/g1GCParPhaseTimesTracker.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "utilities/growableArray.hpp"
|
||||
|
||||
void G1AbstractSubTask::record_work_item(uint worker_id, uint index, size_t count) {
|
||||
G1CollectedHeap* g1h = G1CollectedHeap::heap();
|
||||
g1h->phase_times()->record_thread_work_item(_tag, worker_id, count, index);
|
||||
}
|
||||
|
||||
const char* G1AbstractSubTask::name() const {
|
||||
G1CollectedHeap* g1h = G1CollectedHeap::heap();
|
||||
return g1h->phase_times()->phase_name(_tag);
|
||||
}
|
||||
|
||||
bool G1BatchedGangTask::try_claim_serial_task(int& task) {
|
||||
task = Atomic::fetch_and_add(&_num_serial_tasks_done, 1);
|
||||
return task < _serial_tasks.length();
|
||||
}
|
||||
|
||||
void G1BatchedGangTask::add_serial_task(G1AbstractSubTask* task) {
|
||||
assert(task != nullptr, "must be");
|
||||
_serial_tasks.push(task);
|
||||
}
|
||||
|
||||
void G1BatchedGangTask::add_parallel_task(G1AbstractSubTask* task) {
|
||||
assert(task != nullptr, "must be");
|
||||
_parallel_tasks.push(task);
|
||||
}
|
||||
|
||||
G1BatchedGangTask::G1BatchedGangTask(const char* name, G1GCPhaseTimes* phase_times) :
|
||||
AbstractGangTask(name),
|
||||
_num_serial_tasks_done(0),
|
||||
_phase_times(phase_times),
|
||||
_serial_tasks(),
|
||||
_parallel_tasks() {
|
||||
}
|
||||
|
||||
uint G1BatchedGangTask::num_workers_estimate() const {
|
||||
double sum = 0.0;
|
||||
for (G1AbstractSubTask* task : _serial_tasks) {
|
||||
sum += task->worker_cost();
|
||||
}
|
||||
for (G1AbstractSubTask* task : _parallel_tasks) {
|
||||
sum += task->worker_cost();
|
||||
}
|
||||
return ceil(sum);
|
||||
}
|
||||
|
||||
void G1BatchedGangTask::set_max_workers(uint max_workers) {
|
||||
for (G1AbstractSubTask* task : _serial_tasks) {
|
||||
task->set_max_workers(max_workers);
|
||||
}
|
||||
for (G1AbstractSubTask* task : _parallel_tasks) {
|
||||
task->set_max_workers(max_workers);
|
||||
}
|
||||
}
|
||||
|
||||
void G1BatchedGangTask::work(uint worker_id) {
|
||||
int t = 0;
|
||||
while (try_claim_serial_task(t)) {
|
||||
G1AbstractSubTask* task = _serial_tasks.at(t);
|
||||
G1GCParPhaseTimesTracker x(_phase_times, task->tag(), worker_id);
|
||||
task->do_work(worker_id);
|
||||
}
|
||||
for (G1AbstractSubTask* task : _parallel_tasks) {
|
||||
G1GCParPhaseTimesTracker x(_phase_times, task->tag(), worker_id);
|
||||
task->do_work(worker_id);
|
||||
}
|
||||
}
|
||||
|
||||
G1BatchedGangTask::~G1BatchedGangTask() {
|
||||
assert(Atomic::load(&_num_serial_tasks_done) >= _serial_tasks.length(),
|
||||
"Only %d tasks of %d claimed", Atomic::load(&_num_serial_tasks_done), _serial_tasks.length());
|
||||
|
||||
for (G1AbstractSubTask* task : _parallel_tasks) {
|
||||
delete task;
|
||||
}
|
||||
for (G1AbstractSubTask* task : _serial_tasks) {
|
||||
delete task;
|
||||
}
|
||||
}
|
149
src/hotspot/share/gc/g1/g1BatchedGangTask.hpp
Normal file
149
src/hotspot/share/gc/g1/g1BatchedGangTask.hpp
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHARE_GC_G1_G1BATCHEDGANGTASK_HPP
|
||||
#define SHARE_GC_G1_G1BATCHEDGANGTASK_HPP
|
||||
|
||||
#include "gc/g1/g1GCPhaseTimes.hpp"
|
||||
#include "gc/shared/workgroup.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
|
||||
template <typename E, MEMFLAGS F>
|
||||
class GrowableArrayCHeap;
|
||||
|
||||
// G1AbstractSubTask represents a task to be performed either within a
|
||||
// G1BatchedGangTask running on a single worker ("serially") or multiple workers
|
||||
// ("in parallel"). A G1AbstractSubTask is always associated with a phase tag
|
||||
// that is used to automatically store timing information.
|
||||
//
|
||||
// A "serial" task is some piece of work that either can not be parallelized
|
||||
// easily, or is typically so short that parallelization is not worth the effort.
|
||||
// Current examples would be summarizing per worker thread information gathered
|
||||
// during garbage collection (e.g. Merge PSS work).
|
||||
//
|
||||
// A "parallel" task could be some large amount of work that typically naturally
|
||||
// splits across the heap in some way. Current examples would be clearing the
|
||||
// card table.
|
||||
//
|
||||
// See G1BatchedGangTask for information on execution.
|
||||
class G1AbstractSubTask : public CHeapObj<mtGC> {
|
||||
G1GCPhaseTimes::GCParPhases _tag;
|
||||
|
||||
NONCOPYABLE(G1AbstractSubTask);
|
||||
|
||||
protected:
|
||||
// Record work item for this tag in G1GCPhaseTimes.
|
||||
void record_work_item(uint worker_id, uint index, size_t count);
|
||||
|
||||
public:
|
||||
G1AbstractSubTask(G1GCPhaseTimes::GCParPhases tag) : _tag(tag) { }
|
||||
virtual ~G1AbstractSubTask() { }
|
||||
|
||||
// How many workers (threads) would this task be able to keep busy for at least
|
||||
// as long as to amortize worker startup costs.
|
||||
// Called by G1BatchedGangTask to determine total number of workers.
|
||||
virtual double worker_cost() const = 0;
|
||||
|
||||
// Called by G1BatchedGangTask to provide information about the the maximum
|
||||
// number of workers for all subtasks after it has been determined.
|
||||
virtual void set_max_workers(uint max_workers) { }
|
||||
|
||||
// Perform the actual work. Gets the worker id it is run on passed in.
|
||||
virtual void do_work(uint worker_id) = 0;
|
||||
|
||||
// Tag for this G1AbstractSubTask.
|
||||
G1GCPhaseTimes::GCParPhases tag() const { return _tag; }
|
||||
// Human readable name derived from the tag.
|
||||
const char* name() const;
|
||||
};
|
||||
|
||||
// G1BatchedGangTask runs a set of G1AbstractSubTask using a work gang.
|
||||
//
|
||||
// Subclasses of this class add their G1AbstractSubTasks into either the list
|
||||
// of "serial" or the list of "parallel" tasks. They are supposed to be the owners
|
||||
// of the G1AbstractSubTasks.
|
||||
//
|
||||
// Eg. the constructor contains code like the following:
|
||||
//
|
||||
// add_serial_task(new SomeSubTask());
|
||||
// [...]
|
||||
// add_parallel_task(new SomeOtherSubTask());
|
||||
// [...]
|
||||
//
|
||||
// During execution in the work gang, this class will make sure that the "serial"
|
||||
// tasks are executed by a single worker exactly once, but different "serial"
|
||||
// tasks may be executed in parallel using different workers. "Parallel" tasks'
|
||||
// do_work() method may be called by different workers passing a different
|
||||
// worker_id at the same time, but at most once per given worker_id.
|
||||
//
|
||||
// There is also no guarantee that G1AbstractSubTasks::do_work() of different tasks
|
||||
// are actually run in parallel.
|
||||
//
|
||||
// The current implementation assumes that constructors and destructors of the
|
||||
// G1AbstractSubTasks can executed in the constructor/destructor of an instance
|
||||
// of this class.
|
||||
//
|
||||
// The constructor, destructor and the do_work() methods from different
|
||||
// G1AbstractSubTasks may run in any order so they must not have any
|
||||
// dependencies at all.
|
||||
//
|
||||
// For a given G1AbstractSubTask T call order of its methods are as follows:
|
||||
//
|
||||
// 1) T()
|
||||
// 2) T::thread_usage()
|
||||
// 3) T::set_max_workers()
|
||||
// 4) T::do_work() // potentially in parallel with any other registered G1AbstractSubTask
|
||||
// 5) ~T()
|
||||
//
|
||||
class G1BatchedGangTask : public AbstractGangTask {
|
||||
volatile int _num_serial_tasks_done;
|
||||
G1GCPhaseTimes* _phase_times;
|
||||
|
||||
bool try_claim_serial_task(int& task);
|
||||
|
||||
NONCOPYABLE(G1BatchedGangTask);
|
||||
|
||||
GrowableArrayCHeap<G1AbstractSubTask*, mtGC> _serial_tasks;
|
||||
GrowableArrayCHeap<G1AbstractSubTask*, mtGC> _parallel_tasks;
|
||||
|
||||
protected:
|
||||
void add_serial_task(G1AbstractSubTask* task);
|
||||
void add_parallel_task(G1AbstractSubTask* task);
|
||||
|
||||
G1BatchedGangTask(const char* name, G1GCPhaseTimes* phase_times);
|
||||
|
||||
public:
|
||||
void work(uint worker_id) override;
|
||||
|
||||
// How many workers can this gang task keep busy and should be started for
|
||||
// "optimal" performance.
|
||||
uint num_workers_estimate() const;
|
||||
// Informs the G1AbstractSubTasks about that we will start execution with the
|
||||
// given number of workers.
|
||||
void set_max_workers(uint max_workers);
|
||||
|
||||
~G1BatchedGangTask();
|
||||
};
|
||||
|
||||
#endif // SHARE_GC_G1_G1BATCHEDGANGTASK_HPP
|
157
test/hotspot/gtest/gc/g1/test_g1BatchedGangTask.cpp
Normal file
157
test/hotspot/gtest/gc/g1/test_g1BatchedGangTask.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
|
||||
#include "gc/g1/g1BatchedGangTask.hpp"
|
||||
#include "gc/shared/workgroup.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "unittest.hpp"
|
||||
|
||||
class G1BatchedGangTaskWorkers : AllStatic {
|
||||
static WorkGang* _work_gang;
|
||||
static WorkGang* work_gang() {
|
||||
if (_work_gang == nullptr) {
|
||||
_work_gang = new WorkGang("G1 Small Workers", MaxWorkers, false, false);
|
||||
_work_gang->initialize_workers();
|
||||
_work_gang->update_active_workers(MaxWorkers);
|
||||
}
|
||||
return _work_gang;
|
||||
}
|
||||
|
||||
public:
|
||||
static const uint MaxWorkers = 4;
|
||||
static void run_task(AbstractGangTask* task) {
|
||||
work_gang()->run_task(task);
|
||||
}
|
||||
};
|
||||
|
||||
WorkGang* G1BatchedGangTaskWorkers::_work_gang = nullptr;
|
||||
|
||||
class G1TestSubTask : public G1AbstractSubTask {
|
||||
mutable uint _phase;
|
||||
volatile uint _num_do_work; // Amount of do_work() has been called.
|
||||
|
||||
void check_and_inc_phase(uint expected) const {
|
||||
ASSERT_EQ(_phase, expected);
|
||||
_phase++;
|
||||
}
|
||||
|
||||
bool volatile* _do_work_called_by;
|
||||
|
||||
protected:
|
||||
uint _max_workers;
|
||||
|
||||
void do_work_called(uint worker_id) {
|
||||
Atomic::inc(&_num_do_work);
|
||||
bool orig_value = Atomic::cmpxchg(&_do_work_called_by[worker_id], false, true);
|
||||
ASSERT_EQ(orig_value, false);
|
||||
}
|
||||
|
||||
void verify_do_work_called_by(uint num_workers) {
|
||||
ASSERT_EQ(Atomic::load(&_num_do_work), num_workers);
|
||||
// Do not need to check the _do_work_called_by array. The count is already verified
|
||||
// by above statement, and we already check that a given flag is only set once.
|
||||
}
|
||||
|
||||
public:
|
||||
// Actual use of GCParPhasesSentinel will cause an assertion failure when trying
|
||||
// to add timing information - this should be disabled here.
|
||||
G1TestSubTask() : G1AbstractSubTask(G1GCPhaseTimes::GCParPhasesSentinel),
|
||||
_phase(0),
|
||||
_num_do_work(0),
|
||||
_do_work_called_by(nullptr),
|
||||
_max_workers(0) {
|
||||
check_and_inc_phase(0);
|
||||
}
|
||||
|
||||
~G1TestSubTask() {
|
||||
check_and_inc_phase(3);
|
||||
FREE_C_HEAP_ARRAY(bool, _do_work_called_by);
|
||||
}
|
||||
|
||||
double worker_cost() const override {
|
||||
check_and_inc_phase(1);
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Called by G1BatchedGangTask to provide information about the the maximum
|
||||
// number of workers for all subtasks after it has been determined.
|
||||
void set_max_workers(uint max_workers) override {
|
||||
assert(max_workers >= 1, "must be");
|
||||
check_and_inc_phase(2);
|
||||
|
||||
_do_work_called_by = NEW_C_HEAP_ARRAY(bool, max_workers, mtInternal);
|
||||
for (uint i = 0; i < max_workers; i++) {
|
||||
_do_work_called_by[i] = false;
|
||||
}
|
||||
_max_workers = max_workers;
|
||||
}
|
||||
|
||||
void do_work(uint worker_id) override {
|
||||
do_work_called(worker_id);
|
||||
}
|
||||
};
|
||||
|
||||
class G1SerialTestSubTask : public G1TestSubTask {
|
||||
public:
|
||||
G1SerialTestSubTask() : G1TestSubTask() { }
|
||||
~G1SerialTestSubTask() {
|
||||
verify_do_work_called_by(1);
|
||||
}
|
||||
|
||||
double worker_cost() const override {
|
||||
G1TestSubTask::worker_cost();
|
||||
return 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
class G1ParallelTestSubTask : public G1TestSubTask {
|
||||
public:
|
||||
G1ParallelTestSubTask() : G1TestSubTask() { }
|
||||
~G1ParallelTestSubTask() {
|
||||
verify_do_work_called_by(_max_workers);
|
||||
}
|
||||
|
||||
double worker_cost() const override {
|
||||
G1TestSubTask::worker_cost();
|
||||
return 2.0;
|
||||
}
|
||||
};
|
||||
|
||||
class G1TestBatchedGangTask : public G1BatchedGangTask {
|
||||
public:
|
||||
G1TestBatchedGangTask() : G1BatchedGangTask("Batched Gang Test Task", nullptr) {
|
||||
add_serial_task(new G1SerialTestSubTask());
|
||||
add_parallel_task(new G1ParallelTestSubTask());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_VM(G1BatchedGangTask, check) {
|
||||
G1TestBatchedGangTask task;
|
||||
uint tasks = task.num_workers_estimate();
|
||||
ASSERT_EQ(tasks, 3u);
|
||||
task.set_max_workers(G1BatchedGangTaskWorkers::MaxWorkers);
|
||||
G1BatchedGangTaskWorkers::run_task(&task);
|
||||
}
|
Loading…
Reference in New Issue
Block a user