/
githubmirror
/
panama-vector
Обзор
Документация
Войти
/
githubmirror
/
panama-vector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/hotspot/share/gc/shared/taskqueue.hpp
665 строк
21 KB
Ivan Walulya
8341630: G1: Adopt PartialArrayState to consolidate marking stack in concurrent marking
22 янв 2026, 08:38
22 янв 2026, 08:38
38a8309
Код
Авторство
О чём код?
/* * Copyright (c) 2001, 2026, 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_SHARED_TASKQUEUE_HPP #define SHARE_GC_SHARED_TASKQUEUE_HPP #include "cppstdlib/type_traits.hpp" #include "memory/allocation.hpp" #include "memory/padded.hpp" #include "metaprogramming/primitiveConversions.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/atomic.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/ostream.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/stack.hpp" #if TASKQUEUE_STATS class TaskQueueStats { public: enum StatId { push, // number of taskqueue pushes pop, // number of taskqueue pops pop_slow, // subset of taskqueue pops that were done slow-path steal_attempt, // number of taskqueue steal attempts steal_empty, // number of empty taskqueues steal_contended, // number of contended steals steal_success, // number of successful steals steal_max_contended_in_a_row, // maximum number of contended steals in a row steal_bias_drop, // number of times the bias has been dropped overflow, // number of overflow pushes overflow_max_len, // max length of overflow stack last_stat_id }; public: inline TaskQueueStats() { reset(); } inline void record_push() { ++_stats[push]; } inline void record_pop() { ++_stats[pop]; } inline void record_pop_slow() { record_pop(); ++_stats[pop_slow]; } inline void record_steal_attempt(uint kind) { ++_stats[steal_attempt]; ++_stats[steal_empty + kind]; } inline void record_contended_in_a_row(uint in_a_row) { if (_stats[steal_max_contended_in_a_row] < in_a_row) { _stats[steal_max_contended_in_a_row] = in_a_row; } } inline void record_bias_drop() { ++_stats[steal_bias_drop]; } inline void record_overflow(size_t new_length); TaskQueueStats & operator +=(const TaskQueueStats & addend); inline size_t get(StatId id) const { return _stats[id]; } inline const size_t* get() const { return _stats; } inline void reset(); // Print the specified line of the header (does not include a line separator). static void print_header(unsigned int line, outputStream* const stream = tty, unsigned int width = 11); // Print the statistics (does not include a line separator). void print(outputStream* const stream = tty, unsigned int width = 11) const; DEBUG_ONLY(void verify() const;) private: size_t _stats[last_stat_id]; static const char * const _names[last_stat_id]; }; void TaskQueueStats::record_overflow(size_t new_len) { ++_stats[overflow]; if (new_len > _stats[overflow_max_len]) _stats[overflow_max_len] = new_len; } void TaskQueueStats::reset() { memset(_stats, 0, sizeof(_stats)); } #endif // TASKQUEUE_STATS // Helper for TaskQueueSuper, encoding {queue index, tag} pair in a form that // supports atomic access to the pair. class TaskQueueAge { friend struct PrimitiveConversions::Translate<TaskQueueAge>; public: // Internal type used for indexing the queue, and for the tag. using idx_t = NOT_LP64(uint16_t) LP64_ONLY(uint32_t); explicit TaskQueueAge(size_t data = 0) : _data{data} {} TaskQueueAge(idx_t top, idx_t tag) : _fields{top, tag} {} idx_t top() const { return _fields._top; } idx_t tag() const { return _fields._tag; } bool operator==(const TaskQueueAge& other) const { return _data == other._data; } private: struct Fields { idx_t _top; idx_t _tag; }; union { size_t _data; // Provides access to _fields as a single integral value. Fields _fields; }; // _data must be able to hold combined _fields. Must be equal to ensure // there isn't any padding that could be uninitialized by 2-arg ctor. static_assert(sizeof(_data) == sizeof(_fields)); }; // Support for Atomic<TaskQueueAge>. template<> struct PrimitiveConversions::Translate<TaskQueueAge> : public std::true_type { using Value = TaskQueueAge; using Decayed = decltype(TaskQueueAge::_data); static Decayed decay(Value x) { return x._data; } static Value recover(Decayed x) { return Value(x); } }; // TaskQueueSuper collects functionality common to all GenericTaskQueue instances. template <unsigned int N, MemTag MT> class TaskQueueSuper: public CHeapObj<MT> { protected: using Age = TaskQueueAge; using idx_t = Age::idx_t; static_assert(N == idx_t(N)); // Ensure N fits in an idx_t. // N must be a power of 2 for computing modulo via masking. // N must be >= 2 for the algorithm to work at all, though larger is better. static_assert(N >= 2); static_assert(is_power_of_2(N)); static const uint MOD_N_MASK = N - 1; uint bottom_relaxed() const { return _bottom.load_relaxed(); } uint bottom_acquire() const { return _bottom.load_acquire(); } void set_bottom_relaxed(uint new_bottom) { _bottom.store_relaxed(new_bottom); } void release_set_bottom(uint new_bottom) { _bottom.release_store(new_bottom); } Age age_relaxed() const { return _age.load_relaxed(); } void set_age_relaxed(Age new_age) { _age.store_relaxed(new_age); } bool par_set_age(Age old_age, Age new_age) { return _age.compare_set(old_age, new_age); } idx_t age_top_relaxed() const { return _age.load_relaxed().top(); } // These both operate mod N. static uint increment_index(uint ind) { return (ind + 1) & MOD_N_MASK; } static uint decrement_index(uint ind) { return (ind - 1) & MOD_N_MASK; } // Returns a number in the range [0..N). If the result is "N-1", it should be // interpreted as 0. uint dirty_size(uint bot, uint top) const { return (bot - top) & MOD_N_MASK; } // Returns the size corresponding to the given "bot" and "top". uint clean_size(uint bot, uint top) const { uint sz = dirty_size(bot, top); // Has the queue "wrapped", so that bottom is less than top? There's a // complicated special case here. A pair of threads could perform pop_local // and pop_global operations concurrently, starting from a state in which // _bottom == _top+1. The pop_local could succeed in decrementing _bottom, // and the pop_global in incrementing _top (in which case the pop_global // will be awarded the contested queue element.) The resulting state must // be interpreted as an empty queue. (We only need to worry about one such // event: only the queue owner performs pop_local's, and several concurrent // threads attempting to perform the pop_global will all perform the same // CAS, and only one can succeed.) Any stealing thread that reads after // either the increment or decrement will see an empty queue, and will not // join the competitors. The "sz == -1" / "sz == N-1" state will not be // modified by concurrent threads, so the owner thread can reset the state // to _bottom == top so subsequent pushes will be performed normally. return (sz == N - 1) ? 0 : sz; } // Assert that we're not in the underflow state where bottom has // been decremented past top, so that _bottom+1 mod N == top. See // the discussion in clean_size. void assert_not_underflow(uint bot, uint top) const { assert_not_underflow(dirty_size(bot, top)); } void assert_not_underflow(uint dirty_size) const { assert(dirty_size != N - 1, "invariant"); } private: DEFINE_PAD_MINUS_SIZE(0, DEFAULT_PADDING_SIZE, 0); // Index of the first free element after the last one pushed (mod N). Atomic<uint> _bottom; DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(_bottom)); // top() is the index of the oldest pushed element (mod N), and tag() // is the associated epoch, to distinguish different modifications of // the age. There is no available element if top() == _bottom or // (_bottom - top()) mod N == N-1; the latter indicates underflow // during concurrent pop_local/pop_global. Atomic<Age> _age; DEFINE_PAD_MINUS_SIZE(2, DEFAULT_PADDING_SIZE, sizeof(_age)); NONCOPYABLE(TaskQueueSuper); public: TaskQueueSuper() : _bottom(0), _age() {} // Assert the queue is empty. // Unreliable if there are concurrent pushes or pops. void assert_empty() const { assert(bottom_relaxed() == age_top_relaxed(), "not empty"); } bool is_empty() const { return size() == 0; } // Return an estimate of the number of elements in the queue. // Treats pop_local/pop_global race that underflows as empty. uint size() const { return clean_size(bottom_relaxed(), age_top_relaxed()); } // Discard the contents of the queue. void set_empty() { set_bottom_relaxed(0); set_age_relaxed(Age()); } // Maximum number of elements allowed in the queue. This is two less // than the actual queue size, so that a full queue can be distinguished // from underflow involving pop_local and concurrent pop_global operations // in GenericTaskQueue. uint max_elems() const { return N - 2; } // The result of a pop_global operation. The value order of this must correspond // to the order in the corresponding TaskQueueStats StatId. enum class PopResult : uint { Empty = 0, // Queue has been empty. t is undefined. Contended = 1, // Contention prevented successful retrieval, queue most likely contains elements. t is undefined. Success = 2 // Successfully retrieved an element, t contains it. }; TASKQUEUE_STATS_ONLY(void record_steal_attempt(PopResult kind) { stats.record_steal_attempt((uint)kind); }) TASKQUEUE_STATS_ONLY(TaskQueueStats stats;) }; // // GenericTaskQueue implements an ABP, Aurora-Blumofe-Plaxton, double- // ended-queue (deque), intended for use in work stealing. Queue operations // are non-blocking. // // A queue owner thread performs push() and pop_local() operations on one end // of the queue, while other threads may steal work using the pop_global() // method. // // The main difference to the original algorithm is that this // implementation allows wrap-around at the end of its allocated // storage, which is an array. // // The original paper is: // // Arora, N. S., Blumofe, R. D., and Plaxton, C. G. // Thread scheduling for multiprogrammed multiprocessors. // Theory of Computing Systems 34, 2 (2001), 115-144. // // The following paper provides an correctness proof and an // implementation for weakly ordered memory models including (pseudo-) // code containing memory barriers for a Chase-Lev deque. Chase-Lev is // similar to ABP, with the main difference that it allows resizing of the // underlying storage: // // Le, N. M., Pop, A., Cohen A., and Nardell, F. Z. // Correct and efficient work-stealing for weak memory models // Proceedings of the 18th ACM SIGPLAN symposium on Principles and // practice of parallel programming (PPoPP 2013), 69-80 // template <class E, MemTag MT, unsigned int N = TASKQUEUE_SIZE> class GenericTaskQueue: public TaskQueueSuper<N, MT> { protected: typedef typename TaskQueueSuper<N, MT>::Age Age; typedef typename TaskQueueSuper<N, MT>::idx_t idx_t; using TaskQueueSuper<N, MT>::MOD_N_MASK; using TaskQueueSuper<N, MT>::bottom_relaxed; using TaskQueueSuper<N, MT>::bottom_acquire; using TaskQueueSuper<N, MT>::set_bottom_relaxed; using TaskQueueSuper<N, MT>::release_set_bottom; using TaskQueueSuper<N, MT>::age_relaxed; using TaskQueueSuper<N, MT>::set_age_relaxed; using TaskQueueSuper<N, MT>::par_set_age; using TaskQueueSuper<N, MT>::age_top_relaxed; using TaskQueueSuper<N, MT>::increment_index; using TaskQueueSuper<N, MT>::decrement_index; using TaskQueueSuper<N, MT>::dirty_size; using TaskQueueSuper<N, MT>::clean_size; using TaskQueueSuper<N, MT>::assert_not_underflow; public: typedef typename TaskQueueSuper<N, MT>::PopResult PopResult; using TaskQueueSuper<N, MT>::max_elems; using TaskQueueSuper<N, MT>::size; #if TASKQUEUE_STATS using TaskQueueSuper<N, MT>::stats; #endif private: // Slow path for pop_local, dealing with possible conflict with pop_global. bool pop_local_slow(uint localBot, Age oldAge); public: typedef E element_type; // Initializes the queue to empty. GenericTaskQueue(); // Push the task "t" on the queue. Returns "false" iff the queue is full. inline bool push(E t); // Attempts to claim a task from the "local" end of the queue (the most // recently pushed) as long as the number of entries exceeds the threshold. // If successfully claims a task, returns true and sets t to the task; // otherwise, returns false and t is unspecified. May fail and return // false because of a successful steal by pop_global. inline bool pop_local(E& t, uint threshold = 0); // Like pop_local(), but uses the "global" end of the queue (the least // recently pushed). PopResult pop_global(E& t); // Delete any resource associated with the queue. ~GenericTaskQueue(); // Apply fn to each element in the task queue. The queue must not // be modified while iterating. template<typename Fn> void iterate(Fn fn); private: // Base class has trailing padding. // Element array. E* _elems; DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(E*)); // Queue owner local variables. Not to be accessed by other threads. static const uint InvalidQueueId = uint(-1); uint _last_stolen_queue_id; // The id of the queue we last stole from int _seed; // Current random seed used for selecting a random queue during stealing. DEFINE_PAD_MINUS_SIZE(2, DEFAULT_PADDING_SIZE, sizeof(uint) + sizeof(int)); public: int next_random_queue_id(); void set_last_stolen_queue_id(uint id) { _last_stolen_queue_id = id; } uint last_stolen_queue_id() const { return _last_stolen_queue_id; } bool is_last_stolen_queue_id_valid() const { return _last_stolen_queue_id != InvalidQueueId; } void invalidate_last_stolen_queue_id() { TASKQUEUE_STATS_ONLY(stats.record_bias_drop();) _last_stolen_queue_id = InvalidQueueId; } }; // OverflowTaskQueue is a TaskQueue that also includes an overflow stack for // elements that do not fit in the TaskQueue. // // This class hides two methods from super classes: // // push() - push onto the task queue or, if that fails, onto the overflow stack // is_empty() - return true if both the TaskQueue and overflow stack are empty // // Note that size() is not hidden--it returns the number of elements in the // TaskQueue, and does not include the size of the overflow stack. This // simplifies replacement of GenericTaskQueues with OverflowTaskQueues. template<class E, MemTag MT, unsigned int N = TASKQUEUE_SIZE> class OverflowTaskQueue: public GenericTaskQueue<E, MT, N> { public: typedef Stack<E, MT> overflow_t; typedef GenericTaskQueue<E, MT, N> taskqueue_t; TASKQUEUE_STATS_ONLY(using taskqueue_t::stats;) // Push task t onto the queue or onto the overflow stack. Return true. inline bool push(E t); // Try to push task t onto the queue only. Returns true if successful, false otherwise. inline bool try_push_to_taskqueue(E t); // Attempt to pop from the overflow stack; return true if anything was popped. inline bool pop_overflow(E& t); inline overflow_t* overflow_stack() { return &_overflow_stack; } inline bool taskqueue_empty() const { return taskqueue_t::is_empty(); } inline bool overflow_empty() const { return _overflow_stack.is_empty(); } inline bool is_empty() const { return taskqueue_empty() && overflow_empty(); } private: overflow_t _overflow_stack; }; class TaskQueueSetSuper { public: // Assert all queues in the set are empty. NOT_DEBUG(void assert_empty() const {}) DEBUG_ONLY(virtual void assert_empty() const = 0;) // Tasks in queue virtual uint tasks() const = 0; }; template <MemTag MT> class TaskQueueSetSuperImpl: public CHeapObj<MT>, public TaskQueueSetSuper { }; template<class T, MemTag MT> class GenericTaskQueueSet: public TaskQueueSetSuperImpl<MT> { public: typedef typename T::element_type E; typedef typename T::PopResult PopResult; private: uint _n; T** _queues; // Attempts to steal an element from a foreign queue (!= queue_num), setting // the result in t. Validity of this value and the return value is the same // as for the last pop_global() operation. PopResult steal_best_of_2(uint queue_num, E& t); public: GenericTaskQueueSet(uint n); ~GenericTaskQueueSet(); // Set the i'th queue to the provided queue. // Does not transfer ownership of the queue to this queue set. void register_queue(uint i, T* q); T* queue(uint n); // Try to steal a task from some other queue than queue_num. It may perform several attempts at doing so. // Returns if stealing succeeds, and sets "t" to the stolen task. bool steal(uint queue_num, E& t); DEBUG_ONLY(virtual void assert_empty() const;) virtual uint tasks() const; uint size() const { return _n; } #if TASKQUEUE_STATS private: static void print_taskqueue_stats_hdr(outputStream* const st, const char* label); public: void print_taskqueue_stats(outputStream* const st, const char* label); void reset_taskqueue_stats(); // Prints taskqueue set statistics into gc+task+stats=trace and resets // its statistics. void print_and_reset_taskqueue_stats(const char* label); #endif // TASKQUEUE_STATS }; template<class T, MemTag MT> void GenericTaskQueueSet<T, MT>::register_queue(uint i, T* q) { assert(i < _n, "index out of range."); _queues[i] = q; } template<class T, MemTag MT> T* GenericTaskQueueSet<T, MT>::queue(uint i) { assert(i < _n, "index out of range."); return _queues[i]; } #ifdef ASSERT template<class T, MemTag MT> void GenericTaskQueueSet<T, MT>::assert_empty() const { for (uint j = 0; j < _n; j++) { _queues[j]->assert_empty(); } } #endif // ASSERT template<class T, MemTag MT> uint GenericTaskQueueSet<T, MT>::tasks() const { uint n = 0; for (uint j = 0; j < _n; j++) { n += _queues[j]->size(); } return n; } // When to terminate from the termination protocol. class TerminatorTerminator: public CHeapObj<mtInternal> { public: virtual bool should_exit_termination() = 0; }; class ObjArrayTask { public: ObjArrayTask(oop o = nullptr, int idx = 0): _obj(o), _index(idx) { } ObjArrayTask(oop o, size_t idx): _obj(o), _index(int(idx)) { assert(idx <= size_t(max_jint), "too big"); } // Trivially copyable, for use in GenericTaskQueue. inline oop obj() const { return _obj; } inline int index() const { return _index; } DEBUG_ONLY(bool is_valid() const); // Tasks to be pushed/popped must be valid. private: oop _obj; int _index; }; class PartialArrayState; // Discriminated union over oop/oop*, narrowOop*, and PartialArrayState. // Uses a low tag in the associated pointer to identify the category. // Oop/oop* are overloaded using the same tag because they can not appear at the // same time. // Used as a task queue element type. class ScannerTask { void* _p; static const uintptr_t OopTag = 0; static const uintptr_t NarrowOopTag = 1; static const uintptr_t PartialArrayTag = 2; static const uintptr_t TagSize = 2; static const uintptr_t TagAlignment = 1 << TagSize; static const uintptr_t TagMask = TagAlignment - 1; static void* encode(void* p, uintptr_t tag) { assert(is_aligned(p, TagAlignment), "misaligned: " PTR_FORMAT, p2i(p)); return static_cast<char*>(p) + tag; } uintptr_t raw_value() const { return reinterpret_cast<uintptr_t>(_p); } bool has_tag(uintptr_t tag) const { return (raw_value() & TagMask) == tag; } void* decode(uintptr_t tag) const { assert(has_tag(tag), "precondition"); return static_cast<char*>(_p) - tag; } public: ScannerTask() : _p(nullptr) {} explicit ScannerTask(oop p) : _p(encode(p, OopTag)) {} explicit ScannerTask(oop* p) : _p(encode(p, OopTag)) {} explicit ScannerTask(narrowOop* p) : _p(encode(p, NarrowOopTag)) {} explicit ScannerTask(PartialArrayState* state) : _p(encode(state, PartialArrayTag)) {} // Trivially copyable. // Predicate implementations assume OopTag == 0, others are powers of 2. bool is_oop_ptr() const { return (raw_value() & (NarrowOopTag | PartialArrayTag)) == 0; } bool is_narrow_oop_ptr() const { return (raw_value() & NarrowOopTag) != 0; } bool is_partial_array_state() const { return (raw_value() & PartialArrayTag) != 0; } bool is_null() const { return _p == nullptr; } oop* to_oop_ptr() const { return static_cast<oop*>(decode(OopTag)); } oop to_oop() const { return cast_to_oop(decode(OopTag)); } narrowOop* to_narrow_oop_ptr() const { return static_cast<narrowOop*>(decode(NarrowOopTag)); } PartialArrayState* to_partial_array_state() const { return static_cast<PartialArrayState*>(decode(PartialArrayTag)); } }; #endif // SHARE_GC_SHARED_TASKQUEUE_HPP