Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
8247819: G1: Process strong OopStorage entries in parallel
Implement parallel processing of strong OopStorage entries for G1.

Co-authored-by: Erik Osterlund <erik.osterlund@oracle.com>
Co-authored-by: Stefan Karlsson <stefan.karlsson@oracle.com>
Reviewed-by: kbarrett, stefank
  • Loading branch information
3 people committed Jun 26, 2020
1 parent 51ddc2a commit 18cddad5a2a6f70c1945b10d6f6330185af20e45
Show file tree
Hide file tree
Showing 15 changed files with 92 additions and 123 deletions.
@@ -53,54 +53,61 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) :
{
assert(max_gc_threads > 0, "Must have some GC threads");

_gc_par_phases[GCWorkerStart] = new WorkerDataArray<double>("GC Worker Start (ms):", max_gc_threads);
_gc_par_phases[ExtRootScan] = new WorkerDataArray<double>("Ext Root Scanning (ms):", max_gc_threads);
_gc_par_phases[GCWorkerStart] = new WorkerDataArray<double>("GCWorkerStart", "GC Worker Start (ms):", max_gc_threads);
_gc_par_phases[ExtRootScan] = new WorkerDataArray<double>("ExtRootScan", "Ext Root Scanning (ms):", max_gc_threads);

// Root scanning phases
_gc_par_phases[ThreadRoots] = new WorkerDataArray<double>("Thread Roots (ms):", max_gc_threads);
_gc_par_phases[UniverseRoots] = new WorkerDataArray<double>("Universe Roots (ms):", max_gc_threads);
_gc_par_phases[JNIRoots] = new WorkerDataArray<double>("JNI Handles Roots (ms):", max_gc_threads);
_gc_par_phases[ObjectSynchronizerRoots] = new WorkerDataArray<double>("ObjectSynchronizer Roots (ms):", max_gc_threads);
_gc_par_phases[ManagementRoots] = new WorkerDataArray<double>("Management Roots (ms):", max_gc_threads);
_gc_par_phases[VMGlobalRoots] = new WorkerDataArray<double>("VM Global Roots (ms):", max_gc_threads);
_gc_par_phases[CLDGRoots] = new WorkerDataArray<double>("CLDG Roots (ms):", max_gc_threads);
_gc_par_phases[JVMTIRoots] = new WorkerDataArray<double>("JVMTI Roots (ms):", max_gc_threads);
AOT_ONLY(_gc_par_phases[AOTCodeRoots] = new WorkerDataArray<double>("AOT Root Scan (ms):", max_gc_threads);)
_gc_par_phases[CMRefRoots] = new WorkerDataArray<double>("CM RefProcessor Roots (ms):", max_gc_threads);

_gc_par_phases[MergeER] = new WorkerDataArray<double>("Eager Reclaim (ms):", max_gc_threads);

_gc_par_phases[MergeRS] = new WorkerDataArray<double>("Remembered Sets (ms):", max_gc_threads);
_gc_par_phases[ThreadRoots] = new WorkerDataArray<double>("ThreadRoots", "Thread Roots (ms):", max_gc_threads);
_gc_par_phases[UniverseRoots] = new WorkerDataArray<double>("UniverseRoots", "Universe Roots (ms):", max_gc_threads);
_gc_par_phases[ObjectSynchronizerRoots] = new WorkerDataArray<double>("ObjectSynchronizerRoots", "ObjectSynchronizer Roots (ms):", max_gc_threads);
_gc_par_phases[ManagementRoots] = new WorkerDataArray<double>("ManagementRoots", "Management Roots (ms):", max_gc_threads);
_gc_par_phases[CLDGRoots] = new WorkerDataArray<double>("CLDGRoots", "CLDG Roots (ms):", max_gc_threads);
_gc_par_phases[JVMTIRoots] = new WorkerDataArray<double>("JVMTIRoots", "JVMTI Roots (ms):", max_gc_threads);
AOT_ONLY(_gc_par_phases[AOTCodeRoots] = new WorkerDataArray<double>("AOTCodeRoots", "AOT Root Scan (ms):", max_gc_threads);)
_gc_par_phases[CMRefRoots] = new WorkerDataArray<double>("CMRefRoots", "CM RefProcessor Roots (ms):", max_gc_threads);

int index = G1GCPhaseTimes::StrongOopStorageSetRoots;
for (OopStorageSet::Iterator it = OopStorageSet::strong_iterator(); !it.is_end(); ++it, ++index) {
const char* phase_name_postfix = " Roots (ms):";
char* oop_storage_phase_name = NEW_C_HEAP_ARRAY(char, strlen(phase_name_postfix) + strlen(it->name()) + 1, mtGC);
strcpy(oop_storage_phase_name, it->name());
strcat(oop_storage_phase_name, phase_name_postfix);
_gc_par_phases[index] = new WorkerDataArray<double>(it->name(), oop_storage_phase_name, max_gc_threads);
}

_gc_par_phases[MergeER] = new WorkerDataArray<double>("MergeER", "Eager Reclaim (ms):", max_gc_threads);

_gc_par_phases[MergeRS] = new WorkerDataArray<double>("MergeRS", "Remembered Sets (ms):", max_gc_threads);
_gc_par_phases[MergeRS]->create_thread_work_items("Merged Sparse:", MergeRSMergedSparse);
_gc_par_phases[MergeRS]->create_thread_work_items("Merged Fine:", MergeRSMergedFine);
_gc_par_phases[MergeRS]->create_thread_work_items("Merged Coarse:", MergeRSMergedCoarse);
_gc_par_phases[MergeRS]->create_thread_work_items("Dirty Cards:", MergeRSDirtyCards);

_gc_par_phases[OptMergeRS] = new WorkerDataArray<double>("Optional Remembered Sets (ms):", max_gc_threads);
_gc_par_phases[OptMergeRS] = new WorkerDataArray<double>("OptMergeRS", "Optional Remembered Sets (ms):", max_gc_threads);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Merged Sparse:", MergeRSMergedSparse);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Merged Fine:", MergeRSMergedFine);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Merged Coarse:", MergeRSMergedCoarse);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Dirty Cards:", MergeRSDirtyCards);

