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:
Michael Lippautz 2022-03-09 15:30:08 +01:00 committed by V8 LUCI CQ
parent 4f3dd3db80
commit cf25b3bc53
5 changed files with 128 additions and 29 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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;