Skip to content

Commit d5396c7

Browse files
committed
Purge N dirty blocks, using currently dirtiest region(s), and tests for same.
1 parent c01585f commit d5396c7

File tree

1 file changed

+175
-29
lines changed

1 file changed

+175
-29
lines changed

sdlib/d/gc/region.d

Lines changed: 175 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ import sdc.intrinsics;
1313

1414
alias ClassTree = RBTree!(Region, classAddrRegionCmp, "rbClass");
1515
alias RangeTree = RBTree!(Region, addrRangeRegionCmp, "rbRange");
16+
alias DirtTree = RBTree!(Region, dirtAddrRegionCmp, "rbDirt");
1617

1718
alias ClassNode = rbtree.Node!(Region, "rbClass");
1819
alias RangeNode = rbtree.Node!(Region, "rbRange");
20+
alias DirtNode = rbtree.Node!(Region, "rbDirt");
1921
alias PHNode = heap.Node!Region;
2022

2123
// Reserve memory in blocks of 1GB.
@@ -59,6 +61,9 @@ private:
5961
ClassTree regionsByClass;
6062
RangeTree regionsByRange;
6163

64+
// Dirty regions only, by dirt (in descending order)
65+
DirtTree regionsByDirt;
66+
6267
// Unused region objects.
6368
Heap!(Region, unusedRegionCmp) unusedRegions;
6469

@@ -76,10 +81,6 @@ public:
7681
void release(void* ptr, uint blocks) shared {
7782
assert(blocks > 0, "Invalid number of blocks!");
7883

79-
// Eagerly clean the block we are returned.
80-
import d.gc.memmap;
81-
pages_purge(ptr, blocks * BlockSize);
82-
8384
mutex.lock();
8485
scope(exit) mutex.unlock();
8586

@@ -93,6 +94,15 @@ public:
9394
return (cast(RegionAllocator*) &this).computeAddressRangeImpl();
9495
}
9596

97+
uint purgeDirtyBlocks(uint blocks) shared {
98+
assert(blocks > 0, "Invalid number of blocks!");
99+
100+
mutex.lock();
101+
scope(exit) mutex.unlock();
102+
103+
return (cast(RegionAllocator*) &this).purgeDirtyBlocksImpl(blocks);
104+
}
105+
96106
private:
97107
auto computeAddressRangeImpl() {
98108
assert(mutex.isHeld(), "Mutex not held!");
@@ -116,6 +126,7 @@ private:
116126
}
117127
} else {
118128
regionsByRange.remove(r);
129+
unregisterRegionDirt(r);
119130
}
120131

121132
assert(r.address !is null && isAligned(r.address, BlockSize),
@@ -158,6 +169,46 @@ private:
158169
registerRegion(r);
159170
}
160171

172+
void registerRegionDirt(Region* r) {
173+
assert(r !is null, "Region is null!");
174+
175+
if (r.dirtyBlockCount > 0) {
176+
regionsByDirt.insert(r);
177+
}
178+
}
179+
180+
void unregisterRegionDirt(Region* r) {
181+
assert(r !is null, "Region is null!");
182+
183+
if (r.dirtyBlockCount > 0) {
184+
regionsByDirt.remove(r);
185+
}
186+
}
187+
188+
uint purgeDirtyBlocksImpl(uint blocks) {
189+
assert(mutex.isHeld(), "Mutex not held!");
190+
191+
blocks = min(blocks, dirtyBlockCount);
192+
auto blocksToPurge = blocks;
193+
194+
Region rr;
195+
rr.dirtyBlockCount = uint.max;
196+
197+
while (blocksToPurge > 0) {
198+
auto r = regionsByDirt.extractBestFit(&rr);
199+
assert(r !is null,
200+
"Nonzero dirty block count, but there is no dirty region!");
201+
202+
auto regionBlocksToPurge = min(blocksToPurge, r.dirtyBlockCount);
203+
r.purgeDirtyBlocks(regionBlocksToPurge);
204+
registerRegionDirt(r);
205+
blocksToPurge -= regionBlocksToPurge;
206+
}
207+
208+
dirtyBlockCount -= blocks;
209+
return blocks;
210+
}
211+
161212
void registerRegion(Region* r) {
162213
assert(r !is null, "Region is null!");
163214

@@ -169,6 +220,7 @@ private:
169220
}
170221

171222
regionsByClass.remove(adjacent);
223+
unregisterRegionDirt(adjacent);
172224

173225
// Make sure we keep using the best region.
174226
bool needSwap = unusedRegionCmp(r, adjacent) < 0;
@@ -181,6 +233,7 @@ private:
181233

182234
regionsByClass.insert(r);
183235
regionsByRange.insert(r);
236+
registerRegionDirt(r);
184237
}
185238