_gc_par_phases[MergeLB] = new WorkerDataArray<double>("Log Buffers (ms):", max_gc_threads);
_gc_par_phases[MergeLB] = new WorkerDataArray<double>("MergeLB", "Log Buffers (ms):", max_gc_threads);
if (G1HotCardCache::default_use_cache()) {
_gc_par_phases[MergeHCC] = new WorkerDataArray<double>("Hot Card Cache (ms):", max_gc_threads);
_gc_par_phases[MergeHCC] = new WorkerDataArray<double>("MergeHCC", "Hot Card Cache (ms):", max_gc_threads);
_gc_par_phases[MergeHCC]->create_thread_work_items("Dirty Cards:", MergeHCCDirtyCards);
_gc_par_phases[MergeHCC]->create_thread_work_items("Skipped Cards:", MergeHCCSkippedCards);
} else {
_gc_par_phases[MergeHCC] = NULL;
}
_gc_par_phases[ScanHR] = new WorkerDataArray<double>("Scan Heap Roots (ms):", max_gc_threads);
_gc_par_phases[OptScanHR] = new WorkerDataArray<double>("Optional Scan Heap Roots (ms):", max_gc_threads);
_gc_par_phases[CodeRoots] = new WorkerDataArray<double>("Code Root Scan (ms):", max_gc_threads);
_gc_par_phases[OptCodeRoots] = new WorkerDataArray<double>("Optional Code Root Scan (ms):", max_gc_threads);
_gc_par_phases[ObjCopy] = new WorkerDataArray<double>("Object Copy (ms):", max_gc_threads);
_gc_par_phases[OptObjCopy] = new WorkerDataArray<double>("Optional Object Copy (ms):", max_gc_threads);
_gc_par_phases[Termination] = new WorkerDataArray<double>("Termination (ms):", max_gc_threads);
_gc_par_phases[OptTermination] = new WorkerDataArray<double>("Optional Termination (ms):", max_gc_threads);
_gc_par_phases[GCWorkerTotal] = new WorkerDataArray<double>("GC Worker Total (ms):", max_gc_threads);
_gc_par_phases[GCWorkerEnd] = new WorkerDataArray<double>("GC Worker End (ms):", max_gc_threads);
_gc_par_phases[Other] = new WorkerDataArray<double>("GC Worker Other (ms):", max_gc_threads);
_gc_par_phases[ScanHR] = new WorkerDataArray<double>("ScanHR", "Scan Heap Roots (ms):", max_gc_threads);
_gc_par_phases[OptScanHR] = new WorkerDataArray<double>("OptScanHR", "Optional Scan Heap Roots (ms):", max_gc_threads);
_gc_par_phases[CodeRoots] = new WorkerDataArray<double>("CodeRoots", "Code Root Scan (ms):", max_gc_threads);
_gc_par_phases[OptCodeRoots] = new WorkerDataArray<double>("OptCodeRoots", "Optional Code Root Scan (ms):", max_gc_threads);
_gc_par_phases[ObjCopy] = new WorkerDataArray<double>("ObjCopy", "Object Copy (ms):", max_gc_threads);
_gc_par_phases[OptObjCopy] = new WorkerDataArray<double>("OptObjCopy", "Optional Object Copy (ms):", max_gc_threads);
_gc_par_phases[Termination] = new WorkerDataArray<double>("Termination", "Termination (ms):", max_gc_threads);
_gc_par_phases[OptTermination] = new WorkerDataArray<double>("OptTermination", "Optional Termination (ms):", max_gc_threads);
_gc_par_phases[GCWorkerTotal] = new WorkerDataArray<double>("GCWorkerTotal", "GC Worker Total (ms):", max_gc_threads);
_gc_par_phases[GCWorkerEnd] = new WorkerDataArray<double>("GCWorkerEnd", "GC Worker End (ms):", max_gc_threads);
_gc_par_phases[Other] = new WorkerDataArray<double>("Other", "GC Worker Other (ms):", max_gc_threads);

