8265842: G1: Introduce API to run multiple separate tasks in a single gangtask

Reviewed-by: lkorinth, ayang, sjohanss, iwalulya
This commit is contained in:
Thomas Schatzl 2021-04-29 14:44:14 +00:00
parent 294347b59b
commit c76ce28754
3 changed files with 415 additions and 0 deletions

View 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;
}
}

View 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

View 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);
}