blob: 163dd2ad685ae78b9b9c1c8d6653d6725f0be4d2 [file]
/*
* Copyright (c) 2021, 2025, 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_Z_ZINDEXDISTRIBUTOR_INLINE_HPP
#define SHARE_GC_Z_ZINDEXDISTRIBUTOR_INLINE_HPP
#include "gc/z/zIndexDistributor.hpp"
#include "gc/shared/gc_globals.hpp"
#include "gc/z/zGlobals.hpp"
#include "runtime/atomic.hpp"
#include "runtime/os.hpp"
#include "runtime/thread.hpp"
#include "utilities/align.hpp"
#include "utilities/powerOfTwo.hpp"
class ZIndexDistributorStriped : public CHeapObj<mtGC> {
static const int MemSize = 4096;
static const int StripeCount = MemSize / ZCacheLineSize;
const int _count;
// For claiming a stripe
volatile int _claim_stripe;
// For claiming inside a stripe
char _mem[MemSize + ZCacheLineSize];
int claim_stripe() {
return Atomic::fetch_then_add(&_claim_stripe, 1, memory_order_relaxed);
}
volatile int* claim_addr(int index) {
return (volatile int*)(align_up(_mem, ZCacheLineSize) + (size_t)index * ZCacheLineSize);
}
public:
ZIndexDistributorStriped(int count)
: _count(count),
_claim_stripe(0),
_mem() {
memset(_mem, 0, MemSize + ZCacheLineSize);
}
template <typename Function>
void do_indices(Function function) {
const int stripe_max = _count / StripeCount;
// Use claiming
for (int i; (i = claim_stripe()) < StripeCount;) {
for (int index; (index = Atomic::fetch_then_add(claim_addr(i), 1, memory_order_relaxed)) < stripe_max;) {
if (!function(i * stripe_max + index)) {
return;
}
}
}
// Use stealing
for (int i = 0; i < StripeCount; i++) {
for (int index; (index = Atomic::fetch_then_add(claim_addr(i), 1, memory_order_relaxed)) < stripe_max;) {
if (!function(i * stripe_max + index)) {
return;
}
}
}
}
static size_t get_count(size_t max_count) {
// Must be multiple of the StripeCount
return align_up(max_count, StripeCount);
}
};
class ZIndexDistributorClaimTree : public CHeapObj<mtGC> {
friend class ZIndexDistributorTest;
private:
// The N - 1 levels are used to claim a segment in the
// next level the Nth level claims an index.
static constexpr int N = 4;
static constexpr int ClaimLevels = N - 1;
// Describes the how the number of indices increases when going up from the given level
static constexpr int level_multiplier(int level) {
assert(level < ClaimLevels, "Must be");
constexpr int array[ClaimLevels]{16, 16, 16};
return array[level];
}
// Number of indices in one segment at the last level
const int _last_level_segment_size_shift;
// For deallocation
char* _malloced;
// Contains the tree of claim variables
volatile int* _claim_array;
// Claim index functions
// Number of claim entries at the given level
static constexpr int claim_level_size(int level) {
if (level == 0) {
return 1;
}
return level_multiplier(level - 1) * claim_level_size(level - 1);
}
// The index the next level starts at
static constexpr int claim_level_end_index(int level) {
if (level == 0) {
// First level uses padding
return ZCacheLineSize / sizeof(int);
}
return claim_level_size(level) + claim_level_end_index(level - 1);
}
static constexpr int claim_level_start_index(int level) {
return claim_level_end_index(level - 1);
}
// Total size used to hold all claim variables
static size_t claim_variables_size() {
return sizeof(int) * (size_t)claim_level_end_index(ClaimLevels);
}
// Returns the index of the start of the current segment of the current level
static constexpr int claim_level_index_accumulate(int* indices, int level, int acc = 1) {
if (level == 0) {
return acc * indices[level];
}
return acc * indices[level] + claim_level_index_accumulate(indices, level - 1, acc * level_multiplier(level));
}
static constexpr int claim_level_index(int* indices, int level) {
assert(level > 0, "Must be");
// The claim index for the current level is found in the previous levels
return claim_level_index_accumulate(indices, level - 1);
}
static constexpr int claim_index(int* indices, int level) {
if (level == 0) {
return 0;
}
return claim_level_start_index(level) + claim_level_index(indices, level);
}
// Claim functions
int claim(int index) {
return Atomic::fetch_then_add(&_claim_array[index], 1, memory_order_relaxed);
}
int claim_at(int* indices, int level) {
const int index = claim_index(indices, level);
const int value = claim(index);
#if 0
if (level == 0) { tty->print_cr("Claim at: %d index: %d got: %d", indices[0], index, value); }
else if (level == 1) { tty->print_cr("Claim at: %d %d index: %d got: %d", indices[0], indices[1], index, value); }
else if (level == 2) { tty->print_cr("Claim at: %d %d %d index: %d got: %d", indices[0], indices[1], indices[2], index, value); }
else if (level == 3) { tty->print_cr("Claim at: %d %d %d %d index: %d got: %d", indices[0], indices[1], indices[2], indices[3], index, value); }
else if (level == 4) { tty->print_cr("Claim at: %d %d %d %d %d index: %d got: %d", indices[0], indices[1], indices[2], indices[3], indices[4], index, value); }
#endif
return value;
}
template <typename Function>
void claim_and_do(Function function, int* indices, int level) {
if (level < N) {
// Visit ClaimLevels and the last level
const int ci = claim_index(indices, level);
for (indices[level] = 0; (indices[level] = claim(ci)) < level_segment_size(level);) {
claim_and_do(function, indices, level + 1);
}
return;
}
doit(function, indices);
}
template <typename Function>
void steal_and_do(Function function, int* indices, int level) {
for (indices[level] = 0; indices[level] < level_segment_size(level); indices[level]++) {
const int next_level = level + 1;
// First try to claim at next level
claim_and_do(function, indices, next_level);
// Then steal at next level
if (next_level < ClaimLevels) {
steal_and_do(function, indices, next_level);
}
}
}
// Functions to claimed values to an index
static constexpr int levels_size(int level) {
if (level == 0) {
return level_multiplier(0);
}
return level_multiplier(level) * levels_size(level - 1);
}
static int constexpr level_to_last_level_count_coverage(int level) {
return levels_size(ClaimLevels - 1) / levels_size(level);
}
static int constexpr calculate_last_level_count(int* indices, int level = 0) {
if (level == N - 1) {
return 0;
}
return indices[level] * level_to_last_level_count_coverage(level) + calculate_last_level_count(indices, level + 1);
}
int calculate_index(int* indices) {
const int segment_start = calculate_last_level_count(indices) << _last_level_segment_size_shift;
return segment_start + indices[N - 1];
}
int level_segment_size(int level) {
if (level == ClaimLevels) {
return 1 << _last_level_segment_size_shift;
}
return level_multiplier(level);
}
template <typename Function>
void doit(Function function, int* indices) {
//const int index = first_level * second_level_max * _third_level_max + second_level * _third_level_max + third_level;
const int index = calculate_index(indices);
#if 0
tty->print_cr("doit Thread: " PTR_FORMAT ": %d %d %d %d => %d",
p2i(Thread::current()),
indices[0], indices[1], indices[2], indices[3], index);
#endif
function(index);
}
static int last_level_segment_size_shift(int count) {
const int last_level_size = count / levels_size(ClaimLevels - 1);
assert(levels_size(ClaimLevels - 1) * last_level_size == count, "Not exactly divisible");
return log2i_exact(last_level_size);
}
public:
ZIndexDistributorClaimTree(int count)
: _last_level_segment_size_shift(last_level_segment_size_shift(count)),
_malloced((char*)os::malloc(claim_variables_size() + os::vm_page_size(), mtGC)),
_claim_array((volatile int*)align_up(_malloced, os::vm_page_size())) {
assert((levels_size(ClaimLevels - 1) << _last_level_segment_size_shift) == count, "Incorrectly setup");
#if 0
tty->print_cr("ZIndexDistributorClaimTree count: %d byte size: %zu", count, claim_variables_size() + os::vm_page_size());
#endif
memset(_malloced, 0, claim_variables_size() + os::vm_page_size());
}
~ZIndexDistributorClaimTree() {
os::free(_malloced);
}
template <typename Function>
void do_indices(Function function) {
int indices[N];
claim_and_do(function, indices, 0 /* level */);
steal_and_do(function, indices, 0 /* level */);
}
static size_t get_count(size_t max_count) {
// Must be at least claim_level_size(ClaimLevels) and a power of two
const size_t min_count = claim_level_size(ClaimLevels);
return round_up_power_of_2(MAX2(max_count, min_count));
}
};
// Using dynamically allocated objects just to be able to evaluate
// different strategies. Revert when one has been choosen.
inline void* ZIndexDistributor::create_strategy(int count) {
switch (ZIndexDistributorStrategy) {
case 0: return new ZIndexDistributorClaimTree(count);
case 1: return new ZIndexDistributorStriped(count);
default: fatal("Unknown ZIndexDistributorStrategy"); return nullptr;
};
}
inline ZIndexDistributor::ZIndexDistributor(int count)
: _strategy(create_strategy(count)) {}
inline ZIndexDistributor::~ZIndexDistributor() {
switch (ZIndexDistributorStrategy) {
case 0: delete static_cast<ZIndexDistributorClaimTree*>(_strategy); break;
case 1: delete static_cast<ZIndexDistributorStriped*>(_strategy); break;
default: fatal("Unknown ZIndexDistributorStrategy"); break;
};
}
template <typename Strategy>
inline Strategy* ZIndexDistributor::strategy() {
return static_cast<Strategy*>(_strategy);
}
template <typename Function>
inline void ZIndexDistributor::do_indices(Function function) {
switch (ZIndexDistributorStrategy) {
case 0: strategy<ZIndexDistributorClaimTree>()->do_indices(function); break;
case 1: strategy<ZIndexDistributorStriped>()->do_indices(function); break;
default: fatal("Unknown ZIndexDistributorStrategy");
};
}
inline size_t ZIndexDistributor::get_count(size_t max_count) {
size_t required_count;
switch (ZIndexDistributorStrategy) {
case 0: required_count = ZIndexDistributorClaimTree::get_count(max_count); break;
case 1: required_count = ZIndexDistributorStriped::get_count(max_count); break;
default: fatal("Unknown ZIndexDistributorStrategy");
};
assert(max_count <= required_count, "unsupported max_count: %zu", max_count);
return required_count;
}
#endif // SHARE_GC_Z_ZINDEXDISTRIBUTOR_INLINE_HPP