_gc_par_phases[ScanHR]->create_thread_work_items("Scanned Cards:", ScanHRScannedCards);
_gc_par_phases[ScanHR]->create_thread_work_items("Scanned Blocks:", ScanHRScannedBlocks);
@@ -115,7 +122,7 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) :
_gc_par_phases[MergeLB]->create_thread_work_items("Dirty Cards:", MergeLBDirtyCards);
_gc_par_phases[MergeLB]->create_thread_work_items("Skipped Cards:", MergeLBSkippedCards);

_gc_par_phases[MergePSS] = new WorkerDataArray<double>("Merge Per-Thread State", 1 /* length */, true /* is_serial */);
_gc_par_phases[MergePSS] = new WorkerDataArray<double>("MergePSS", "Merge Per-Thread State", 1 /* length */, true /* is_serial */);

_gc_par_phases[MergePSS]->create_thread_work_items("Copied Bytes", MergePSSCopiedBytes, max_gc_threads);
_gc_par_phases[MergePSS]->create_thread_work_items("LAB Waste", MergePSSLABWasteBytes, max_gc_threads);
@@ -126,20 +133,20 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) :
_gc_par_phases[OptTermination]->create_thread_work_items("Optional Termination Attempts:");

if (UseStringDeduplication) {
_gc_par_phases[StringDedupQueueFixup] = new WorkerDataArray<double>("Queue Fixup (ms):", max_gc_threads);
_gc_par_phases[StringDedupTableFixup] = new WorkerDataArray<double>("Table Fixup (ms):", max_gc_threads);
_gc_par_phases[StringDedupQueueFixup] = new WorkerDataArray<double>("StringDedupQueueFixup", "Queue Fixup (ms):", max_gc_threads);
_gc_par_phases[StringDedupTableFixup] = new WorkerDataArray<double>("StringDedupTableFixup", "Table Fixup (ms):", max_gc_threads);
} else {
_gc_par_phases[StringDedupQueueFixup] = NULL;
_gc_par_phases[StringDedupTableFixup] = NULL;
}

_gc_par_phases[RedirtyCards] = new WorkerDataArray<double>("Parallel Redirty (ms):", max_gc_threads);
_gc_par_phases[RedirtyCards] = new WorkerDataArray<double>("RedirtyCards", "Parallel Redirty (ms):", max_gc_threads);
_gc_par_phases[RedirtyCards]->create_thread_work_items("Redirtied Cards:");

