[K/N] Track memory in big chunks ^KT-57773
Additionally removed testFixtures{} and test{} in gcScheduler/common runtime module,
because that source set became empty.
This commit is contained in:
committed by
Space Team
parent
0a726d4ebf
commit
220ecc4788
@@ -370,8 +370,6 @@ bitcode {
|
|||||||
headersDirs.from(files("src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
headersDirs.from(files("src/gc/common/cpp", "src/mm/cpp", "src/main/cpp"))
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {}
|
main {}
|
||||||
testFixtures {}
|
|
||||||
test {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onlyIf { target.supportsThreads() }
|
onlyIf { target.supportsThreads() }
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ uint64_t ArrayAllocatedDataSize(const TypeInfo* typeInfo, uint32_t count) noexce
|
|||||||
return AlignUp<uint64_t>(heapArrayHeaderSize + membersSize, kObjectAlignment);
|
return AlignUp<uint64_t>(heapArrayHeaderSize + membersSize, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomAllocator::CustomAllocator(Heap& heap, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept :
|
CustomAllocator::CustomAllocator(Heap& heap) noexcept : heap_(heap), nextFitPage_(nullptr), extraObjectPage_(nullptr) {
|
||||||
heap_(heap), gcScheduler_(gcScheduler), nextFitPage_(nullptr), extraObjectPage_(nullptr) {
|
|
||||||
CustomAllocInfo("CustomAllocator::CustomAllocator(heap)");
|
CustomAllocInfo("CustomAllocator::CustomAllocator(heap)");
|
||||||
memset(fixedBlockPages_, 0, sizeof(fixedBlockPages_));
|
memset(fixedBlockPages_, 0, sizeof(fixedBlockPages_));
|
||||||
}
|
}
|
||||||
@@ -126,7 +125,6 @@ size_t CustomAllocator::GetAllocatedHeapSize(ObjHeader* object) noexcept {
|
|||||||
|
|
||||||
uint8_t* CustomAllocator::Allocate(uint64_t size) noexcept {
|
uint8_t* CustomAllocator::Allocate(uint64_t size) noexcept {
|
||||||
RuntimeAssert(size, "CustomAllocator::Allocate cannot allocate 0 bytes");
|
RuntimeAssert(size, "CustomAllocator::Allocate cannot allocate 0 bytes");
|
||||||
gcScheduler_.OnSafePointAllocation(size);
|
|
||||||
CustomAllocDebug("CustomAllocator::Allocate(%" PRIu64 ")", size);
|
CustomAllocDebug("CustomAllocator::Allocate(%" PRIu64 ")", size);
|
||||||
uint64_t cellCount = (size + sizeof(Cell) - 1) / sizeof(Cell);
|
uint64_t cellCount = (size + sizeof(Cell) - 1) / sizeof(Cell);
|
||||||
uint8_t* ptr;
|
uint8_t* ptr;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
#include "ExtraObjectData.hpp"
|
#include "ExtraObjectData.hpp"
|
||||||
#include "ExtraObjectPage.hpp"
|
#include "ExtraObjectPage.hpp"
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
#include "Heap.hpp"
|
#include "Heap.hpp"
|
||||||
#include "NextFitPage.hpp"
|
#include "NextFitPage.hpp"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
@@ -21,7 +20,7 @@ namespace kotlin::alloc {
|
|||||||
|
|
||||||
class CustomAllocator {
|
class CustomAllocator {
|
||||||
public:
|
public:
|
||||||
explicit CustomAllocator(Heap& heap, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept;
|
explicit CustomAllocator(Heap& heap) noexcept;
|
||||||
|
|
||||||
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
ObjHeader* CreateObject(const TypeInfo* typeInfo) noexcept;
|
||||||
|
|
||||||
@@ -49,7 +48,6 @@ private:
|
|||||||
uint8_t* AllocateInFixedBlockPage(uint32_t cellCount) noexcept;
|
uint8_t* AllocateInFixedBlockPage(uint32_t cellCount) noexcept;
|
||||||
|
|
||||||
Heap& heap_;
|
Heap& heap_;
|
||||||
gcScheduler::GCSchedulerThreadData& gcScheduler_;
|
|
||||||
NextFitPage* nextFitPage_;
|
NextFitPage* nextFitPage_;
|
||||||
FixedBlockPage* fixedBlockPages_[FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 1];
|
FixedBlockPage* fixedBlockPages_[FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 1];
|
||||||
ExtraObjectPage* extraObjectPage_;
|
ExtraObjectPage* extraObjectPage_;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include "CustomAllocConstants.hpp"
|
#include "CustomAllocConstants.hpp"
|
||||||
#include "CustomAllocator.hpp"
|
#include "CustomAllocator.hpp"
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "Heap.hpp"
|
#include "Heap.hpp"
|
||||||
@@ -28,9 +27,7 @@ TEST(CustomAllocTest, SmallAllocNonNull) {
|
|||||||
fakeTypes[i] = {.typeInfo_ = &fakeTypes[i], .instanceSize_ = 8 * i, .flags_ = 0};
|
fakeTypes[i] = {.typeInfo_ = &fakeTypes[i], .instanceSize_ = 8 * i, .flags_ = 0};
|
||||||
}
|
}
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gcScheduler::GCSchedulerConfig config;
|
CustomAllocator ca(heap);
|
||||||
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
|
||||||
CustomAllocator ca(heap, schedulerData);
|
|
||||||
ObjHeader* obj[N];
|
ObjHeader* obj[N];
|
||||||
for (int i = 1; i < N; ++i) {
|
for (int i = 1; i < N; ++i) {
|
||||||
TypeInfo* type = fakeTypes + i;
|
TypeInfo* type = fakeTypes + i;
|
||||||
@@ -43,9 +40,7 @@ TEST(CustomAllocTest, SmallAllocSameFixedBlockPage) {
|
|||||||
const int N = FIXED_BLOCK_PAGE_CELL_COUNT / FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE;
|
const int N = FIXED_BLOCK_PAGE_CELL_COUNT / FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE;
|
||||||
for (int blocks = MIN_BLOCK_SIZE; blocks < FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE; ++blocks) {
|
for (int blocks = MIN_BLOCK_SIZE; blocks < FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE; ++blocks) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gcScheduler::GCSchedulerConfig config;
|
CustomAllocator ca(heap);
|
||||||
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
|
||||||
CustomAllocator ca(heap, schedulerData);
|
|
||||||
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
||||||
uint8_t* first = reinterpret_cast<uint8_t*>(ca.CreateObject(&fakeType));
|
uint8_t* first = reinterpret_cast<uint8_t*>(ca.CreateObject(&fakeType));
|
||||||
for (int i = 1; i < N; ++i) {
|
for (int i = 1; i < N; ++i) {
|
||||||
@@ -58,9 +53,7 @@ TEST(CustomAllocTest, SmallAllocSameFixedBlockPage) {
|
|||||||
|
|
||||||
TEST(CustomAllocTest, FixedBlockPageThreshold) {
|
TEST(CustomAllocTest, FixedBlockPageThreshold) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gcScheduler::GCSchedulerConfig config;
|
CustomAllocator ca(heap);
|
||||||
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
|
||||||
CustomAllocator ca(heap, schedulerData);
|
|
||||||
const int FROM = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE - 10;
|
const int FROM = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE - 10;
|
||||||
const int TO = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 10;
|
const int TO = FIXED_BLOCK_PAGE_MAX_BLOCK_SIZE + 10;
|
||||||
for (int blocks = FROM; blocks <= TO; ++blocks) {
|
for (int blocks = FROM; blocks <= TO; ++blocks) {
|
||||||
@@ -71,9 +64,7 @@ TEST(CustomAllocTest, FixedBlockPageThreshold) {
|
|||||||
|
|
||||||
TEST(CustomAllocTest, NextFitPageThreshold) {
|
TEST(CustomAllocTest, NextFitPageThreshold) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gcScheduler::GCSchedulerConfig config;
|
CustomAllocator ca(heap);
|
||||||
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
|
||||||
CustomAllocator ca(heap, schedulerData);
|
|
||||||
const int FROM = NEXT_FIT_PAGE_MAX_BLOCK_SIZE - 10;
|
const int FROM = NEXT_FIT_PAGE_MAX_BLOCK_SIZE - 10;
|
||||||
const int TO = NEXT_FIT_PAGE_MAX_BLOCK_SIZE + 10;
|
const int TO = NEXT_FIT_PAGE_MAX_BLOCK_SIZE + 10;
|
||||||
for (int blocks = FROM; blocks <= TO; ++blocks) {
|
for (int blocks = FROM; blocks <= TO; ++blocks) {
|
||||||
@@ -85,11 +76,8 @@ TEST(CustomAllocTest, NextFitPageThreshold) {
|
|||||||
TEST(CustomAllocTest, TwoAllocatorsDifferentPages) {
|
TEST(CustomAllocTest, TwoAllocatorsDifferentPages) {
|
||||||
for (int blocks = MIN_BLOCK_SIZE; blocks < 2000; ++blocks) {
|
for (int blocks = MIN_BLOCK_SIZE; blocks < 2000; ++blocks) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gcScheduler::GCSchedulerConfig config;
|
CustomAllocator ca1(heap);
|
||||||
kotlin::gcScheduler::GCSchedulerThreadData schedulerData1(config, [](auto&) {});
|
CustomAllocator ca2(heap);
|
||||||
kotlin::gcScheduler::GCSchedulerThreadData schedulerData2(config, [](auto&) {});
|
|
||||||
CustomAllocator ca1(heap, schedulerData1);
|
|
||||||
CustomAllocator ca2(heap, schedulerData2);
|
|
||||||
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
TypeInfo fakeType = {.typeInfo_ = &fakeType, .instanceSize_ = 8 * blocks, .flags_ = 0};
|
||||||
uint8_t* obj1 = reinterpret_cast<uint8_t*>(ca1.CreateObject(&fakeType));
|
uint8_t* obj1 = reinterpret_cast<uint8_t*>(ca1.CreateObject(&fakeType));
|
||||||
uint8_t* obj2 = reinterpret_cast<uint8_t*>(ca2.CreateObject(&fakeType));
|
uint8_t* obj2 = reinterpret_cast<uint8_t*>(ca2.CreateObject(&fakeType));
|
||||||
@@ -102,9 +90,7 @@ using Data = typename kotlin::mm::ExtraObjectData;
|
|||||||
|
|
||||||
TEST(CustomAllocTest, AllocExtraObjectNonNullZeroed) {
|
TEST(CustomAllocTest, AllocExtraObjectNonNullZeroed) {
|
||||||
Heap heap;
|
Heap heap;
|
||||||
kotlin::gcScheduler::GCSchedulerConfig config;
|
CustomAllocator ca(heap);
|
||||||
kotlin::gcScheduler::GCSchedulerThreadData schedulerData(config, [](auto&) {});
|
|
||||||
CustomAllocator ca(heap, schedulerData);
|
|
||||||
for (int i = 1; i < 10; ++i) {
|
for (int i = 1; i < 10; ++i) {
|
||||||
uint8_t* obj = reinterpret_cast<uint8_t*>(ca.CreateExtraObject());
|
uint8_t* obj = reinterpret_cast<uint8_t*>(ca.CreateExtraObject());
|
||||||
EXPECT_TRUE(obj);
|
EXPECT_TRUE(obj);
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ void* SafeAlloc(uint64_t size) noexcept {
|
|||||||
konan::consoleErrorf("Out of memory trying to allocate %" PRIu64 "bytes: %s. Aborting.\n", size, strerror(errno));
|
konan::consoleErrorf("Out of memory trying to allocate %" PRIu64 "bytes: %s. Aborting.\n", size, strerror(errno));
|
||||||
konan::abort();
|
konan::abort();
|
||||||
}
|
}
|
||||||
allocatedBytesCounter.fetch_add(static_cast<size_t>(size), std::memory_order_relaxed);
|
auto previousSize = allocatedBytesCounter.fetch_add(static_cast<size_t>(size), std::memory_order_relaxed);
|
||||||
|
OnMemoryAllocation(previousSize + static_cast<size_t>(size));
|
||||||
CustomAllocDebug("SafeAlloc(%zu) = %p", static_cast<size_t>(size), memory);
|
CustomAllocDebug("SafeAlloc(%zu) = %p", static_cast<size_t>(size), memory);
|
||||||
return memory;
|
return memory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,10 +59,6 @@ struct ProcessWeaksTraits {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void gc::ConcurrentMarkAndSweep::ThreadData::SafePointAllocation(size_t size) noexcept {
|
|
||||||
gcScheduler_.OnSafePointAllocation(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::ConcurrentMarkAndSweep::ThreadData::Schedule() noexcept {
|
void gc::ConcurrentMarkAndSweep::ThreadData::Schedule() noexcept {
|
||||||
RuntimeLogInfo({kTagGC}, "Scheduling GC manually");
|
RuntimeLogInfo({kTagGC}, "Scheduling GC manually");
|
||||||
ThreadStateGuard guard(ThreadState::kNative);
|
ThreadStateGuard guard(ThreadState::kNative);
|
||||||
@@ -187,8 +183,6 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
gc::Mark<internal::MarkTraits>(gcHandle, markQueue_);
|
gc::Mark<internal::MarkTraits>(gcHandle, markQueue_);
|
||||||
|
|
||||||
mm::WaitForThreadsSuspension();
|
mm::WaitForThreadsSuspension();
|
||||||
auto markStats = gcHandle.getMarked();
|
|
||||||
scheduler.gcData().UpdateAliveSetBytes(markStats.markedSizeBytes);
|
|
||||||
|
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
// Taking the locks before the pause is completed. So that any destroying thread
|
// Taking the locks before the pause is completed. So that any destroying thread
|
||||||
@@ -228,6 +222,7 @@ void gc::ConcurrentMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
finalizerQueue.TransferAllFrom(thread.gc().impl().alloc().ExtractFinalizerQueue());
|
finalizerQueue.TransferAllFrom(thread.gc().impl().alloc().ExtractFinalizerQueue());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
scheduler.gcData().UpdateAliveSetBytes(allocatedBytes());
|
||||||
state_.finish(epoch);
|
state_.finish(epoch);
|
||||||
gcHandle.finalizersScheduled(finalizerQueue.size());
|
gcHandle.finalizersScheduled(finalizerQueue.size());
|
||||||
gcHandle.finished();
|
gcHandle.finished();
|
||||||
|
|||||||
@@ -78,13 +78,9 @@ public:
|
|||||||
|
|
||||||
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
||||||
|
|
||||||
explicit ThreadData(
|
explicit ThreadData(ConcurrentMarkAndSweep& gc, mm::ThreadData& threadData) noexcept : gc_(gc), threadData_(threadData) {}
|
||||||
ConcurrentMarkAndSweep& gc, mm::ThreadData& threadData, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept :
|
|
||||||
gc_(gc), threadData_(threadData), gcScheduler_(gcScheduler) {}
|
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
void SafePointAllocation(size_t size) noexcept;
|
|
||||||
|
|
||||||
void Schedule() noexcept;
|
void Schedule() noexcept;
|
||||||
void ScheduleAndWaitFullGC() noexcept;
|
void ScheduleAndWaitFullGC() noexcept;
|
||||||
void ScheduleAndWaitFullGCWithFinalizers() noexcept;
|
void ScheduleAndWaitFullGCWithFinalizers() noexcept;
|
||||||
@@ -103,7 +99,6 @@ public:
|
|||||||
friend ConcurrentMarkAndSweep;
|
friend ConcurrentMarkAndSweep;
|
||||||
ConcurrentMarkAndSweep& gc_;
|
ConcurrentMarkAndSweep& gc_;
|
||||||
mm::ThreadData& threadData_;
|
mm::ThreadData& threadData_;
|
||||||
gcScheduler::GCSchedulerThreadData& gcScheduler_;
|
|
||||||
std::atomic<bool> marking_;
|
std::atomic<bool> marking_;
|
||||||
BarriersThreadData barriers_;
|
BarriersThreadData barriers_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,8 +15,7 @@
|
|||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
gc::GC::ThreadData::ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
||||||
impl_(std_support::make_unique<Impl>(gc, gcScheduler, threadData)) {}
|
|
||||||
|
|
||||||
gc::GC::ThreadData::~ThreadData() = default;
|
gc::GC::ThreadData::~ThreadData() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ private:
|
|||||||
|
|
||||||
class GC::ThreadData::Impl : private Pinned {
|
class GC::ThreadData::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
Impl(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
||||||
gc_(gc.impl_->gc(), threadData, gcScheduler),
|
gc_(gc.impl_->gc(), threadData),
|
||||||
#ifndef CUSTOM_ALLOCATOR
|
#ifndef CUSTOM_ALLOCATOR
|
||||||
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()),
|
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()),
|
||||||
extraObjectDataFactoryThreadQueue_(gc.impl_->extraObjectDataFactory()) {
|
extraObjectDataFactoryThreadQueue_(gc.impl_->extraObjectDataFactory()) {
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
alloc_(gc.impl_->gc().heap(), gcScheduler) {
|
alloc_(gc.impl_->gc().heap()) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public:
|
|||||||
AllocatorWithGC(BaseAllocator base, GCThreadData& gc) noexcept : base_(std::move(base)), gc_(gc) {}
|
AllocatorWithGC(BaseAllocator base, GCThreadData& gc) noexcept : base_(std::move(base)), gc_(gc) {}
|
||||||
|
|
||||||
void* Alloc(size_t size) noexcept {
|
void* Alloc(size_t size) noexcept {
|
||||||
gc_.SafePointAllocation(size);
|
|
||||||
if (void* ptr = base_.Alloc(size)) {
|
if (void* ptr = base_.Alloc(size)) {
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ private:
|
|||||||
|
|
||||||
class MockGC {
|
class MockGC {
|
||||||
public:
|
public:
|
||||||
MOCK_METHOD(void, SafePointAllocation, (size_t));
|
|
||||||
MOCK_METHOD(void, OnOOM, (size_t));
|
MOCK_METHOD(void, OnOOM, (size_t));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,7 +45,6 @@ TEST(AllocatorWithGCTest, AllocateWithoutOOM) {
|
|||||||
testing::StrictMock<MockGC> gc;
|
testing::StrictMock<MockGC> gc;
|
||||||
{
|
{
|
||||||
testing::InSequence seq;
|
testing::InSequence seq;
|
||||||
EXPECT_CALL(gc, SafePointAllocation(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nonNull));
|
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nonNull));
|
||||||
EXPECT_CALL(gc, OnOOM(_)).Times(0);
|
EXPECT_CALL(gc, OnOOM(_)).Times(0);
|
||||||
}
|
}
|
||||||
@@ -62,7 +60,6 @@ TEST(AllocatorWithGCTest, AllocateWithFixableOOM) {
|
|||||||
testing::StrictMock<MockGC> gc;
|
testing::StrictMock<MockGC> gc;
|
||||||
{
|
{
|
||||||
testing::InSequence seq;
|
testing::InSequence seq;
|
||||||
EXPECT_CALL(gc, SafePointAllocation(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nullptr));
|
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nullptr));
|
||||||
EXPECT_CALL(gc, OnOOM(size));
|
EXPECT_CALL(gc, OnOOM(size));
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nonNull));
|
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nonNull));
|
||||||
@@ -78,7 +75,6 @@ TEST(AllocatorWithGCTest, AllocateWithUnfixableOOM) {
|
|||||||
testing::StrictMock<MockGC> gc;
|
testing::StrictMock<MockGC> gc;
|
||||||
{
|
{
|
||||||
testing::InSequence seq;
|
testing::InSequence seq;
|
||||||
EXPECT_CALL(gc, SafePointAllocation(size));
|
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nullptr));
|
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nullptr));
|
||||||
EXPECT_CALL(gc, OnOOM(size));
|
EXPECT_CALL(gc, OnOOM(size));
|
||||||
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nullptr));
|
EXPECT_CALL(*baseAllocator, Alloc(size)).WillOnce(testing::Return(nullptr));
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
class Impl;
|
class Impl;
|
||||||
|
|
||||||
ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept;
|
ThreadData(GC& gc, mm::ThreadData& threadData) noexcept;
|
||||||
~ThreadData();
|
~ThreadData();
|
||||||
|
|
||||||
Impl& impl() noexcept { return *impl_; }
|
Impl& impl() noexcept { return *impl_; }
|
||||||
|
|||||||
@@ -309,7 +309,6 @@ void GCHandle::marked(kotlin::gc::MarkStats stats) {
|
|||||||
if (!stat->markStats) {
|
if (!stat->markStats) {
|
||||||
stat->markStats = MarkStats{};
|
stat->markStats = MarkStats{};
|
||||||
}
|
}
|
||||||
stat->markStats->markedSizeBytes += stats.markedSizeBytes;
|
|
||||||
stat->markStats->markedCount += stats.markedCount;
|
stat->markStats->markedCount += stats.markedCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ struct SweepStats {
|
|||||||
|
|
||||||
struct MarkStats {
|
struct MarkStats {
|
||||||
uint64_t markedCount = 0;
|
uint64_t markedCount = 0;
|
||||||
uint64_t markedSizeBytes = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GCHandle {
|
class GCHandle {
|
||||||
@@ -103,10 +102,7 @@ public:
|
|||||||
explicit GCMarkScope(GCHandle& handle);
|
explicit GCMarkScope(GCHandle& handle);
|
||||||
~GCMarkScope();
|
~GCMarkScope();
|
||||||
|
|
||||||
void addObject(uint64_t objectSize) noexcept {
|
void addObject() noexcept { ++stats_.markedCount; }
|
||||||
++stats_.markedCount;
|
|
||||||
stats_.markedSizeBytes += objectSize;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GCProcessWeaksScope : GCStageScopeUsTimer, Pinned {
|
class GCProcessWeaksScope : GCStageScopeUsTimer, Pinned {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ void processExtraObjectData(GCHandle::GCMarkScope& markHandle, typename Traits::
|
|||||||
// Do not schedule RegularWeakReferenceImpl but process it right away.
|
// Do not schedule RegularWeakReferenceImpl but process it right away.
|
||||||
// This will skip markQueue interaction.
|
// This will skip markQueue interaction.
|
||||||
if (Traits::tryMark(weakReference)) {
|
if (Traits::tryMark(weakReference)) {
|
||||||
markHandle.addObject(mm::GetAllocatedHeapSize(weakReference));
|
markHandle.addObject();
|
||||||
// RegularWeakReferenceImpl is empty, but keeping this just in case.
|
// RegularWeakReferenceImpl is empty, but keeping this just in case.
|
||||||
Traits::processInMark(markQueue, weakReference);
|
Traits::processInMark(markQueue, weakReference);
|
||||||
}
|
}
|
||||||
@@ -94,9 +94,7 @@ template <typename Traits>
|
|||||||
void Mark(GCHandle handle, typename Traits::MarkQueue& markQueue) noexcept {
|
void Mark(GCHandle handle, typename Traits::MarkQueue& markQueue) noexcept {
|
||||||
auto markHandle = handle.mark();
|
auto markHandle = handle.mark();
|
||||||
while (ObjHeader* top = Traits::tryDequeue(markQueue)) {
|
while (ObjHeader* top = Traits::tryDequeue(markQueue)) {
|
||||||
// TODO: Consider moving it to the sweep phase to make this loop more tight.
|
markHandle.addObject();
|
||||||
// This, however, requires care with scheduler interoperation.
|
|
||||||
markHandle.addObject(mm::GetAllocatedHeapSize(top));
|
|
||||||
|
|
||||||
Traits::processInMark(markQueue, top);
|
Traits::processInMark(markQueue, top);
|
||||||
|
|
||||||
@@ -169,7 +167,6 @@ typename Traits::ObjectFactory::FinalizerQueue Sweep(GCHandle handle, typename T
|
|||||||
template <typename Traits>
|
template <typename Traits>
|
||||||
void collectRootSetForThread(GCHandle gcHandle, typename Traits::MarkQueue& markQueue, mm::ThreadData& thread) {
|
void collectRootSetForThread(GCHandle gcHandle, typename Traits::MarkQueue& markQueue, mm::ThreadData& thread) {
|
||||||
auto handle = gcHandle.collectThreadRoots(thread);
|
auto handle = gcHandle.collectThreadRoots(thread);
|
||||||
thread.gcScheduler().OnStoppedForGC();
|
|
||||||
// TODO: Remove useless mm::ThreadRootSet abstraction.
|
// TODO: Remove useless mm::ThreadRootSet abstraction.
|
||||||
for (auto value : mm::ThreadRootSet(thread)) {
|
for (auto value : mm::ThreadRootSet(thread)) {
|
||||||
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
if (internal::collectRoot<Traits>(markQueue, value.object)) {
|
||||||
|
|||||||
@@ -157,19 +157,10 @@ private:
|
|||||||
ScopedMarkTraits markTraits_;
|
ScopedMarkTraits markTraits_;
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t GetObjectsSize(std::initializer_list<std::reference_wrapper<test_support::Any>> objects) {
|
|
||||||
size_t size = 0;
|
|
||||||
for (auto& object : objects) {
|
|
||||||
size += mm::GetAllocatedHeapSize(object.get().header());
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define EXPECT_MARKED(stats, ...) \
|
#define EXPECT_MARKED(stats, ...) \
|
||||||
do { \
|
do { \
|
||||||
std::initializer_list<std::reference_wrapper<test_support::Any>> objects = {__VA_ARGS__}; \
|
std::initializer_list<std::reference_wrapper<test_support::Any>> objects = {__VA_ARGS__}; \
|
||||||
EXPECT_THAT(stats.markedCount, objects.size()); \
|
EXPECT_THAT(stats.markedCount, objects.size()); \
|
||||||
EXPECT_THAT(stats.markedSizeBytes, GetObjectsSize(objects)); \
|
|
||||||
EXPECT_THAT(marked(), MarkedMatcher(objects)); \
|
EXPECT_THAT(marked(), MarkedMatcher(objects)); \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,7 @@
|
|||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
gc::GC::ThreadData::ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
|
||||||
impl_(std_support::make_unique<Impl>(gc, gcScheduler, threadData)) {}
|
|
||||||
#else
|
|
||||||
impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
gc::GC::ThreadData::~ThreadData() = default;
|
gc::GC::ThreadData::~ThreadData() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ private:
|
|||||||
class GC::ThreadData::Impl : private Pinned {
|
class GC::ThreadData::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
#ifdef CUSTOM_ALLOCATOR
|
||||||
Impl(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
Impl(GC& gc, mm::ThreadData& threadData) noexcept : alloc_(gc.impl_->gc().heap()) {}
|
||||||
alloc_(gc.impl_->gc().heap(), gcScheduler) {}
|
|
||||||
#else
|
#else
|
||||||
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
||||||
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()),
|
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()),
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ public:
|
|||||||
ThreadData() noexcept {}
|
ThreadData() noexcept {}
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
void SafePointAllocation(size_t size) noexcept {}
|
|
||||||
|
|
||||||
void Schedule() noexcept {}
|
void Schedule() noexcept {}
|
||||||
void ScheduleAndWaitFullGC() noexcept {}
|
void ScheduleAndWaitFullGC() noexcept {}
|
||||||
void ScheduleAndWaitFullGCWithFinalizers() noexcept {}
|
void ScheduleAndWaitFullGCWithFinalizers() noexcept {}
|
||||||
|
|||||||
@@ -15,8 +15,7 @@
|
|||||||
|
|
||||||
using namespace kotlin;
|
using namespace kotlin;
|
||||||
|
|
||||||
gc::GC::ThreadData::ThreadData(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
gc::GC::ThreadData::ThreadData(GC& gc, mm::ThreadData& threadData) noexcept : impl_(std_support::make_unique<Impl>(gc, threadData)) {}
|
||||||
impl_(std_support::make_unique<Impl>(gc, gcScheduler, threadData)) {}
|
|
||||||
|
|
||||||
gc::GC::ThreadData::~ThreadData() = default;
|
gc::GC::ThreadData::~ThreadData() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,11 @@ private:
|
|||||||
|
|
||||||
class GC::ThreadData::Impl : private Pinned {
|
class GC::ThreadData::Impl : private Pinned {
|
||||||
public:
|
public:
|
||||||
Impl(GC& gc, gcScheduler::GCSchedulerThreadData& gcScheduler, mm::ThreadData& threadData) noexcept :
|
Impl(GC& gc, mm::ThreadData& threadData) noexcept :
|
||||||
gc_(gc.impl_->gc(), threadData, gcScheduler),
|
gc_(gc.impl_->gc(), threadData),
|
||||||
#ifdef CUSTOM_ALLOCATOR
|
#ifdef CUSTOM_ALLOCATOR
|
||||||
alloc_(gc.impl_->gc().heap(), gcScheduler) {}
|
alloc_(gc.impl_->gc().heap()) {
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()),
|
objectFactoryThreadQueue_(gc.impl_->objectFactory(), gc_.CreateAllocator()),
|
||||||
extraObjectDataFactoryThreadQueue_(gc.impl_->extraObjectDataFactory()) {
|
extraObjectDataFactoryThreadQueue_(gc.impl_->extraObjectDataFactory()) {
|
||||||
|
|||||||
@@ -58,10 +58,6 @@ struct ProcessWeaksTraits {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void gc::SameThreadMarkAndSweep::ThreadData::SafePointAllocation(size_t size) noexcept {
|
|
||||||
gcScheduler_.OnSafePointAllocation(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void gc::SameThreadMarkAndSweep::ThreadData::Schedule() noexcept {
|
void gc::SameThreadMarkAndSweep::ThreadData::Schedule() noexcept {
|
||||||
RuntimeLogInfo({kTagGC}, "Scheduling GC manually");
|
RuntimeLogInfo({kTagGC}, "Scheduling GC manually");
|
||||||
ThreadStateGuard guard(ThreadState::kNative);
|
ThreadStateGuard guard(ThreadState::kNative);
|
||||||
@@ -161,8 +157,6 @@ void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
gc::collectRootSet<internal::MarkTraits>(gcHandle, markQueue_, [](mm::ThreadData&) { return true; });
|
gc::collectRootSet<internal::MarkTraits>(gcHandle, markQueue_, [](mm::ThreadData&) { return true; });
|
||||||
|
|
||||||
gc::Mark<internal::MarkTraits>(gcHandle, markQueue_);
|
gc::Mark<internal::MarkTraits>(gcHandle, markQueue_);
|
||||||
auto markStats = gcHandle.getMarked();
|
|
||||||
scheduler.gcData().UpdateAliveSetBytes(markStats.markedSizeBytes);
|
|
||||||
|
|
||||||
gc::processWeaks<ProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
|
gc::processWeaks<ProcessWeaksTraits>(gcHandle, mm::SpecialRefRegistry::instance());
|
||||||
|
|
||||||
@@ -181,6 +175,8 @@ void gc::SameThreadMarkAndSweep::PerformFullGC(int64_t epoch) noexcept {
|
|||||||
auto finalizerQueue = heap_.Sweep(gcHandle);
|
auto finalizerQueue = heap_.Sweep(gcHandle);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
scheduler.gcData().UpdateAliveSetBytes(allocatedBytes());
|
||||||
|
|
||||||
mm::ResumeThreads();
|
mm::ResumeThreads();
|
||||||
gcHandle.threadsAreResumed();
|
gcHandle.threadsAreResumed();
|
||||||
state_.finish(epoch);
|
state_.finish(epoch);
|
||||||
|
|||||||
@@ -79,12 +79,9 @@ public:
|
|||||||
using ObjectData = SameThreadMarkAndSweep::ObjectData;
|
using ObjectData = SameThreadMarkAndSweep::ObjectData;
|
||||||
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
using Allocator = AllocatorWithGC<Allocator, ThreadData>;
|
||||||
|
|
||||||
ThreadData(SameThreadMarkAndSweep& gc, mm::ThreadData& threadData, gcScheduler::GCSchedulerThreadData& gcScheduler) noexcept :
|
ThreadData(SameThreadMarkAndSweep& gc, mm::ThreadData& threadData) noexcept : gc_(gc) {}
|
||||||
gc_(gc), gcScheduler_(gcScheduler) {}
|
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
|
|
||||||
void SafePointAllocation(size_t size) noexcept;
|
|
||||||
|
|
||||||
void Schedule() noexcept;
|
void Schedule() noexcept;
|
||||||
void ScheduleAndWaitFullGC() noexcept;
|
void ScheduleAndWaitFullGC() noexcept;
|
||||||
void ScheduleAndWaitFullGCWithFinalizers() noexcept;
|
void ScheduleAndWaitFullGCWithFinalizers() noexcept;
|
||||||
@@ -96,7 +93,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
SameThreadMarkAndSweep& gc_;
|
SameThreadMarkAndSweep& gc_;
|
||||||
gcScheduler::GCSchedulerThreadData& gcScheduler_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using Allocator = ThreadData::Allocator;
|
using Allocator = ThreadData::Allocator;
|
||||||
|
|||||||
@@ -38,14 +38,6 @@ public:
|
|||||||
RuntimeLogInfo({kTagGC}, "Adaptive GC scheduler initialized");
|
RuntimeLogInfo({kTagGC}, "Adaptive GC scheduler initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateFromThreadData(GCSchedulerThreadData& threadData) noexcept override {
|
|
||||||
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
|
|
||||||
if (heapGrowthController_.NeedsGC()) {
|
|
||||||
RuntimeLogDebug({kTagGC}, "Scheduling GC by allocation");
|
|
||||||
scheduleGC_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnPerformFullGC() noexcept override {
|
void OnPerformFullGC() noexcept override {
|
||||||
heapGrowthController_.OnPerformFullGC();
|
heapGrowthController_.OnPerformFullGC();
|
||||||
regularIntervalPacer_.OnPerformFullGC();
|
regularIntervalPacer_.OnPerformFullGC();
|
||||||
@@ -54,6 +46,13 @@ public:
|
|||||||
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
||||||
|
|
||||||
|
void SetAllocatedBytes(size_t bytes) noexcept override {
|
||||||
|
if (heapGrowthController_.SetAllocatedBytes(bytes)) {
|
||||||
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by allocation");
|
||||||
|
scheduleGC_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCSchedulerConfig& config_;
|
GCSchedulerConfig& config_;
|
||||||
std::function<void()> scheduleGC_;
|
std::function<void()> scheduleGC_;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
#include "AppStateTrackingTestSupport.hpp"
|
#include "AppStateTrackingTestSupport.hpp"
|
||||||
#include "ClockTestSupport.hpp"
|
#include "ClockTestSupport.hpp"
|
||||||
#include "GCSchedulerTestSupport.hpp"
|
|
||||||
#include "SingleThreadExecutor.hpp"
|
#include "SingleThreadExecutor.hpp"
|
||||||
#include "TestSupport.hpp"
|
#include "TestSupport.hpp"
|
||||||
#include "std_support/Vector.hpp"
|
#include "std_support/Vector.hpp"
|
||||||
@@ -21,25 +20,18 @@ namespace {
|
|||||||
|
|
||||||
class MutatorThread : private Pinned {
|
class MutatorThread : private Pinned {
|
||||||
public:
|
public:
|
||||||
MutatorThread(gcScheduler::GCSchedulerConfig& config, std::function<void(gcScheduler::GCSchedulerThreadData&)> slowPath) :
|
explicit MutatorThread(gcScheduler::GCSchedulerData& scheduler) : executor_([&scheduler] { return Context{scheduler}; }) {}
|
||||||
executor_([&config, slowPath = std::move(slowPath)] { return Context(config, std::move(slowPath)); }) {}
|
|
||||||
|
|
||||||
std::future<void> Allocate(size_t bytes) {
|
std::future<void> SetAllocatedBytes(size_t bytes) {
|
||||||
return executor_.execute([&, bytes] {
|
return executor_.execute([&, bytes] {
|
||||||
auto& context = executor_.context();
|
auto& context = executor_.context();
|
||||||
context.threadDataTestApi.SetAllocatedBytes(bytes);
|
context.scheduler.SetAllocatedBytes(bytes);
|
||||||
context.slowPath(context.threadData);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Context {
|
struct Context {
|
||||||
gcScheduler::GCSchedulerThreadData threadData;
|
gcScheduler::GCSchedulerData& scheduler;
|
||||||
gcScheduler::test_support::GCSchedulerThreadDataTestApi threadDataTestApi;
|
|
||||||
std::function<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
|
||||||
|
|
||||||
Context(gcScheduler::GCSchedulerConfig& config, std::function<void(gcScheduler::GCSchedulerThreadData&)> slowPath) :
|
|
||||||
threadData(config, [](gcScheduler::GCSchedulerThreadData&) {}), threadDataTestApi(threadData), slowPath(slowPath) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SingleThreadExecutor<Context> executor_;
|
SingleThreadExecutor<Context> executor_;
|
||||||
@@ -51,16 +43,22 @@ public:
|
|||||||
explicit GCSchedulerDataTestApi(gcScheduler::GCSchedulerConfig& config) : scheduler_(config, scheduleGC_.AsStdFunction()) {
|
explicit GCSchedulerDataTestApi(gcScheduler::GCSchedulerConfig& config) : scheduler_(config, scheduleGC_.AsStdFunction()) {
|
||||||
mutators_.reserve(MutatorCount);
|
mutators_.reserve(MutatorCount);
|
||||||
for (int i = 0; i < MutatorCount; ++i) {
|
for (int i = 0; i < MutatorCount; ++i) {
|
||||||
mutators_.emplace_back(std_support::make_unique<MutatorThread>(
|
mutators_.emplace_back(std_support::make_unique<MutatorThread>(scheduler_));
|
||||||
config, [this](gcScheduler::GCSchedulerThreadData& threadData) { scheduler_.UpdateFromThreadData(threadData); }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::future<void> Allocate(int mutator, size_t bytes) { return mutators_[mutator]->Allocate(bytes); }
|
std::future<void> Allocate(int mutator, size_t bytes) {
|
||||||
|
size_t allocatedBytes = allocatedBytes_.fetch_add(bytes);
|
||||||
|
allocatedBytes += bytes;
|
||||||
|
return mutators_[mutator]->SetAllocatedBytes(allocatedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
void OnPerformFullGC() { scheduler_.OnPerformFullGC(); }
|
void OnPerformFullGC() { scheduler_.OnPerformFullGC(); }
|
||||||
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) { scheduler_.UpdateAliveSetBytes(bytes); }
|
void UpdateAliveSetBytes(size_t bytes) {
|
||||||
|
allocatedBytes_.store(bytes);
|
||||||
|
scheduler_.UpdateAliveSetBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
testing::MockFunction<void()>& scheduleGC() { return scheduleGC_; }
|
testing::MockFunction<void()>& scheduleGC() { return scheduleGC_; }
|
||||||
|
|
||||||
@@ -70,6 +68,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::atomic<size_t> allocatedBytes_ = 0;
|
||||||
std_support::vector<std_support::unique_ptr<MutatorThread>> mutators_;
|
std_support::vector<std_support::unique_ptr<MutatorThread>> mutators_;
|
||||||
testing::MockFunction<void()> scheduleGC_;
|
testing::MockFunction<void()> scheduleGC_;
|
||||||
gcScheduler::internal::GCSchedulerDataAdaptive<test_support::manual_clock> scheduler_;
|
gcScheduler::internal::GCSchedulerDataAdaptive<test_support::manual_clock> scheduler_;
|
||||||
|
|||||||
@@ -22,16 +22,15 @@ class GCSchedulerDataAggressive : public GCSchedulerData {
|
|||||||
public:
|
public:
|
||||||
GCSchedulerDataAggressive(GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
|
GCSchedulerDataAggressive(GCSchedulerConfig& config, std::function<void()> scheduleGC) noexcept :
|
||||||
scheduleGC_(std::move(scheduleGC)), heapGrowthController_(config) {
|
scheduleGC_(std::move(scheduleGC)), heapGrowthController_(config) {
|
||||||
// Trigger the slowpath on each allocation.
|
|
||||||
config.allocationThresholdBytes = 1;
|
|
||||||
RuntimeLogInfo({kTagGC}, "Aggressive GC scheduler initialized");
|
RuntimeLogInfo({kTagGC}, "Aggressive GC scheduler initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateFromThreadData(GCSchedulerThreadData& threadData) noexcept override {
|
void OnPerformFullGC() noexcept override { heapGrowthController_.OnPerformFullGC(); }
|
||||||
heapGrowthController_.OnAllocated(threadData.allocatedBytes());
|
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
||||||
if (heapGrowthController_.NeedsGC()) {
|
void SetAllocatedBytes(size_t bytes) noexcept override {
|
||||||
// Still checking allocations: with a long running loop all safepoints
|
// Still checking allocations: with a long running loop all safepoints
|
||||||
// might be "met", so that's the only trigger to not run out of memory.
|
// might be "met", so that's the only trigger to not run out of memory.
|
||||||
|
if (heapGrowthController_.SetAllocatedBytes(bytes)) {
|
||||||
RuntimeLogDebug({kTagGC}, "Scheduling GC by allocation");
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by allocation");
|
||||||
scheduleGC_();
|
scheduleGC_();
|
||||||
} else {
|
} else {
|
||||||
@@ -39,9 +38,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPerformFullGC() noexcept override { heapGrowthController_.OnPerformFullGC(); }
|
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); }
|
|
||||||
|
|
||||||
void safePoint() noexcept {
|
void safePoint() noexcept {
|
||||||
if (safePointTracker_.registerCurrentSafePoint(1)) {
|
if (safePointTracker_.registerCurrentSafePoint(1)) {
|
||||||
RuntimeLogDebug({kTagGC}, "Scheduling GC by safepoint");
|
RuntimeLogDebug({kTagGC}, "Scheduling GC by safepoint");
|
||||||
|
|||||||
@@ -50,14 +50,9 @@ TEST(AggressiveSchedulerTest, TriggerGCOnAllocationThreshold) {
|
|||||||
testing::MockFunction<void()> scheduleGC;
|
testing::MockFunction<void()> scheduleGC;
|
||||||
|
|
||||||
gcScheduler::GCSchedulerConfig config;
|
gcScheduler::GCSchedulerConfig config;
|
||||||
gcScheduler::internal::GCSchedulerDataAggressive scheduler(config, scheduleGC.AsStdFunction());
|
|
||||||
gcScheduler::GCSchedulerThreadData threadSchedulerData(
|
|
||||||
config, [&scheduler](gcScheduler::GCSchedulerThreadData& data) { scheduler.UpdateFromThreadData(data); });
|
|
||||||
|
|
||||||
ASSERT_EQ(config.allocationThresholdBytes, 1);
|
|
||||||
|
|
||||||
config.autoTune = false;
|
config.autoTune = false;
|
||||||
config.targetHeapBytes = 10;
|
config.targetHeapBytes = 10;
|
||||||
|
gcScheduler::internal::GCSchedulerDataAggressive scheduler(config, scheduleGC.AsStdFunction());
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
// We trigger GC on the first iteration, when the unique allocation point is faced,
|
// We trigger GC on the first iteration, when the unique allocation point is faced,
|
||||||
@@ -65,7 +60,7 @@ TEST(AggressiveSchedulerTest, TriggerGCOnAllocationThreshold) {
|
|||||||
EXPECT_CALL(scheduleGC, Call()).WillOnce([&i]() { EXPECT_THAT(i, 0); }).WillOnce([&i]() { EXPECT_THAT(i, 9); });
|
EXPECT_CALL(scheduleGC, Call()).WillOnce([&i]() { EXPECT_THAT(i, 0); }).WillOnce([&i]() { EXPECT_THAT(i, 9); });
|
||||||
|
|
||||||
for (; i < 10; i++) {
|
for (; i < 10; i++) {
|
||||||
threadSchedulerData.OnSafePointAllocation(1);
|
scheduler.SetAllocatedBytes(i + 1);
|
||||||
}
|
}
|
||||||
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
testing::Mock::VerifyAndClearExpectations(&scheduleGC);
|
||||||
}();
|
}();
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ class GCSchedulerData {
|
|||||||
public:
|
public:
|
||||||
virtual ~GCSchedulerData() = default;
|
virtual ~GCSchedulerData() = default;
|
||||||
|
|
||||||
// Called by different mutator threads.
|
|
||||||
virtual void UpdateFromThreadData(GCSchedulerThreadData& threadData) noexcept = 0;
|
|
||||||
|
|
||||||
// The protocol is: after the scheduler schedules the GC, the GC eventually calls `OnPerformFullGC`
|
// The protocol is: after the scheduler schedules the GC, the GC eventually calls `OnPerformFullGC`
|
||||||
// when the collection has started, followed by `UpdateAliveSetBytes` when the marking has finished.
|
// when the collection has started, followed by `UpdateAliveSetBytes` when the marking has finished.
|
||||||
// TODO: Consider returning a sort of future from the scheduleGC, and listen to it instead.
|
// TODO: Consider returning a sort of future from the scheduleGC, and listen to it instead.
|
||||||
@@ -38,48 +35,9 @@ public:
|
|||||||
|
|
||||||
// Always called by the GC thread.
|
// Always called by the GC thread.
|
||||||
virtual void UpdateAliveSetBytes(size_t bytes) noexcept = 0;
|
virtual void UpdateAliveSetBytes(size_t bytes) noexcept = 0;
|
||||||
};
|
|
||||||
|
|
||||||
class GCSchedulerThreadData {
|
// Called by different mutator threads.
|
||||||
public:
|
virtual void SetAllocatedBytes(size_t bytes) noexcept = 0;
|
||||||
explicit GCSchedulerThreadData(GCSchedulerConfig& config, std::function<void(GCSchedulerThreadData&)> slowPath) noexcept :
|
|
||||||
config_(config), slowPath_(std::move(slowPath)) {
|
|
||||||
ClearCountersAndUpdateThresholds();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be called on encountering a safepoint placed by the allocator.
|
|
||||||
// TODO: Should this even be a safepoint (i.e. a place, where we suspend)?
|
|
||||||
void OnSafePointAllocation(size_t size) noexcept {
|
|
||||||
allocatedBytes_ += size;
|
|
||||||
if (allocatedBytes_ < allocatedBytesThreshold_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
OnSafePointSlowPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnStoppedForGC() noexcept { ClearCountersAndUpdateThresholds(); }
|
|
||||||
|
|
||||||
size_t allocatedBytes() const noexcept { return allocatedBytes_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class test_support::GCSchedulerThreadDataTestApi;
|
|
||||||
|
|
||||||
void OnSafePointSlowPath() noexcept {
|
|
||||||
slowPath_(*this);
|
|
||||||
ClearCountersAndUpdateThresholds();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClearCountersAndUpdateThresholds() noexcept {
|
|
||||||
allocatedBytes_ = 0;
|
|
||||||
|
|
||||||
allocatedBytesThreshold_ = config_.allocationThresholdBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
GCSchedulerConfig& config_;
|
|
||||||
std::function<void(GCSchedulerThreadData&)> slowPath_;
|
|
||||||
|
|
||||||
size_t allocatedBytes_ = 0;
|
|
||||||
size_t allocatedBytesThreshold_ = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GCScheduler : private Pinned {
|
class GCScheduler : private Pinned {
|
||||||
@@ -89,10 +47,6 @@ public:
|
|||||||
GCSchedulerConfig& config() noexcept { return config_; }
|
GCSchedulerConfig& config() noexcept { return config_; }
|
||||||
GCSchedulerData& gcData() noexcept { return *gcData_; }
|
GCSchedulerData& gcData() noexcept { return *gcData_; }
|
||||||
|
|
||||||
GCSchedulerThreadData NewThreadData() noexcept {
|
|
||||||
return GCSchedulerThreadData(config_, [this](auto& threadData) { gcData_->UpdateFromThreadData(threadData); });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be called on encountering a safepoint.
|
// Should be called on encountering a safepoint.
|
||||||
void safePoint() noexcept;
|
void safePoint() noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ namespace kotlin::gcScheduler {
|
|||||||
|
|
||||||
// NOTE: When changing default values, reflect them in GC.kt as well.
|
// NOTE: When changing default values, reflect them in GC.kt as well.
|
||||||
struct GCSchedulerConfig {
|
struct GCSchedulerConfig {
|
||||||
// How many object bytes a thread must allocate to trigger slow path.
|
|
||||||
std::atomic<int64_t> allocationThresholdBytes = 10 * 1024;
|
|
||||||
std::atomic<bool> autoTune = true;
|
std::atomic<bool> autoTune = true;
|
||||||
// The target interval between collections when Kotlin code is idle. GC will be triggered
|
// The target interval between collections when Kotlin code is idle. GC will be triggered
|
||||||
// by timer no sooner than this value and no later than twice this value since the previous collection.
|
// by timer no sooner than this value and no later than twice this value since the previous collection.
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
|
|
||||||
#include "GCSchedulerTestSupport.hpp"
|
|
||||||
|
|
||||||
using namespace kotlin;
|
|
||||||
|
|
||||||
using testing::_;
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, AllocationSafePoint) {
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
|
||||||
gcScheduler::GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold);
|
|
||||||
});
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, ResetByGC) {
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
|
||||||
gcScheduler::GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
scheduler.OnStoppedForGC();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterResetByGC) {
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
|
||||||
gcScheduler::GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
scheduler.OnStoppedForGC();
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterAllocationSafePoint) {
|
|
||||||
constexpr size_t kSize = 2;
|
|
||||||
constexpr size_t kCount = 10;
|
|
||||||
constexpr size_t kAllocationThreshold = kCount * kSize;
|
|
||||||
testing::MockFunction<void(gcScheduler::GCSchedulerThreadData&)> slowPath;
|
|
||||||
gcScheduler::GCSchedulerConfig config;
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold;
|
|
||||||
gcScheduler::GCSchedulerThreadData scheduler(config, slowPath.AsStdFunction());
|
|
||||||
gcScheduler::test_support::GCSchedulerThreadDataTestApi schedulerTestApi(scheduler);
|
|
||||||
|
|
||||||
config.allocationThresholdBytes = kAllocationThreshold - kSize;
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(_)).Times(0);
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold);
|
|
||||||
});
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
|
|
||||||
EXPECT_CALL(slowPath, Call(testing::Ref(scheduler))).WillOnce([&](gcScheduler::GCSchedulerThreadData& scheduler) {
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), kAllocationThreshold - kSize);
|
|
||||||
});
|
|
||||||
for (size_t i = 0; i < kCount - 1; ++i) {
|
|
||||||
scheduler.OnSafePointAllocation(kSize);
|
|
||||||
}
|
|
||||||
testing::Mock::VerifyAndClearExpectations(&slowPath);
|
|
||||||
EXPECT_THAT(scheduler.allocatedBytes(), 0);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "GCSchedulerTestSupport.hpp"
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
|
|
||||||
namespace kotlin::gcScheduler::test_support {
|
|
||||||
|
|
||||||
class GCSchedulerThreadDataTestApi : private Pinned {
|
|
||||||
public:
|
|
||||||
explicit GCSchedulerThreadDataTestApi(GCSchedulerThreadData& scheduler) : scheduler_(scheduler) {}
|
|
||||||
|
|
||||||
void SetAllocatedBytes(size_t bytes) { scheduler_.allocatedBytes_ = bytes; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
GCSchedulerThreadData& scheduler_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace kotlin::gcScheduler::test_support
|
|
||||||
@@ -17,44 +17,39 @@ namespace kotlin::gcScheduler::internal {
|
|||||||
|
|
||||||
class HeapGrowthController {
|
class HeapGrowthController {
|
||||||
public:
|
public:
|
||||||
explicit HeapGrowthController(GCSchedulerConfig& config) noexcept : config_(config) {}
|
explicit HeapGrowthController(GCSchedulerConfig& config) noexcept :
|
||||||
|
config_(config), targetHeapBytes_(config.targetHeapBytes.load(std::memory_order_relaxed)) {}
|
||||||
|
|
||||||
// Called by the mutators.
|
// Called by the mutators.
|
||||||
void OnAllocated(size_t allocatedBytes) noexcept { allocatedBytes_ += allocatedBytes; }
|
// Returns true if needs GC.
|
||||||
|
bool SetAllocatedBytes(size_t totalAllocatedBytes) noexcept { return totalAllocatedBytes >= targetHeapBytes_; }
|
||||||
// Called by the GC thread.
|
|
||||||
void OnPerformFullGC() noexcept { allocatedBytes_ = 0; }
|
|
||||||
|
|
||||||
// Called by the GC thread.
|
// Called by the GC thread.
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept {
|
void UpdateAliveSetBytes(size_t bytes) noexcept {
|
||||||
lastAliveSetBytes_ = bytes;
|
|
||||||
|
|
||||||
if (config_.autoTune.load()) {
|
if (config_.autoTune.load()) {
|
||||||
double targetHeapBytes = static_cast<double>(bytes) / config_.targetHeapUtilization;
|
double targetHeapBytes = static_cast<double>(bytes) / config_.targetHeapUtilization;
|
||||||
if (!std::isfinite(targetHeapBytes)) {
|
if (!std::isfinite(targetHeapBytes)) {
|
||||||
// This shouldn't happen in practice: targetHeapUtilization is in (0, 1]. But in case it does, don't touch anything.
|
// This shouldn't happen in practice: targetHeapUtilization is in (0, 1]. But in case it does, don't touch anything.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double minHeapBytes = static_cast<double>(config_.minHeapBytes.load());
|
double minHeapBytes = static_cast<double>(config_.minHeapBytes.load(std::memory_order_relaxed));
|
||||||
double maxHeapBytes = static_cast<double>(config_.maxHeapBytes.load());
|
double maxHeapBytes = static_cast<double>(config_.maxHeapBytes.load(std::memory_order_relaxed));
|
||||||
targetHeapBytes = std::min(std::max(targetHeapBytes, minHeapBytes), maxHeapBytes);
|
targetHeapBytes = std::min(std::max(targetHeapBytes, minHeapBytes), maxHeapBytes);
|
||||||
config_.targetHeapBytes = static_cast<int64_t>(targetHeapBytes);
|
config_.targetHeapBytes.store(static_cast<int64_t>(targetHeapBytes), std::memory_order_relaxed);
|
||||||
|
targetHeapBytes_ = static_cast<size_t>(targetHeapBytes);
|
||||||
|
} else {
|
||||||
|
targetHeapBytes_ = config_.targetHeapBytes.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the mutators.
|
void OnPerformFullGC() noexcept {
|
||||||
bool NeedsGC() const noexcept {
|
// TODO: Need to protect against mutators that can overrun the GC thread.
|
||||||
uint64_t currentHeapBytes = allocatedBytes_.load() + lastAliveSetBytes_.load();
|
targetHeapBytes_ = std::numeric_limits<size_t>::max();
|
||||||
uint64_t targetHeapBytes = config_.targetHeapBytes;
|
|
||||||
return currentHeapBytes >= targetHeapBytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCSchedulerConfig& config_;
|
GCSchedulerConfig& config_;
|
||||||
// Updated by both the mutators and the GC thread.
|
size_t targetHeapBytes_ = 0;
|
||||||
std::atomic<size_t> allocatedBytes_ = 0;
|
|
||||||
// Updated by the GC thread, read by the mutators.
|
|
||||||
std::atomic<size_t> lastAliveSetBytes_ = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace kotlin::gcScheduler::internal
|
} // namespace kotlin::gcScheduler::internal
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class GCSchedulerDataManual : public GCSchedulerData {
|
|||||||
public:
|
public:
|
||||||
GCSchedulerDataManual() noexcept { RuntimeLogInfo({kTagGC}, "Manual GC scheduler initialized"); }
|
GCSchedulerDataManual() noexcept { RuntimeLogInfo({kTagGC}, "Manual GC scheduler initialized"); }
|
||||||
|
|
||||||
void UpdateFromThreadData(GCSchedulerThreadData& threadData) noexcept override {}
|
|
||||||
void OnPerformFullGC() noexcept override {}
|
void OnPerformFullGC() noexcept override {}
|
||||||
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
|
void UpdateAliveSetBytes(size_t bytes) noexcept override {}
|
||||||
|
void SetAllocatedBytes(size_t bytes) noexcept override {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace kotlin::gcScheduler::internal
|
} // namespace kotlin::gcScheduler::internal
|
||||||
|
|||||||
@@ -3786,3 +3786,5 @@ void kotlin::StartFinalizerThreadIfNeeded() noexcept {}
|
|||||||
bool kotlin::FinalizersThreadIsRunning() noexcept {
|
bool kotlin::FinalizersThreadIsRunning() noexcept {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void kotlin::OnMemoryAllocation(size_t totalAllocatedBytes) noexcept {}
|
||||||
|
|||||||
@@ -553,6 +553,8 @@ extern const bool kSupportsMultipleMutators;
|
|||||||
void StartFinalizerThreadIfNeeded() noexcept;
|
void StartFinalizerThreadIfNeeded() noexcept;
|
||||||
bool FinalizersThreadIsRunning() noexcept;
|
bool FinalizersThreadIsRunning() noexcept;
|
||||||
|
|
||||||
|
void OnMemoryAllocation(size_t totalAllocatedBytes) noexcept;
|
||||||
|
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
RUNTIME_NOTHROW ALWAYS_INLINE extern "C" void Kotlin_processObjectInMark(void* state, ObjHeader* object);
|
RUNTIME_NOTHROW ALWAYS_INLINE extern "C" void Kotlin_processObjectInMark(void* state, ObjHeader* object);
|
||||||
|
|||||||
@@ -135,14 +135,12 @@ public object GC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How many bytes a thread can allocate before informing the GC scheduler.
|
* Deprecated and unused.
|
||||||
*
|
|
||||||
* Default: 10 * 1024
|
|
||||||
*
|
*
|
||||||
* Legacy MM: GC allocation threshold, controlling how many bytes allocated since last
|
* Legacy MM: GC allocation threshold, controlling how many bytes allocated since last
|
||||||
* collection will trigger new GC.
|
* collection will trigger new GC.
|
||||||
*
|
*
|
||||||
* Default: (legacy MM) 8 * 1024 * 1024
|
* Default: 8 * 1024 * 1024
|
||||||
*
|
*
|
||||||
* @throws [IllegalArgumentException] when value is not positive.
|
* @throws [IllegalArgumentException] when value is not positive.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -375,6 +375,8 @@ mi_decl_nodiscard mi_decl_export void* mi_new_reallocn(void* p, size_t newcount,
|
|||||||
|
|
||||||
#if KONAN_MI_MALLOC
|
#if KONAN_MI_MALLOC
|
||||||
mi_decl_nodiscard mi_decl_export size_t mi_allocated_size(void) mi_attr_noexcept;
|
mi_decl_nodiscard mi_decl_export size_t mi_allocated_size(void) mi_attr_noexcept;
|
||||||
|
// This hook must be implemented by the embedder. `allocated_size` is the new size of the heap.
|
||||||
|
mi_decl_export void mi_hook_allocation(size_t allocated_size) mi_attr_noexcept;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -387,7 +387,8 @@ void* _mi_mem_alloc_aligned(size_t size, size_t alignment, bool* commit, bool* l
|
|||||||
if (*commit) { ((uint8_t*)p)[0] = 0; } // ensure the memory is committed
|
if (*commit) { ((uint8_t*)p)[0] = 0; } // ensure the memory is committed
|
||||||
#endif
|
#endif
|
||||||
#if KONAN_MI_MALLOC
|
#if KONAN_MI_MALLOC
|
||||||
mi_atomic_add_relaxed(&allocated_size, size);
|
size_t prev_allocated_size = mi_atomic_add_relaxed(&allocated_size, size);
|
||||||
|
mi_hook_allocation(prev_allocated_size + size);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
|
|||||||
@@ -358,12 +358,14 @@ extern "C" int64_t Kotlin_native_internal_GC_getCollectCyclesThreshold(ObjHeader
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setThresholdAllocations(ObjHeader*, int64_t value) {
|
extern "C" void Kotlin_native_internal_GC_setThresholdAllocations(ObjHeader*, int64_t value) {
|
||||||
RuntimeAssert(value > 0, "Must be handled by the caller");
|
// TODO: Remove when legacy MM is gone.
|
||||||
mm::GlobalData::Instance().gcScheduler().config().allocationThresholdBytes = value;
|
// Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" int64_t Kotlin_native_internal_GC_getThresholdAllocations(ObjHeader*) {
|
extern "C" int64_t Kotlin_native_internal_GC_getThresholdAllocations(ObjHeader*) {
|
||||||
return mm::GlobalData::Instance().gcScheduler().config().allocationThresholdBytes.load();
|
// TODO: Remove when legacy MM is gone.
|
||||||
|
// Nothing to do
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Kotlin_native_internal_GC_setTuneThreshold(ObjHeader*, KBoolean value) {
|
extern "C" void Kotlin_native_internal_GC_setTuneThreshold(ObjHeader*, KBoolean value) {
|
||||||
@@ -619,3 +621,7 @@ RUNTIME_NOTHROW extern "C" OBJ_GETTER(Konan_RegularWeakReferenceImpl_get, ObjHea
|
|||||||
RUNTIME_NOTHROW extern "C" void DisposeRegularWeakReferenceImpl(ObjHeader* weakRef) {
|
RUNTIME_NOTHROW extern "C" void DisposeRegularWeakReferenceImpl(ObjHeader* weakRef) {
|
||||||
mm::disposeRegularWeakReferenceImpl(weakRef);
|
mm::disposeRegularWeakReferenceImpl(weakRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void kotlin::OnMemoryAllocation(size_t totalAllocatedBytes) noexcept {
|
||||||
|
mm::GlobalData::Instance().gcScheduler().gcData().SetAllocatedBytes(totalAllocatedBytes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include "GlobalData.hpp"
|
#include "GlobalData.hpp"
|
||||||
#include "GlobalsRegistry.hpp"
|
#include "GlobalsRegistry.hpp"
|
||||||
#include "GC.hpp"
|
#include "GC.hpp"
|
||||||
#include "GCScheduler.hpp"
|
|
||||||
#include "ObjectFactory.hpp"
|
#include "ObjectFactory.hpp"
|
||||||
#include "ShadowStack.hpp"
|
#include "ShadowStack.hpp"
|
||||||
#include "SpecialRefRegistry.hpp"
|
#include "SpecialRefRegistry.hpp"
|
||||||
@@ -33,8 +32,7 @@ public:
|
|||||||
threadId_(threadId),
|
threadId_(threadId),
|
||||||
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
globalsThreadQueue_(GlobalsRegistry::Instance()),
|
||||||
specialRefRegistry_(SpecialRefRegistry::instance()),
|
specialRefRegistry_(SpecialRefRegistry::instance()),
|
||||||
gcScheduler_(GlobalData::Instance().gcScheduler().NewThreadData()),
|
gc_(GlobalData::Instance().gc(), *this),
|
||||||
gc_(GlobalData::Instance().gc(), gcScheduler_, *this),
|
|
||||||
suspensionData_(ThreadState::kNative, *this) {}
|
suspensionData_(ThreadState::kNative, *this) {}
|
||||||
|
|
||||||
~ThreadData() = default;
|
~ThreadData() = default;
|
||||||
@@ -55,8 +53,6 @@ public:
|
|||||||
|
|
||||||
std_support::vector<std::pair<ObjHeader**, ObjHeader*>>& initializingSingletons() noexcept { return initializingSingletons_; }
|
std_support::vector<std::pair<ObjHeader**, ObjHeader*>>& initializingSingletons() noexcept { return initializingSingletons_; }
|
||||||
|
|
||||||
gcScheduler::GCSchedulerThreadData& gcScheduler() noexcept { return gcScheduler_; }
|
|
||||||
|
|
||||||
gc::GC::ThreadData& gc() noexcept { return gc_; }
|
gc::GC::ThreadData& gc() noexcept { return gc_; }
|
||||||
|
|
||||||
ThreadSuspensionData& suspensionData() { return suspensionData_; }
|
ThreadSuspensionData& suspensionData() { return suspensionData_; }
|
||||||
@@ -80,7 +76,6 @@ private:
|
|||||||
ThreadLocalStorage tls_;
|
ThreadLocalStorage tls_;
|
||||||
SpecialRefRegistry::ThreadQueue specialRefRegistry_;
|
SpecialRefRegistry::ThreadQueue specialRefRegistry_;
|
||||||
ShadowStack shadowStack_;
|
ShadowStack shadowStack_;
|
||||||
gcScheduler::GCSchedulerThreadData gcScheduler_;
|
|
||||||
gc::GC::ThreadData gc_;
|
gc::GC::ThreadData gc_;
|
||||||
std_support::vector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;
|
std_support::vector<std::pair<ObjHeader**, ObjHeader*>> initializingSingletons_;
|
||||||
ThreadSuspensionData suspensionData_;
|
ThreadSuspensionData suspensionData_;
|
||||||
|
|||||||
@@ -72,3 +72,7 @@ void kotlin::compactObjectPoolInMainThread() noexcept {
|
|||||||
size_t kotlin::allocatedBytes() noexcept {
|
size_t kotlin::allocatedBytes() noexcept {
|
||||||
return mi_allocated_size();
|
return mi_allocated_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void mi_hook_allocation(size_t allocated_size) mi_attr_noexcept {
|
||||||
|
OnMemoryAllocation(allocated_size);
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "Memory.h"
|
||||||
|
|
||||||
#if KONAN_INTERNAL_DLMALLOC
|
#if KONAN_INTERNAL_DLMALLOC
|
||||||
extern "C" void* dlcalloc(size_t, size_t);
|
extern "C" void* dlcalloc(size_t, size_t);
|
||||||
extern "C" void dlfree(void*);
|
extern "C" void dlfree(void*);
|
||||||
@@ -37,13 +39,17 @@ size_t allocatedBytesCounter = 0;
|
|||||||
void kotlin::initObjectPool() noexcept {}
|
void kotlin::initObjectPool() noexcept {}
|
||||||
|
|
||||||
void* kotlin::allocateInObjectPool(size_t size) noexcept {
|
void* kotlin::allocateInObjectPool(size_t size) noexcept {
|
||||||
|
// TODO: Check that alignment to kObjectAlignment is satisfied.
|
||||||
|
void* result = callocImpl(1, size);
|
||||||
#ifndef KONAN_NO_THREADS
|
#ifndef KONAN_NO_THREADS
|
||||||
allocatedBytesCounter.fetch_add(size, std::memory_order_relaxed);
|
auto newSize = allocatedBytesCounter.fetch_add(size, std::memory_order_relaxed);
|
||||||
|
newSize += size;
|
||||||
#else
|
#else
|
||||||
allocatedBytesCounter += size;
|
allocatedBytesCounter += size;
|
||||||
|
auto newSize = allocatedBytesCounter;
|
||||||
#endif
|
#endif
|
||||||
// TODO: Check that alignment to kObjectAlignment is satisfied.
|
OnMemoryAllocation(newSize);
|
||||||
return callocImpl(1, size);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void kotlin::freeInObjectPool(void* ptr, size_t size) noexcept {
|
void kotlin::freeInObjectPool(void* ptr, size_t size) noexcept {
|
||||||
|
|||||||
Reference in New Issue
Block a user