186239
Region* refillAddressSpace(uint extraBlocks) {
@@ -369,6 +422,7 @@ struct Region {
369422
struct UsedLinks {
370423
ClassNode rbClass;
371424
RangeNode rbRange;
425+
DirtNode rbDirt;
372426
}
373427

374428
union Links {
@@ -466,6 +520,11 @@ public:
466520
return _links.usedLinks.rbRange;
467521
}
468522

523+
@property
524+
ref DirtNode rbDirt() {
525+
return _links.usedLinks.rbDirt;
526+
}
527+
469528
@property
470529
size_t size() const {
471530
return blockCount * BlockSize;
@@ -493,17 +552,6 @@ public:
493552
assert(address is (r.address + r.size) || r.address is (address + size),
494553
"Regions are not adjacent!");
495554

496-
auto left = address < r.address ? &this : r;
497-
auto right = address < r.address ? r : &this;
498-
499-
// Dirt is at all times contiguous within a region, and starts at the bottom.
500-
// Given as purging is not yet supported, this invariant always holds.
501-
assert(
502-
left.dirtyBlockCount == left.blockCount
503-
|| right.dirtyBlockCount == 0,
504-
"Merge would place dirty blocks in front of clean blocks!"
505-
);
506-
507555
// Copy the dirty bits.
508556
// FIXME: We use min to ensures we don't trip an assert
509557
// when the region is larger than 1GB.
@@ -514,6 +562,54 @@ public:
514562
return at(a, blockCount + r.blockCount,
515563
dirtyBlockCount + r.dirtyBlockCount);
516564
}
565+
566+
Region* purgeDirtyBlocks(uint blocksToPurge) {
567+
assert(blocksToPurge <= dirtyBlockCount,
568+
"Region has fewer dirty blocks than requested to purge!");
569+
assert(blocksToPurge > 0, "Requested to purge zero blocks!");
570+
571+
auto startIndex = startOffset;
572+
auto endIndex = (startIndex + blockCount) % RefillBlockCount;
573+
uint index = endIndex;
574+
575+
while (blocksToPurge > 0) {
576+
bool roll = index <= startIndex;
577+
578+
// Find the last dirty run
579+
auto endDirtRun = dirtyBlocks.findSetBackward(index);
580+
if (endDirtRun < 0) {
581+
if (roll) {
582+
index = RefillBlockCount;
583+
continue;
584+
}
585+
586+
break;
587+
}
588+
589+
index = dirtyBlocks.findClearBackward(endDirtRun);
590+
auto startDirtRun = index + 1;
591+
auto runBlocksToPurge = min(blocksToPurge, endDirtRun - index);
592+
startDirtRun = endDirtRun - runBlocksToPurge + 1;
593+
594+
auto effectiveIndex =
595+
startDirtRun + (roll ? RefillBlockCount : 0) - startIndex;
596+
assert(effectiveIndex < blockCount, "Purge offset out of range!");
597+
598+
// Purge.
599+
import d.gc.memmap;
600+
pages_purge(address + effectiveIndex * BlockSize,
601+
runBlocksToPurge * BlockSize);
602+
603+
dirtyBlocks.clearRange(startDirtRun, runBlocksToPurge);
604+
blocksToPurge -= runBlocksToPurge;
605+
dirtyBlockCount -= runBlocksToPurge;
606+
}
607+
608+
assert(blocksToPurge == 0,
609+
"Failed to purge all requested blocks in region!");
610+
611+
return &this;
612+
}
517613
}
518614

519615
ptrdiff_t addrRangeRegionCmp(Region* lhs, Region* rhs) {
@@ -552,6 +648,23 @@ ptrdiff_t classAddrRegionCmp(Region* lhs, Region* rhs) {
552648
return (l > r) - (l < r);
553649
}
554650

651+
ptrdiff_t dirtAddrRegionCmp(Region* lhs, Region* rhs) {
652+
// Descending order of dirt
653+
auto rdirt = lhs.dirtyBlockCount;
654+
auto ldirt = rhs.dirtyBlockCount;
655+
auto dirtCmp = (ldirt > rdirt) - (ldirt < rdirt);
656+
657+
if (dirtCmp != 0) {
658+
return dirtCmp;
659+
}
660+
661+
// Descending order of address
662+
auto r = cast(size_t) lhs.address;
663+
auto l = cast(size_t) rhs.address;
664+
665+
return (l > r) - (l < r);
666+
}
667+
555668
ptrdiff_t unusedRegionCmp(Region* lhs, Region* rhs) {
556669
static assert(LgAddressSpace <= 56, "Address space too large!");
557670

@@ -609,37 +722,70 @@ unittest trackDirtyBlocks {
609722
assert(r.countDirtyBlocksInSubRegion(0, blocks) == dirtyBlocks);
610723
}
611724

725+
// Verify that the currently-dirtiest region has the given attributes.
726+
void verifyDirtiestRegion(void* address, uint blocks, uint dirtyBlocks) {
727+
Region rr;
728+
rr.dirtyBlockCount = uint.max;
729+
730+
auto r = ra.regionsByDirt.extractBestFit(&rr);
731+
assert(r !is null);
732+
assert(r.address is address);
733+
assert(r.blockCount == blocks);
734+
assert(r.dirtyBlockCount == dirtyBlocks);
735+
assert(r.countDirtyBlocksInSubRegion(0, blocks) == dirtyBlocks);
736+
ra.registerRegionDirt(r);
737+
}
738+
612739
// Initially, there are no dirty blocks.
613740
assert(regionAllocator.dirtyBlockCount == 0);
614741

615742
// Make some dirty regions.
616-
freeRun(addresses[0 .. 2]);
617-
assert(regionAllocator.dirtyBlockCount == 2);
618-
verifyUniqueRegion(addresses[0], 2, 2, 2);
619743
freeRun(addresses[4 .. 8]);
620-
assert(regionAllocator.dirtyBlockCount == 6);
744+
assert(regionAllocator.dirtyBlockCount == 4);
621745
verifyUniqueRegion(addresses[4], 4, 4, 4);
746+
verifyDirtiestRegion(addresses[4], 4, 4);
747+
assert(regionAllocator.purgeDirtyBlocks(2) == 2);
748+
verifyUniqueRegion(addresses[4], 4, 4, 2);
749+
verifyDirtiestRegion(addresses[4], 4, 2);
622750
freeRun(addresses[10 .. 15]);
623-
assert(regionAllocator.dirtyBlockCount == 11);
751+
assert(regionAllocator.dirtyBlockCount == 7);
624752
verifyUniqueRegion(addresses[10], 5, 5, 5);
753+
verifyDirtiestRegion(addresses[10], 5, 5);
754+
assert(regionAllocator.purgeDirtyBlocks(4) == 4);
755+
verifyUniqueRegion(addresses[10], 5, 5, 1);
756+
verifyDirtiestRegion(addresses[4], 4, 2);
757+
freeRun(addresses[0 .. 2]);
758+
assert(regionAllocator.dirtyBlockCount == 5);
759+
verifyUniqueRegion(addresses[0], 2, 2, 2);
760+
verifyDirtiestRegion(addresses[4], 4, 2);
625761

626762
// Merge regions and confirm expected effect.
627763
freeRun(addresses[8 .. 10]);
628-
assert(regionAllocator.dirtyBlockCount == 13);
629-
verifyUniqueRegion(addresses[4], 10, 11, 11);
764+
assert(regionAllocator.dirtyBlockCount == 7);
765+
verifyUniqueRegion(addresses[4], 10, 11, 5);
766+
verifyDirtiestRegion(addresses[4], 11, 5);
630767
freeRun(addresses[2 .. 4]);
631-
assert(regionAllocator.dirtyBlockCount == 15);
632-
verifyUniqueRegion(addresses[0], 14, 15, 15);
768+
assert(regionAllocator.dirtyBlockCount == 9);
769+
verifyUniqueRegion(addresses[0], 14, 15, 9);
770+
verifyDirtiestRegion(addresses[0], 15, 9);
771+
assert(regionAllocator.purgeDirtyBlocks(3) == 3);
772+
assert(regionAllocator.dirtyBlockCount == 6);
633773
freeRun(addresses[15 .. 16]);
634-
verifyUniqueRegion(addresses[0], 1, RefillBlockCount, 16);
774+
assert(regionAllocator.dirtyBlockCount == 7);
775+
verifyUniqueRegion(addresses[0], 1, RefillBlockCount, 7);
776+
verifyDirtiestRegion(addresses[0], RefillBlockCount, 7);
635777

636778
// Test dirt behaviour in acquire and release.
637779
void* addr0;
638780
assert(regionAllocator.acquire(&addr0, 5));
639781
assert(addr0 is addresses[5]);
640-
assert(regionAllocator.dirtyBlockCount == 10);
641-
verifyUniqueRegion(addresses[6], 1, RefillBlockCount - 6, 10);
782+
assert(regionAllocator.dirtyBlockCount == 1);
783+
verifyUniqueRegion(addresses[6], 1, RefillBlockCount - 6, 1);
784+
verifyDirtiestRegion(addresses[6], RefillBlockCount - 6, 1);
642785
regionAllocator.release(addresses[0], 6);
643-
assert(regionAllocator.dirtyBlockCount == 16);
644-
verifyUniqueRegion(addresses[0], 1, RefillBlockCount, 16);
786+
assert(regionAllocator.dirtyBlockCount == 7);
787+
verifyUniqueRegion(addresses[0], 1, RefillBlockCount, 7);
788+
verifyDirtiestRegion(addresses[0], RefillBlockCount, 7);
789+
assert(regionAllocator.purgeDirtyBlocks(666) == 7);
790+
assert(regionAllocator.dirtyBlockCount == 0);
645791
}

0 commit comments

Comments
 (0)