_gc_par_phases[ParFreeCSet] = new WorkerDataArray<double>("Parallel Free Collection Set (ms):", max_gc_threads);
_gc_par_phases[YoungFreeCSet] = new WorkerDataArray<double>("Young Free Collection Set (ms):", max_gc_threads);
_gc_par_phases[NonYoungFreeCSet] = new WorkerDataArray<double>("Non-Young Free Collection Set (ms):", max_gc_threads);
_gc_par_phases[RebuildFreeList] = new WorkerDataArray<double>("Parallel Rebuild Free List (ms):", max_gc_threads);
_gc_par_phases[ParFreeCSet] = new WorkerDataArray<double>("ParFreeCSet", "Parallel Free Collection Set (ms):", max_gc_threads);
_gc_par_phases[YoungFreeCSet] = new WorkerDataArray<double>("YoungFreeCSet", "Young Free Collection Set (ms):", max_gc_threads);
_gc_par_phases[NonYoungFreeCSet] = new WorkerDataArray<double>("NonYoungFreeCSet", "Non-Young Free Collection Set (ms):", max_gc_threads);
_gc_par_phases[RebuildFreeList] = new WorkerDataArray<double>("RebuildFreeList", "Parallel Rebuild Free List (ms):", max_gc_threads);

reset();
}
@@ -553,49 +560,8 @@ void G1GCPhaseTimes::print() {
}

const char* G1GCPhaseTimes::phase_name(GCParPhases phase) {
static const char* names[] = {
"GCWorkerStart",
"ExtRootScan",
"ThreadRoots",
"UniverseRoots",
"JNIRoots",
"ObjectSynchronizerRoots",
"ManagementRoots",
"VMGlobalRoots",
"CLDGRoots",
"JVMTIRoots",
AOT_ONLY("AOTCodeRoots" COMMA)
"CMRefRoots",
"MergeER",
"MergeRS",
"OptMergeRS",
"MergeLB",
"MergeHCC",
"ScanHR",
"OptScanHR",
"CodeRoots",
"OptCodeRoots",
"ObjCopy",
"OptObjCopy",
"Termination",
"OptTermination",
"Other",
"GCWorkerTotal",
"GCWorkerEnd",
"StringDedupQueueFixup",
"StringDedupTableFixup",
"RedirtyCards",
"ParFreeCSet",
"YoungFreeCSet",
"NonYoungFreeCSet",
"RebuildFreeList",
"MergePSS"
//GCParPhasesSentinel only used to tell end of enum
};

STATIC_ASSERT(ARRAY_SIZE(names) == G1GCPhaseTimes::GCParPhasesSentinel); // GCParPhases enum and corresponding string array should have the same "length", this tries to assert it

return names[phase];
G1GCPhaseTimes* phase_times = G1CollectedHeap::heap()->phase_times();
return phase_times->_gc_par_phases[phase]->short_name();
}

G1EvacPhaseWithTrimTimeTracker::G1EvacPhaseWithTrimTimeTracker(G1ParScanThreadState* pss, Tickspan& total_time, Tickspan& trim_time) :
@@ -25,6 +25,7 @@
#ifndef SHARE_GC_G1_G1GCPHASETIMES_HPP
#define SHARE_GC_G1_G1GCPHASETIMES_HPP

#include "gc/shared/oopStorageSet.hpp"
#include "gc/shared/referenceProcessorPhaseTimes.hpp"
#include "gc/shared/weakProcessorPhaseTimes.hpp"
#include "logging/logLevel.hpp"
@@ -48,15 +49,16 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
ExtRootScan,
ThreadRoots,
UniverseRoots,
JNIRoots,
ObjectSynchronizerRoots,
ManagementRoots,
VMGlobalRoots,
CLDGRoots,
JVMTIRoots,
AOT_ONLY(AOTCodeRoots COMMA)
CMRefRoots,
MergeER,
// For every OopStorage there will be one element in the enum, starting with
// StrongOopStorageSetRoots.
StrongOopStorageSetRoots,
MergeER = StrongOopStorageSetRoots + OopStorageSet::strong_count,
MergeRS,
OptMergeRS,
MergeLB,
@@ -84,7 +86,7 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
};

