Skip to content

Commit 2dd2252

Browse files
committed
Merge branch 'master' of github.com:dsm9000/sdc into slab_alloc_threadcache
2 parents 54c3968 + 45559e2 commit 2dd2252

File tree

2 files changed

+193
-113
lines changed

2 files changed

+193
-113
lines changed

sdlib/d/gc/slab.d

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ public:
7676
return true;
7777
}
7878

79+
void clearMetadata() {
80+
setFreeSpace(0);
81+
}
82+
7983
@property
8084
Finalizer finalizer() {
8185
if (!finalizerEnabled) {

sdlib/d/gc/tcache.d

Lines changed: 189 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -16,106 +16,7 @@ private:
1616
const(void)* stackBottom;
1717
const(void*)[][] roots;
1818

19-
//// Cache for Slab Allocs
20-
struct SmallAllocCache {
21-
private:
22-
alias Entry = void*;
23-
enum BinCount = ClassCount.Small;
24-
enum CacheEntries = 4;
25-
enum BinEntries = CacheEntries * 2;
26-
enum LowWaterMark = 1;
27-
enum HighWaterMark = CacheEntries;
28-
enum Ceiling = BinEntries - 1;
29-
30-
struct CacheBin {
31-
private:
32-
uint top = 0;
33-
uint bottom = 0;
34-
Entry[BinEntries] entries;
35-
36-
void push(Entry ptr) {
37-
assert(entryCount < CacheEntries, "No room to push() !");
38-
assert(top < Ceiling, "No room to push()!");
39-
40-
entries[++top] = ptr;
41-
}
42-
43-
Entry pop() {
44-
assert(entryCount > 0, "No entry to pop() !");
45-
46-
auto entry = entries[bottom];
47-
assert(entry !is null, "Pop() got a null!");
48-
49-
entries[bottom++] = null;
50-
return entry;
51-
}
52-
53-
@property
54-
uint entryCount() {
55-
assert(top >= bottom, "Top is below bottom!");
56-
57-
return top - bottom;
58-
}
59-
60-
public:
61-
@property
62-
bool needRefill() {
63-
return entryCount <= LowWaterMark;
64-
}
65-
66-
@property
67-
bool needFlush() {
68-
return entryCount >= HighWaterMark;
69-
}
70-
71-
void refill() {}
72-
73-
void flush() {}
74-
75-
Entry alloc() {
76-
if (needRefill) {
77-
refill();
78-
assert(!needRefill, "Cache did not refill!");
79-
}
80-
81-
return pop();
82-
}
83-
84-
void free(Entry ptr) {
85-
if (needFlush) {
86-
flush();
87-
assert(!needFlush, "Cache did not flush!");
88-
}
89-
90-
push(ptr);
91-
}
92-
}
93-
94-
CacheBin[2][BinCount] _bins;
95-
96-
ref CacheBin getBin(size_t sizeClass, bool containsPointers) {
97-
assert(isSmallSize(sizeClass), "Invalid size class!");
98-
99-
return _bins[containsPointers][sizeClass];
100-
}
101-
102-
public:
103-
Entry alloc(size_t size, bool containsPointers) {
104-
auto sizeClass = getSizeClass(size);
105-
assert(isSmallSize(sizeClass), "Invalid size class!");
106-
107-
return getBin(sizeClass, containsPointers).alloc();
108-
}
109-
110-
void free(PageDescriptor pd, void* ptr) {
111-
auto sizeClass = pd.sizeClass;
112-
assert(isSmallSize(sizeClass), "Invalid size class!");
113-
114-
getBin(sizeClass, pd.containsPointers).free(ptr);
115-
}
116-
}
117-
118-
SmallAllocCache _smallAllocCache;
19+
SmallAllocCache[2] smallAllocCache;
11920

12021
public:
12122
void* alloc(size_t size, bool containsPointers) {
@@ -127,29 +28,34 @@ public:
12728

12829
auto arena = chooseArena(containsPointers);
12930
return isSmallSize(size)
130-
? arena.allocSmall(emap, size)
31+
? smallAllocCache[containsPointers].alloc(arena, emap, size)
13132
: arena.allocLarge(emap, size, false);
13233
}
13334

13435
void* allocAppendable(size_t size, bool containsPointers,
13536
Finalizer finalizer = null) {
136-
auto asize = getAllocSize(alignUp(size, 2 * Quantum));
37+
if (!isAllocatableSize(size)) {
38+
return null;
39+
}
40+
41+
auto reservedBytes = finalizer is null ? 0 : PointerSize;
42+
auto asize = getAllocSize(alignUp(size + reservedBytes, 2 * Quantum));
13743
assert(sizeClassSupportsMetadata(getSizeClass(asize)),
13844
"allocAppendable got size class without metadata support!");
13945

14046
initializeExtentMap();
14147

14248
auto arena = chooseArena(containsPointers);
14349
if (isSmallSize(asize)) {
144-
auto ptr = arena.allocSmall(emap, asize);
50+
auto ptr =
51+
smallAllocCache[containsPointers].alloc(arena, emap, asize);
14552
auto pd = getPageDescriptor(ptr);
14653
auto si = SlabAllocInfo(pd, ptr);
147-
si.setUsedCapacity(size);
148-
assert(finalizer is null, "finalizer not yet supported for slab!");
54+
si.initializeMetadata(finalizer, size);
14955
return ptr;
15056
}
15157

152-
auto ptr = arena.allocLarge(emap, asize, false);
58+
auto ptr = arena.allocLarge(emap, size, false);
15359
auto pd = getPageDescriptor(ptr);
15460
auto e = pd.extent;
15561
e.setUsedCapacity(size);
@@ -169,7 +75,7 @@ public:
16975
return arena.allocLarge(emap, size, true);
17076
}
17177

172-
auto ret = arena.allocSmall(emap, size);
78+
auto ret = smallAllocCache[containsPointers].alloc(arena, emap, size);
17379
memset(ret, 0, size);
17480
return ret;
17581
}
@@ -180,6 +86,15 @@ public:
18086
}
18187

