cppgc: Rework prefinalizers
Move the check for whether an object is live or dead out of the prefinalizer trampoline. Moving it into the backend allows for inlining the check which avoids a call to the trampoline for live objects. On catapult benchmarks (e.g. cnn:2021, nytimes:2020), there's often ~2k finalizers registered. In order to avoid memory overhead in the range of a few KB, we store the fact whether the object points to the base object payload in the LSB of the pointer. For caged builds this is replaced with just storing the index into the cage for both object and base object payload. Locally saves around ~10% of atomic sweeping processing time which is in the order of .05ms. Bug: v8:12698 Change-Id: I198205a6b1d57fc2df821ee4e73e53dc6f825ff5 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3497764 Reviewed-by: Omer Katz <omerkatz@chromium.org> Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Cr-Commit-Position: refs/heads/main@{#79442}
This commit is contained in:
parent
4f3dd3db80
commit
cf25b3bc53
@ -14,9 +14,9 @@ namespace internal {
|
||||
|
||||
class V8_EXPORT PrefinalizerRegistration final {
|
||||
public:
|
||||
using Callback = bool (*)(const cppgc::LivenessBroker&, void*);
|
||||
using Callback = void (*)(void*);
|
||||
|
||||
PrefinalizerRegistration(void*, Callback);
|
||||
PrefinalizerRegistration(void*, const void*, Callback);
|
||||
|
||||
void* operator new(size_t, void* location) = delete;
|
||||
void* operator new(size_t) = delete;
|
||||
@ -53,21 +53,21 @@ class V8_EXPORT PrefinalizerRegistration final {
|
||||
* };
|
||||
* \endcode
|
||||
*/
|
||||
#define CPPGC_USING_PRE_FINALIZER(Class, PreFinalizer) \
|
||||
public: \
|
||||
static bool InvokePreFinalizer(const cppgc::LivenessBroker& liveness_broker, \
|
||||
void* object) { \
|
||||
static_assert(cppgc::IsGarbageCollectedOrMixinTypeV<Class>, \
|
||||
"Only garbage collected objects can have prefinalizers"); \
|
||||
Class* self = static_cast<Class*>(object); \
|
||||
if (liveness_broker.IsHeapObjectAlive(self)) return false; \
|
||||
self->PreFinalizer(); \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
CPPGC_NO_UNIQUE_ADDRESS cppgc::internal::PrefinalizerRegistration \
|
||||
prefinalizer_dummy_{this, Class::InvokePreFinalizer}; \
|
||||
#define CPPGC_USING_PRE_FINALIZER(Class, PreFinalizer) \
|
||||
public: \
|
||||
static void InvokePreFinalizer(void* object) { \
|
||||
static_assert(cppgc::IsGarbageCollectedOrMixinTypeV<Class>, \
|
||||
"Only garbage collected objects can have prefinalizers"); \
|
||||
Class* self = static_cast<Class*>(object); \
|
||||
self->PreFinalizer(); \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
CPPGC_NO_UNIQUE_ADDRESS cppgc::internal::PrefinalizerRegistration \
|
||||
prefinalizer_dummy_{this, \
|
||||
cppgc::TraceTrait<Class>::GetTraceDescriptor(this) \
|
||||
.base_object_payload, \
|
||||
Class::InvokePreFinalizer}; \
|
||||
static_assert(true, "Force semicolon.")
|
||||
|
||||
} // namespace cppgc
|
||||
|
@ -43,6 +43,7 @@ class PointerWithPayload {
|
||||
|
||||
explicit PointerWithPayload(PointerType* pointer)
|
||||
: pointer_with_payload_(reinterpret_cast<uintptr_t>(pointer)) {
|
||||
DCHECK_EQ(reinterpret_cast<uintptr_t>(pointer) & kPayloadMask, 0);
|
||||
DCHECK_EQ(GetPointer(), pointer);
|
||||
DCHECK_EQ(GetPayload(), static_cast<PayloadType>(0));
|
||||
}
|
||||
@ -57,6 +58,11 @@ class PointerWithPayload {
|
||||
Update(pointer, payload);
|
||||
}
|
||||
|
||||
PointerWithPayload(const PointerWithPayload& other) V8_NOEXCEPT = default;
|
||||
|
||||
PointerWithPayload& operator=(const PointerWithPayload& other)
|
||||
V8_NOEXCEPT = default;
|
||||
|
||||
V8_INLINE PointerType* GetPointer() const {
|
||||
return reinterpret_cast<PointerType*>(pointer_with_payload_ & kPointerMask);
|
||||
}
|
||||
@ -71,17 +77,18 @@ class PointerWithPayload {
|
||||
V8_INLINE PointerType* operator->() const { return GetPointer(); }
|
||||
|
||||
V8_INLINE void Update(PointerType* new_pointer, PayloadType new_payload) {
|
||||
DCHECK_EQ(reinterpret_cast<uintptr_t>(new_pointer) & kPayloadMask, 0);
|
||||
pointer_with_payload_ = reinterpret_cast<uintptr_t>(new_pointer) |
|
||||
static_cast<uintptr_t>(new_payload);
|
||||
DCHECK_EQ(GetPayload(), new_payload);
|
||||
DCHECK_EQ(GetPointer(), new_pointer);
|
||||
}
|
||||
|
||||
V8_INLINE void SetPointer(PointerType* newptr) {
|
||||
DCHECK_EQ(reinterpret_cast<uintptr_t>(newptr) & kPayloadMask, 0);
|
||||
pointer_with_payload_ = reinterpret_cast<uintptr_t>(newptr) |
|
||||
V8_INLINE void SetPointer(PointerType* new_pointer) {
|
||||
DCHECK_EQ(reinterpret_cast<uintptr_t>(new_pointer) & kPayloadMask, 0);
|
||||
pointer_with_payload_ = reinterpret_cast<uintptr_t>(new_pointer) |
|
||||
(pointer_with_payload_ & kPayloadMask);
|
||||
DCHECK_EQ(GetPointer(), newptr);
|
||||
DCHECK_EQ(GetPointer(), new_pointer);
|
||||
}
|
||||
|
||||
V8_INLINE PayloadType GetPayload() const {
|
||||
@ -110,6 +117,15 @@ class PointerWithPayload {
|
||||
static constexpr uintptr_t kPointerMask = ~kPayloadMask;
|
||||
|
||||
uintptr_t pointer_with_payload_ = 0;
|
||||
|
||||
friend bool operator==(const PointerWithPayload& p1,
|
||||
const PointerWithPayload& p2) {
|
||||
return p1.pointer_with_payload_ == p2.pointer_with_payload_;
|
||||
}
|
||||
friend bool operator!=(const PointerWithPayload& p1,
|
||||
const PointerWithPayload& p2) {
|
||||
return !(p1 == p2);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "include/cppgc/platform.h"
|
||||
#include "src/base/bounded-page-allocator.h"
|
||||
#include "src/base/logging.h"
|
||||
#include "src/heap/cppgc/globals.h"
|
||||
#include "src/heap/cppgc/virtual-memory.h"
|
||||
|
||||
@ -32,6 +33,12 @@ class CagedHeap final {
|
||||
(kCagedHeapReservationAlignment - 1);
|
||||
}
|
||||
|
||||
static void* AddressFromOffset(void* base, size_t offset) {
|
||||
DCHECK_LT(offset, kCagedHeapReservationSize);
|
||||
DCHECK_NOT_NULL(base);
|
||||
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base) + offset);
|
||||
}
|
||||
|
||||
static uintptr_t BaseFromAddress(const void* address) {
|
||||
return reinterpret_cast<uintptr_t>(address) &
|
||||
~(kCagedHeapReservationAlignment - 1);
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include <memory>
|
||||
|
||||
#include "src/base/platform/platform.h"
|
||||
#include "src/base/pointer-with-payload.h"
|
||||
#include "src/heap/cppgc/heap-object-header.h"
|
||||
#include "src/heap/cppgc/heap-page.h"
|
||||
#include "src/heap/cppgc/heap.h"
|
||||
#include "src/heap/cppgc/liveness-broker.h"
|
||||
@ -16,15 +18,37 @@
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
PrefinalizerRegistration::PrefinalizerRegistration(void* object,
|
||||
Callback callback) {
|
||||
PrefinalizerRegistration::PrefinalizerRegistration(
|
||||
void* object, const void* base_object_payload, Callback callback) {
|
||||
auto* page = BasePage::FromPayload(object);
|
||||
DCHECK(!page->space().is_compactable());
|
||||
page->heap().prefinalizer_handler()->RegisterPrefinalizer({object, callback});
|
||||
page->heap().prefinalizer_handler()->RegisterPrefinalizer(
|
||||
PreFinalizer(object, base_object_payload, callback));
|
||||
}
|
||||
|
||||
bool PreFinalizer::operator==(const PreFinalizer& other) const {
|
||||
return (object == other.object) && (callback == other.callback);
|
||||
return
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
(object_offset == other.object_offset)
|
||||
#else // !defined(CPPGC_CAGED_HEAP)
|
||||
(object_and_offset == other.object_and_offset)
|
||||
#endif // !defined(CPPGC_CAGED_HEAP)
|
||||
&& (callback == other.callback);
|
||||
}
|
||||
|
||||
PreFinalizer::PreFinalizer(void* object, const void* base_object_payload,
|
||||
Callback cb)
|
||||
:
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
object_offset(CagedHeap::OffsetFromAddress<uint32_t>(object)),
|
||||
base_object_payload_offset(
|
||||
CagedHeap::OffsetFromAddress<uint32_t>(base_object_payload)),
|
||||
#else // !defined(CPPGC_CAGED_HEAP)
|
||||
object_and_offset(object, (object == base_object_payload)
|
||||
? PointerType::kAtBase
|
||||
: PointerType::kInnerPointer),
|
||||
#endif // !defined(CPPGC_CAGED_HEAP)
|
||||
callback(cb) {
|
||||
}
|
||||
|
||||
PreFinalizerHandler::PreFinalizerHandler(HeapBase& heap)
|
||||
@ -48,6 +72,33 @@ void PreFinalizerHandler::RegisterPrefinalizer(PreFinalizer pre_finalizer) {
|
||||
current_ordered_pre_finalizers_->push_back(pre_finalizer);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns true in case the prefinalizer was invoked.
|
||||
V8_INLINE bool InvokeUnmarkedPrefinalizers(void* cage_base,
|
||||
const PreFinalizer& pf) {
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
void* object = CagedHeap::AddressFromOffset(cage_base, pf.object_offset);
|
||||
void* base_object_payload =
|
||||
CagedHeap::AddressFromOffset(cage_base, pf.base_object_payload_offset);
|
||||
#else // !defined(CPPGC_CAGED_HEAP)
|
||||
void* object = pf.object_and_offset.GetPointer();
|
||||
void* base_object_payload =
|
||||
pf.object_and_offset.GetPayload() == PreFinalizer::PointerType::kAtBase
|
||||
? object
|
||||
: reinterpret_cast<void*>(BasePage::FromPayload(object)
|
||||
->ObjectHeaderFromInnerAddress(object)
|
||||
.ObjectStart());
|
||||
#endif // !defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
if (HeapObjectHeader::FromObject(base_object_payload).IsMarked())
|
||||
return false;
|
||||
pf.callback(object);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void PreFinalizerHandler::InvokePreFinalizers() {
|
||||
StatsCollector::EnabledScope stats_scope(heap_.stats_collector(),
|
||||
StatsCollector::kAtomicSweep);
|
||||
@ -55,11 +106,15 @@ void PreFinalizerHandler::InvokePreFinalizers() {
|
||||
heap_.stats_collector(), StatsCollector::kSweepInvokePreFinalizers);
|
||||
|
||||
DCHECK(CurrentThreadIsCreationThread());
|
||||
LivenessBroker liveness_broker = LivenessBrokerFactory::Create();
|
||||
is_invoking_ = true;
|
||||
DCHECK_EQ(0u, bytes_allocated_in_prefinalizers);
|
||||
// Reset all LABs to force allocations to the slow path for black allocation.
|
||||
heap_.object_allocator().ResetLinearAllocationBuffers();
|
||||
|
||||
void* cage_base = nullptr;
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
cage_base = heap_.caged_heap().base();
|
||||
#endif // defined(CPPGC_CAGED_HEAP)
|
||||
// Prefinalizers can allocate other objects with prefinalizers, which will
|
||||
// modify ordered_pre_finalizers_ and break iterators.
|
||||
std::vector<PreFinalizer> new_ordered_pre_finalizers;
|
||||
@ -68,8 +123,8 @@ void PreFinalizerHandler::InvokePreFinalizers() {
|
||||
ordered_pre_finalizers_.begin(),
|
||||
std::remove_if(ordered_pre_finalizers_.rbegin(),
|
||||
ordered_pre_finalizers_.rend(),
|
||||
[liveness_broker](const PreFinalizer& pf) {
|
||||
return (pf.callback)(liveness_broker, pf.object);
|
||||
[cage_base](const PreFinalizer& pf) {
|
||||
return InvokeUnmarkedPrefinalizers(cage_base, pf);
|
||||
})
|
||||
.base());
|
||||
// Newly added objects with prefinalizers will always survive the current GC
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "include/cppgc/prefinalizer.h"
|
||||
#include "src/base/pointer-with-payload.h"
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
@ -18,7 +19,27 @@ class HeapBase;
|
||||
struct PreFinalizer final {
|
||||
using Callback = PrefinalizerRegistration::Callback;
|
||||
|
||||
void* object;
|
||||
PreFinalizer(void* object, const void* base_object_payload,
|
||||
Callback callback);
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
uint32_t object_offset;
|
||||
uint32_t base_object_payload_offset;
|
||||
|
||||
#else // !defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
enum class PointerType : uint8_t {
|
||||
kAtBase,
|
||||
kInnerPointer,
|
||||
};
|
||||
|
||||
// Contains the pointer and also an indicator of whether the pointer points to
|
||||
// the base of the object or is an inner pointer.
|
||||
v8::base::PointerWithPayload<void, PointerType, 1> object_and_offset;
|
||||
|
||||
#endif // !defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
Callback callback;
|
||||
|
||||
bool operator==(const PreFinalizer& other) const;
|
||||
|
Loading…
Reference in New Issue
Block a user