static const GCParPhases ExtRootScanSubPhasesFirst = ThreadRoots;
static const GCParPhases ExtRootScanSubPhasesLast = CMRefRoots;
static const GCParPhases ExtRootScanSubPhasesLast = GCParPhases(MergeER - 1);

enum GCMergeRSWorkTimes {
MergeRSMergedSparse,
@@ -40,6 +40,7 @@
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/shared/oopStorage.inline.hpp"
#include "gc/shared/oopStorageSet.hpp"
#include "gc/shared/oopStorageSetParState.inline.hpp"
#include "gc/shared/referenceProcessor.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/universe.hpp"
@@ -188,13 +189,6 @@ void G1RootProcessor::process_vm_roots(G1RootClosures* closures,
}
}

{
G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::JNIRoots, worker_id);
if (_process_strong_tasks.try_claim_task(G1RP_PS_JNIHandles_oops_do)) {
JNIHandles::oops_do(strong_roots);
}
}

{
G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::ObjectSynchronizerRoots, worker_id);
if (_process_strong_tasks.try_claim_task(G1RP_PS_ObjectSynchronizer_oops_do)) {
@@ -225,11 +219,10 @@ void G1RootProcessor::process_vm_roots(G1RootClosures* closures,
}
#endif

{
G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::VMGlobalRoots, worker_id);
if (_process_strong_tasks.try_claim_task(G1RP_PS_VMGlobal_oops_do)) {
OopStorageSet::vm_global()->oops_do(strong_roots);
}
for (int i = 0; i < _oop_storage_set_strong_par_state.par_state_count(); ++i) {
G1GCPhaseTimes::GCParPhases phase = G1GCPhaseTimes::GCParPhases(G1GCPhaseTimes::StrongOopStorageSetRoots + i);
G1GCParPhaseTimesTracker x(phase_times, phase, worker_id);
_oop_storage_set_strong_par_state.par_state(i)->oops_do(closures->strong_oops());
}
}

@@ -25,6 +25,7 @@
#ifndef SHARE_GC_G1_G1ROOTPROCESSOR_HPP
#define SHARE_GC_G1_G1ROOTPROCESSOR_HPP