18288
auto pd = getPageDescriptor(ptr);
89+
90+
if (pd.isSlab()) {
91+
auto si = SlabAllocInfo(pd, ptr);
92+
si.clearMetadata();
93+
smallAllocCache[pd.containsPointers]
94+
.intern(pd.arena, emap, pd, ptr);
95+
return;
96+
}
97+
18398
pd.arena.free(emap, pd, ptr);
18499
}
185100

@@ -189,10 +104,25 @@ public:
189104
}
190105

191106
auto pd = getPageDescriptor(ptr);
192-
193107
auto e = pd.extent;
194-
// Slab is not yet supported
195-
if (e !is null && !pd.isSlab() && e.finalizer !is null) {
108+
109+
if (pd.isSlab()) {
110+
auto si = SlabAllocInfo(pd, ptr);
111+
auto finalizer = si.finalizer;
112+
if (finalizer !is null) {
113+
assert(cast(void*) si.address == ptr,
114+
"destroy() was invoked on an interior pointer!");
115+
116+
finalizer(ptr, si.usedCapacity);
117+
}
118+
119+
si.clearMetadata();
120+
smallAllocCache[pd.containsPointers]
121+
.intern(pd.arena, emap, pd, ptr);
122+
return;
123+
}
124+
125+
if (e.finalizer !is null) {
196126
e.finalizer(ptr, e.usedCapacity);
197127
}
198128

@@ -222,8 +152,9 @@ public:
222152
auto oldSizeClass = pd.sizeClass;
223153
if (samePointerness && newSizeClass == oldSizeClass) {
224154
auto si = SlabAllocInfo(pd, ptr);
225-
si.setUsedCapacity(size);
226-
return ptr;
155+
if (!si.allowsMetadata || si.setUsedCapacity(size)) {
156+
return ptr;
157+
}
227158
}
228159

229160
if (newSizeClass > oldSizeClass) {
@@ -253,7 +184,7 @@ public:
253184
}
254185

255186
memcpy(newPtr, ptr, copySize);
256-
pd.arena.free(emap, pd, ptr);
187+
free(ptr);
257188

258189
return newPtr;
259190
}
@@ -464,6 +395,112 @@ private:
464395
import d.gc.arena;
465396
return Arena.getOrInitialize((cpuid << 1) | containsPointers);
466397
}
398+
399+
/**
400+
* GC Allocation Cache.
401+
*/
402+
403+
struct SmallAllocCache {
404+
private:
405+
enum CacheEntries = 4;
406+
407+
CacheBin[ClassCount.Small] _bins;
408+
409+
public:
410+
import d.gc.arena;
411+
412+
void* alloc(shared(Arena)* arena, ref shared(ExtentMap)* _emap,
413+
size_t size) {
414+
auto sc = getSizeClass(size);
415+
assert(isSmallSizeClass(sc), "Invalid size class!");
416+
417+
if (_bins[sc].empty) {
418+
// TODO: bulk alloc!!!
419+
foreach (i; 0 .. CacheEntries) {
420+
auto entry = arena.allocSmall(_emap, size);
421+
_bins[sc].insert(entry);
422+
}
423+
}
424+
425+
return _bins[sc].dequeue();
426+
}
427+
428+
void intern(shared(Arena)* arena, ref shared(ExtentMap)* _emap,
429+
PageDescriptor pd, void* ptr) {
430+
auto sc = pd.sizeClass;
431+
assert(isSmallSizeClass(sc), "Size class is not small!");
432+
433+
if (_bins[sc].full) {
434+
// TODO: sort by quality and evict the 'worst' !!!
435+
auto evictedEntry = _bins[sc].pop();
436+
arena.free(_emap, pd, evictedEntry);
437+
}
438+
439+
_bins[sc].push(ptr);
440+
}
441+
442+
private:
443+
struct CacheBin {
444+
private:
445+
enum BinEntries = CacheEntries + 1;
446+
447+
uint _head = 0;
448+
uint _tail = 0;
449+
uint _count = 0;
450+
void*[BinEntries] entries;
451+
452+
public:
453+
void insert(void* ptr) {
454+
assert(ptr !is null, "Tried to insert() null!");
455+
assert(_count < BinEntries, "Cannot insert()!");
456+
457+
entries[_tail] = ptr;
458+
_tail = (_tail + 1) % BinEntries;
459+
_count++;
460+
}
461+
462+
void* dequeue() {
463+
assert(_count > 0, "Nothing to dequeue()!");
464+
465+
auto entry = entries[_head];
466+
_head = (_head + 1) % BinEntries;
467+
_count--;
468+
469+
assert(entry !is null, "dequeue() got null!");
470+
return entry;
471+
}
472+
473+
void push(void* ptr) {
474+
assert(ptr !is null, "Tried to push() null!");
475+
assert(_count < BinEntries, "Cannot push()!");
476+
477+
entries[_head] = ptr;
478+
_head = (_head - 1) % BinEntries;
479+
_count++;
480+
}
481+
482+
void* pop() {
483+
assert(_count > 0, "Nothing to pop()!");
484+
485+
auto entry = entries[_tail];
486+
_tail = (_tail - 1) % BinEntries;
487+
_count--;
488+
489+
assert(entry !is null, "pop() got null!");
490+
return entry;
491+
}
492+
493+
@property
494+
bool empty() {
495+
return _count == 0;
496+
}
497+
498+
@property
499+
bool full() {
500+
return _count == CacheEntries;
501+
}
502+
}
503+
}
467504
}
468505

