Skip to content

Commit c55f039

Browse files
authored
Gc refactor3 rebased (#1195)
The condition for requesting collections is replaced by requesting a collection if an allocation passes a tripwire. This tripwire is initially set at MIN_SPACE and is recalculated after each garbage collation as max(MIN_SPACE, |live data|). This avoids a potential issue with the old behavior where collections were trigged by allocating in the last 1 MB of previously allocated semispace, even if little garbage is being generated, or not collecting frequently enough if a period of low garbage generation is followed by a period of high garbage generation, affecting cache performance. Furthermore, garbage collections are only trigged by allocations in youngspace. Allocations in oldspace no longer trigger collections since oldspace collections are handled during a youngspace collection. Allocations in alwaysgcspace no longer trigger collections since collection of this space is handled outside of the main garbage collector. Some class arena member functions have the arena_ component removed as superfluous.
1 parent c2e8094 commit c55f039

File tree

4 files changed

+55
-67
lines changed

4 files changed

+55
-67
lines changed

include/runtime/arena.h

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@ extern "C" {
1414

1515
size_t const HYPERBLOCK_SIZE = (size_t)BLOCK_SIZE * 1024 * 1024;
1616

17+
// After a garbage collect we change the tripwire to the amount of non-garbage times
18+
// this factor, so we do a decent amount of allocations between collections even
19+
// when there is very little garbage
20+
size_t const EXPAND_FACTOR = 2;
21+
22+
// We don't consider collecting garbage until at least this amount of space has
23+
// been allocated, to avoid collections near startup when there is little garbage.
24+
size_t const MIN_SPACE = 1024 * 1024;
25+
1726
// An arena can be used to allocate objects that can then be deallocated all at
1827
// once.
1928
class arena {
2029
public:
21-
arena(char id)
22-
: allocation_semispace_id(id) { }
30+
arena(char id, bool trigger_collection)
31+
: allocation_semispace_id(id)
32+
, trigger_collection(trigger_collection) { }
2333

2434
~arena() {
2535
if (current_addr_ptr)
@@ -36,17 +46,17 @@ class arena {
3646

3747
// Returns the address of the first byte that belongs in the given arena.
3848
// Returns nullptr if nothing has been allocated ever in that arena.
39-
char *arena_start_ptr() const { return current_addr_ptr; }
49+
char *start_ptr() const { return current_addr_ptr; }
4050

4151
// Returns a pointer to a location holding the address of last allocated
4252
// byte in the given arena plus 1.
4353
// This address is nullptr if nothing has been allocated ever in that arena.
44-
char *arena_end_ptr() { return allocation_ptr; }
54+
char *end_ptr() { return allocation_ptr; }
4555

4656
// Clears the current allocation space by setting its start back to its first
4757
// block. It is used during garbage collection to effectively collect all of the
48-
// arena. Resets the tripwire.
49-
void arena_clear();
58+
// arena.
59+
void arena_clear() { allocation_ptr = current_addr_ptr; }
5060

5161
// Resizes the last allocation.
5262
// Returns the address of the byte following the last newly allocated byte.
@@ -67,6 +77,14 @@ class arena {
6777
// It is used before garbage collection.
6878
void arena_swap_and_clear();
6979

80+
// Decide how much space to use in arena before setting the flag for a collection.
81+
// If an arena is going to request collections, updating this at the end of a
82+
// collection is mandatory.
83+
void update_tripwire() {
84+
size_t space = EXPAND_FACTOR * (allocation_ptr - current_addr_ptr);
85+
tripwire = current_addr_ptr + ((space < MIN_SPACE) ? MIN_SPACE : space);
86+
}
87+
7088
// Given two pointers to objects allocated in the same arena, return the number
7189
// of bytes they are apart. Undefined behavior will result if the pointers
7290
// don't belong to the same arena
@@ -92,20 +110,6 @@ class arena {
92110
static char get_arena_semispace_id_of_object(void *ptr);
93111

94112
private:
95-
//
96-
// We update the number of 1MB blocks actually written to, only when we need this value,
97-
// or before a garbage collection rather than trying to determine when we write to a fresh block.
98-
//
99-
void update_num_blocks() const {
100-
//
101-
// Calculate how many 1M blocks of the current arena we used.
102-
//
103-
size_t num_used_blocks
104-
= (allocation_ptr - current_addr_ptr - 1) / BLOCK_SIZE + 1;
105-
if (num_used_blocks > num_blocks)
106-
num_blocks = num_used_blocks;
107-
}
108-
109113
void initialize_semispace();
110114
//
111115
// Current semispace where allocations are being made.
@@ -114,16 +118,13 @@ class arena {
114118
char *allocation_ptr
115119
= nullptr; // next available location in current semispace
116120
char *tripwire = nullptr; // allocating past this triggers slow allocation
117-
mutable size_t num_blocks
118-
= 0; // notional number of BLOCK_SIZE blocks in current semispace
119121
char allocation_semispace_id; // id of current semispace
122+
bool const trigger_collection; // request collections?
120123
//
121124
// Semispace where allocations will be made during and after garbage collect.
122125
//
123126
char *collection_addr_ptr
124127
= nullptr; // pointer to start of collection address space
125-
size_t num_collection_blocks
126-
= 0; // notional number of BLOCK_SIZE blocks in collection semispace
127128
};
128129

129130
inline char arena::get_arena_semispace_id_of_object(void *ptr) {
@@ -139,10 +140,6 @@ inline char arena::get_arena_semispace_id_of_object(void *ptr) {
139140
return *reinterpret_cast<char *>(end_address);
140141
}
141142

142-
// Macro to define a new arena with the given ID. Supports IDs ranging from 0 to
143-
// 127.
144-
#define REGISTER_ARENA(name, id) thread_local arena name(id)
145-
146143
#ifdef __MACH__
147144
//
148145
// thread_local disabled for Apple
@@ -183,32 +180,12 @@ inline void *arena::kore_arena_alloc(size_t requested) {
183180
return result;
184181
}
185182

186-
inline void arena::arena_clear() {
187-
//
188-
// We set the allocation pointer to the first available address.
189-
//
190-
allocation_ptr = arena_start_ptr();
191-
//
192-
// If the number of blocks we've touched is >= threshold, we want to trigger
193-
// a garbage collection if we get within 1 block of the end of this area.
194-
// Otherwise we only want to generate a garbage collect if we allocate off the
195-
// end of this area.
196-
//
197-
tripwire = current_addr_ptr
198-
+ (num_blocks - (num_blocks >= get_gc_threshold())) * BLOCK_SIZE;
199-
}
200-
201183
inline void arena::arena_swap_and_clear() {
202-
update_num_blocks(); // so we save the correct number of touched blocks
203184
std::swap(current_addr_ptr, collection_addr_ptr);
204-
std::swap(num_blocks, num_collection_blocks);
205185
allocation_semispace_id = ~allocation_semispace_id;
206-
if (current_addr_ptr == nullptr) {
207-
//
208-
// The other semispace hasn't be initialized yet.
209-
//
210-
initialize_semispace();
211-
} else
186+
if (current_addr_ptr == nullptr)
187+
initialize_semispace(); // not yet initialized
188+
else
212189
arena_clear();
213190
}
214191
}

runtime/alloc/arena.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ void arena::initialize_semispace() {
7272
current_addr_ptr[HYPERBLOCK_SIZE - 1] = allocation_semispace_id;
7373
allocation_ptr = current_addr_ptr;
7474
//
75-
// We set the tripwire for this space so we get trigger a garbage collection when we pass BLOCK_SIZE of memory
76-
// allocated from this space.
75+
// If we're set to trigger garbage collections, set the tripwire for MIN_SPACE of allocations otherwise
76+
// set it out-of-bounds (but still legal for comparison).
7777
//
78-
tripwire = current_addr_ptr + BLOCK_SIZE;
79-
num_blocks = 1;
78+
tripwire
79+
= current_addr_ptr + (trigger_collection ? MIN_SPACE : HYPERBLOCK_SIZE);
8080
}

runtime/collect/collect.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ char *arena::evacuate(char *scan_ptr) {
255255
migrate_child(curr_block, layout_data->args, i, false);
256256
}
257257
}
258-
return move_ptr(scan_ptr, get_size(hdr, layout_int), arena_end_ptr());
258+
return move_ptr(scan_ptr, get_size(hdr, layout_int), end_ptr());
259259
}
260260

261261
// Contains the decision logic for collecting the old generation.
@@ -295,21 +295,21 @@ void kore_collect(
295295
if (!last_alloc_ptr) {
296296
last_alloc_ptr = youngspace_ptr();
297297
}
298-
char *current_alloc_ptr = youngspace.arena_end_ptr();
298+
char *current_alloc_ptr = youngspace.end_ptr();
299299
#endif
300300
kore_alloc_swap(collect_old);
301301
#ifdef GC_DBG
302302
for (int i = 0; i < 2048; i++) {
303303
numBytesLiveAtCollection[i] = 0;
304304
}
305305
#endif
306-
char *previous_oldspace_alloc_ptr = oldspace.arena_end_ptr();
306+
char *previous_oldspace_alloc_ptr = oldspace.end_ptr();
307307
for (int i = 0; i < nroots; i++) {
308308
migrate_root(roots, type_info, i);
309309
}
310310
migrate_static_roots();
311311
char *scan_ptr = youngspace_ptr();
312-
if (scan_ptr != youngspace.arena_end_ptr()) {
312+
if (scan_ptr != youngspace.end_ptr()) {
313313
MEM_LOG("Evacuating young generation\n");
314314
while (scan_ptr) {
315315
scan_ptr = youngspace.evacuate(scan_ptr);
@@ -320,7 +320,7 @@ void kore_collect(
320320
} else {
321321
scan_ptr = previous_oldspace_alloc_ptr;
322322
}
323-
if (scan_ptr != oldspace.arena_end_ptr()) {
323+
if (scan_ptr != oldspace.end_ptr()) {
324324
MEM_LOG("Evacuating old generation\n");
325325
while (scan_ptr) {
326326
scan_ptr = oldspace.evacuate(scan_ptr);
@@ -331,12 +331,13 @@ void kore_collect(
331331
= arena::ptr_diff(current_alloc_ptr, last_alloc_ptr);
332332
assert(numBytesAllocedSinceLastCollection >= 0);
333333
fwrite(&numBytesAllocedSinceLastCollection, sizeof(ssize_t), 1, stderr);
334-
last_alloc_ptr = youngspace.arena_end_ptr();
334+
last_alloc_ptr = youngspace.end_ptr();
335335
fwrite(
336336
numBytesLiveAtCollection, sizeof(numBytesLiveAtCollection[0]),
337337
sizeof(numBytesLiveAtCollection) / sizeof(numBytesLiveAtCollection[0]),
338338
stderr);
339339
#endif
340+
youngspace.update_tripwire();
340341
MEM_LOG("Finishing garbage collection\n");
341342
is_gc = false;
342343
}

runtime/lto/alloc.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,26 @@
1111

1212
extern "C" {
1313

14-
REGISTER_ARENA(youngspace, YOUNGSPACE_ID);
15-
REGISTER_ARENA(oldspace, OLDSPACE_ID);
16-
REGISTER_ARENA(alwaysgcspace, ALWAYSGCSPACE_ID);
14+
// class arena supports ID from 0 to 127
15+
16+
// New data in allocated in the youngspace, which requests a
17+
// collection when is gets too full.
18+
thread_local arena youngspace(YOUNGSPACE_ID, true);
19+
20+
// Data that is old enough is migrated to the oldspace. The
21+
// migrated data is always live at this point so it never
22+
// requests a collection.
23+
thread_local arena oldspace(OLDSPACE_ID, false);
24+
25+
// Temporary data is doesn't use the garbage collector.
26+
thread_local arena alwaysgcspace(ALWAYSGCSPACE_ID, false);
1727

1828
char *youngspace_ptr() {
19-
return youngspace.arena_start_ptr();
29+
return youngspace.start_ptr();
2030
}
2131

2232
char *oldspace_ptr() {
23-
return oldspace.arena_start_ptr();
33+
return oldspace.start_ptr();
2434
}
2535

2636
char youngspace_collection_id() {
@@ -73,7 +83,7 @@ kore_resize_last_alloc(void *oldptr, size_t newrequest, size_t last_size) {
7383
newrequest = (newrequest + 7) & ~7;
7484
last_size = (last_size + 7) & ~7;
7585

76-
if (oldptr != youngspace.arena_end_ptr() - last_size) {
86+
if (oldptr != youngspace.end_ptr() - last_size) {
7787
MEM_LOG(
7888
"May only reallocate last allocation. Tried to reallocate %p to %zd\n",
7989
oldptr, newrequest);

0 commit comments

Comments
 (0)