#include "gc/shared/oopStorageSetParState.hpp"
#include "gc/shared/strongRootsScope.hpp"
#include "memory/allocation.hpp"
#include "runtime/mutex.hpp"
@@ -49,13 +50,12 @@ class G1RootProcessor : public StackObj {
G1CollectedHeap* _g1h;
SubTasksDone _process_strong_tasks;
StrongRootsScope _srs;
OopStorageSetStrongParState<false, false> _oop_storage_set_strong_par_state;

enum G1H_process_roots_tasks {
G1RP_PS_Universe_oops_do,
G1RP_PS_JNIHandles_oops_do,
G1RP_PS_ObjectSynchronizer_oops_do,
G1RP_PS_Management_oops_do,
G1RP_PS_VMGlobal_oops_do,
G1RP_PS_ClassLoaderDataGraph_oops_do,
G1RP_PS_jvmti_oops_do,
G1RP_PS_CodeCache_oops_do,
@@ -49,13 +49,13 @@ static OopStorage* make_oopstorage(const char* name) {
}

void OopStorageSet::initialize() {
storages[jni_global_index] = make_oopstorage("JNI global");
storages[vm_global_index] = make_oopstorage("VM global");
storages[jni_weak_index] = make_oopstorage("JNI weak");
storages[vm_weak_index] = make_oopstorage("VM weak");
storages[string_table_weak_index] = make_oopstorage("StringTable weak");
storages[jni_global_index] = make_oopstorage("JNI Global");
storages[vm_global_index] = make_oopstorage("VM Global");
storages[jni_weak_index] = make_oopstorage("JNI Weak");
storages[vm_weak_index] = make_oopstorage("VM Weak");
storages[string_table_weak_index] = make_oopstorage("StringTable Weak");
storages[resolved_method_table_weak_index] =
make_oopstorage("ResolvedMethodTable weak");
make_oopstorage("ResolvedMethodTable Weak");

// Ensure we have all of them.
STATIC_ASSERT(all_count == 6);
@@ -41,6 +41,9 @@ class OopStorageSetStrongParState {

template <typename Closure>
void oops_do(Closure* cl);

ParStateType* par_state(int i) const { return _par_states.at(i); }
int par_state_count() const { return _par_states.count(); }
};

#endif // SHARE_GC_SHARED_OOPSTORAGESETPARSTATE_HPP
@@ -42,5 +42,4 @@ void OopStorageSetStrongParState<concurrent, is_const>::oops_do(Closure* cl) {
}
}


#endif // SHARE_GC_SHARED_OOPSTORAGESETPARSTATE_INLINE_HPP
@@ -177,9 +177,9 @@ ReferenceProcessorPhaseTimes::ReferenceProcessorPhaseTimes(GCTimer* gc_timer, ui
_processing_is_mt(false), _gc_timer(gc_timer) {

for (uint i = 0; i < ReferenceProcessor::RefSubPhaseMax; i++) {
_sub_phases_worker_time_sec[i] = new WorkerDataArray<double>(SubPhasesParWorkTitle[i], max_gc_threads);
_sub_phases_worker_time_sec[i] = new WorkerDataArray<double>(NULL, SubPhasesParWorkTitle[i], max_gc_threads);
}
_phase2_worker_time_sec = new WorkerDataArray<double>(Phase2ParWorkTitle, max_gc_threads);
_phase2_worker_time_sec = new WorkerDataArray<double>(NULL, Phase2ParWorkTitle, max_gc_threads);

reset();
}
@@ -91,7 +91,7 @@ WeakProcessorPhaseTimes::WeakProcessorPhaseTimes(uint max_threads) :
for ( ; !it.is_end(); ++it) {
assert(size_t(wpt - _worker_data) < ARRAY_SIZE(_worker_data), "invariant");
const char* description = it->name();
*wpt = new WorkerDataArray<double>(description, _max_threads);
*wpt = new WorkerDataArray<double>(NULL, description, _max_threads);
(*wpt)->create_thread_work_items("Dead", DeadItems);
(*wpt)->create_thread_work_items("Total", TotalItems);
wpt++;
@@ -38,14 +38,15 @@ class WorkerDataArray : public CHeapObj<mtGC> {
private:
T* _data;
uint _length;
const char* _title;
const char* _short_name; // Short name for JFR
const char* _title; // Title for logging.

bool _is_serial;

WorkerDataArray<size_t>* _thread_work_items[MaxThreadWorkItems];

public:
WorkerDataArray(const char* title, uint length, bool is_serial = false);
WorkerDataArray(const char* short_name, const char* title, uint length, bool is_serial = false);
~WorkerDataArray();

// Create an integer sub-item at the given index to this WorkerDataArray. If length_override
@@ -78,6 +79,10 @@ class WorkerDataArray : public CHeapObj<mtGC> {
return _title;
}

const char* short_name() const {
return _short_name;
}

void reset();
void set_all(T value);

@@ -30,9 +30,10 @@
#include "utilities/ostream.hpp"

template <typename T>
WorkerDataArray<T>::WorkerDataArray(const char* title, uint length, bool is_serial) :
WorkerDataArray<T>::WorkerDataArray(const char* short_name, const char* title, uint length, bool is_serial) :
_data(NULL),
_length(length),
_short_name(short_name),
_title(title),
_is_serial(is_serial) {
assert(length > 0, "Must have some workers to store data for");
@@ -70,7 +71,7 @@ void WorkerDataArray<T>::create_thread_work_items(const char* title, uint index,
assert(index < MaxThreadWorkItems, "Tried to access thread work item %u (max %u)", index, MaxThreadWorkItems);
assert(_thread_work_items[index] == NULL, "Tried to overwrite existing thread work item");
uint length = length_override != 0 ? length_override : _length;
_thread_work_items[index] = new WorkerDataArray<size_t>(title, length);
_thread_work_items[index] = new WorkerDataArray<size_t>(NULL, title, length);
}

template <typename T>

0 comments on commit 18cddad

Please sign in to comment.