469506
private:
@@ -527,6 +564,16 @@ unittest makeRange {
527564
checkRange(ptr[1 .. 8], 8, 8);
528565
}
529566

567+
unittest nonAllocatableSizes {
568+
// Prohibited sizes of allocations
569+
assert(threadCache.alloc(0, false) == null);
570+
assert(threadCache.alloc(MaxAllocationSize + 1, false) == null);
571+
assert(threadCache.calloc(0, false) == null);
572+
assert(threadCache.calloc(MaxAllocationSize + 1, false) == null);
573+
assert(threadCache.allocAppendable(0, false) == null);
574+
assert(threadCache.allocAppendable(MaxAllocationSize + 1, false) == null);
575+
}
576+
530577
unittest getCapacity {
531578
// Non-appendable size class 6 (56 bytes)
532579
auto nonAppendable = threadCache.alloc(50, false);
@@ -921,4 +968,33 @@ unittest finalization {
921968
auto oldDestroyCount = destroyCount;
922969
threadCache.destroy(s1);
923970
assert(destroyCount == oldDestroyCount);
971+
972+
// Finalizers for small allocs:
973+
auto s2 = threadCache.allocAppendable(45, false, &destruct);
974+
assert(threadCache.getCapacity(s2[0 .. 45]) == 56);
975+
assert(!threadCache.extend(s2[0 .. 45], 12));
976+
assert(threadCache.extend(s2[0 .. 45], 11));
977+
assert(threadCache.getCapacity(s2[0 .. 56]) == 56);
978+
threadCache.destroy(s2);
979+
assert(lastKilledAddress == s2);
980+
assert(lastKilledUsedCapacity == 56);
981+
982+
// Behaviour of realloc() on small allocs with finalizers:
983+
auto s3 = threadCache.allocAppendable(70, false, &destruct);
984+
assert(threadCache.getCapacity(s3[0 .. 70]) == 72);
985+
auto s4 = threadCache.realloc(s3, 70, false);
986+
assert(s3 == s4);
987+
988+
// This is in the same size class, but will not work in-place
989+
// given as finalizer occupies final 8 of the 80 bytes in the slot:
990+
auto s5 = threadCache.realloc(s4, 75, false);
991+
assert(s5 != s4);
992+
993+
// So we end up with a new alloc, without metadata:
994+
assert(threadCache.getCapacity(s5[0 .. 80]) == 80);
995+
996+
// And the finalizer has been discarded:
997+
oldDestroyCount = destroyCount;
998+
threadCache.destroy(s5);
999+
assert(destroyCount == oldDestroyCount);
9241000
}

0 commit comments

Comments
 (0)