Skip to content

Commit 3b10726

Browse files
committedSep 16, 2024··
runtime: add compaction
1 parent c3357e4 commit 3b10726

File tree

8 files changed

+197
-38
lines changed

8 files changed

+197
-38
lines changed
 

‎runtime/core/Heap.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,22 @@ namespace alaska {
238238
}
239239

240240

241+
long Heap::compact_sizedpages(void) {
242+
size_t bytes_saved = 0;
243+
long c = 0;
244+
for (auto &mag : size_classes) {
245+
mag.foreach ([&](SizedPage *sp) {
246+
long moved = sp->compact();
247+
c += moved;
248+
bytes_saved += moved * sp->get_object_size();
249+
return true;
250+
});
251+
}
252+
printf("Heap compaction moved %ld objects, recovering %zu bytes\n", c, bytes_saved);
253+
return c;
254+
}
255+
256+
241257
long Heap::jumble(void) {
242258
long c = 0;
243259
for (auto &mag : size_classes) {

‎runtime/core/SizedPage.cpp

+155-3
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,167 @@ namespace alaska {
9090
}
9191

9292

93-
void SizedPage::compact(void) {}
93+
// The goal of this function is to take a fragmented heap, and
94+
// apply a simple two-finger compaction algorithm. We start with
95+
// a heap that looks like this (# is allocated, _ is free)
96+
// [###_##_#_#__#_#_#__#]
97+
// and have pointers to the start and end like this:
98+
// [###_##_#_#__#_#_#__#]
99+
// ^ ^
100+
// We iterate until those pointers are the same. If the left
101+
// pointer points to an allocated object, we increment it. If the
102+
// right pointer points to a free object (or a pinned), it is
103+
// decremented. If neither pointer changes, the left points to a
104+
// free object and the right to an allocated one, we swap their
105+
// locations then inc right and dec left.
106+
//
107+
// When this process is done you should have a heap that looks
108+
// like this:
109+
// [###########_________]
110+
// The last step in this process is to "reset" the free list to be
111+
// empty and for "expansion" to begin at the end of the allocated
112+
// objects. Then, if deemed beneficial, you can use MADV_DONTNEED
113+
// to free memory back to the kernel.
114+
//
115+
// This function then returns how many objects it moved
116+
long SizedPage::compact(void) {
117+
// If there's nothing to do, early return.
118+
// TODO: threshold this.
119+
if (live_objects == capacity) return 0;
120+
121+
// The first step is to clear the free list so that we don't have any
122+
// corruption problems. Later we will update it to point at the end of
123+
// the allocated objects.
124+
allocator.reset_free_list();
125+
126+
// The return value - how many objects this function has moved.
127+
long moved_objects = 0;
128+
129+
// A pointer which tracks the last object in the heap. We have this
130+
// in the event of a pinned object, P:
131+
// [###########_____P___]
132+
// ^ last_object points here.
133+
// If this is not null, whenever we swap an object such that the right
134+
// pointer points to a free slot, we add that location to the local free
135+
// list of the allocator.
136+
// NOTE: this actually is a pointer to the header, not the object.
137+
Header *last_object = nullptr;
138+
139+
Header *left = headers; // the first object
140+
Header *right = headers + capacity - 1;
141+
142+
143+
auto dump = [&]() {
144+
if (capacity > 32) return;
145+
printf("[");
146+
for (int i = 0; i < capacity; i++) {
147+
auto *cur = &headers[i];
148+
149+
if (cur->is_free()) {
150+
printf("_");
151+
} else {
152+
printf("#");
153+
}
154+
}
155+
printf("]\n");
156+
157+
printf(" ");
158+
for (int i = 0; i < capacity; i++) {
159+
auto *cur = &headers[i];
160+
if (cur == left or cur == right) {
161+
printf("^");
162+
} else if (cur == last_object) {
163+
printf("X");
164+
} else {
165+
printf(" ");
166+
}
167+
}
168+
printf("\n");
169+
};
170+
171+
172+
while (right > left) {
173+
// if left points to an allocated object, we can't do anything so
174+
// walk it forward (towards the right)
175+
if (not left->is_free()) {
176+
left++;
177+
continue;
178+
}
179+
180+
// if the right points to a free slot, we can't do anything so
181+
// similarly walk it forward (toward the left)
182+
if (right->is_free()) {
183+
// but, if the last object is set, we need to add this to the
184+
// free list because it constitutes a gap in the heap which
185+
// would not be allocated in the future by the
186+
if (last_object != nullptr) {
187+
void *free_slot = ind_to_object(header_to_ind(right));
188+
allocator.release_local(free_slot);
189+
}
190+
191+
right--;
192+
continue;
193+
}
194+
195+
auto handle_mapping = right->get_mapping();
196+
// At this point, we have the setup we want: the left pointer
197+
// points to a free slot where the object pointed to by the
198+
// right pointer can be moved. The one situation that could
199+
// block us from doing this is if the object we want to move is
200+
// pinned. If it is we maybe update `last_object` and tick
201+
// the right pointer.
202+
if (handle_mapping->is_pinned()) {
203+
if (last_object == nullptr) last_object = right;
204+
right--;
205+
continue;
206+
}
207+
208+
209+
// Now, we are free to apply the compaction!
210+
void *object_to_move = ind_to_object(header_to_ind(right));
211+
void *free_slot = ind_to_object(header_to_ind(left));
212+
213+
// Move the memory of the object to the free slot
214+
memmove(free_slot, object_to_move, object_size);
215+
// And poison the old location.
216+
memset(object_to_move, 0xF0, object_size);
217+
218+
// Update the mapping so the handle points to the right location.
219+
handle_mapping->set_pointer(free_slot);
220+
// make sure the headers make sense
221+
*left = *right;
222+
dump();
223+
224+
ALASKA_ASSERT(left->get_mapping() == right->get_mapping(), ".");
225+
right->set_mapping(0);
226+
right->size_slack = 0;
227+
moved_objects++;
228+
229+
// Note that we don't ight along here. We deal with that on the
230+
// next iteration of the loop to handle edge cases.
231+
// left++;
232+
}
233+
234+
dump();
235+
// If we were lucky, and no pinned object were found, we need to
236+
// point last_object to the end of the heap, which at this point
237+
// is `right`
238+
if (last_object == nullptr) last_object = right;
239+
240+
// Reset the bump pointer of the free list to right after the
241+
// last object in the heap.
242+
void *after_heap = ind_to_object(header_to_ind(last_object + 1));
243+
allocator.reset_bump_allocator(after_heap);
244+
245+
246+
return moved_objects;
247+
}
94248

95249

96250

97251
void SizedPage::validate(void) {}
98252

99253
long SizedPage::jumble(void) {
100-
// printf("Jumble sized page\n");
101-
102254
char buf[this->object_size]; // BAD
103255

104256
// Simple two finger walk to swap every allocation

‎runtime/include/alaska/Heap.hpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ namespace alaska {
152152
// Dump the state of the global heap to some file stream.
153153
void dump(FILE *stream);
154154

155+
// Run a compaction on sized pages.
156+
long compact_sizedpages(void);
157+
155158
long jumble();
156159

157160
private:
@@ -180,7 +183,7 @@ namespace alaska {
180183

181184
template <typename T, typename Fn>
182185
T *Heap::find_or_alloc_page(
183-
alaska::Magazine<T> &mag, ThreadCache *owner, size_t avail_requirement, Fn &&init_fn) {
186+
alaska::Magazine<T> &mag, ThreadCache *owner, size_t avail_requirement, Fn &&INIt_fn) {
184187
ALASKA_SANITY(
185188
this->lock.is_locked(), "The lock must be held before calling find_or_alloc_page");
186189
if (mag.size() != 0) {

‎runtime/include/alaska/ShardedFreeList.hpp

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ namespace alaska {
4040
void free_local(void *);
4141
void free_remote(void *);
4242

43+
// Wipe out the free lists.
44+
void reset() {
45+
local_free = remote_free = NULL;
46+
num_local_free = num_remote_free = 0;
47+
}
48+
4349
private:
4450
// A simple `Block` structure to give us nicer linked-list style access
4551
struct Block {
@@ -113,4 +119,6 @@ namespace alaska {
113119
atomic_inc(num_remote_free, 1);
114120
}
115121

122+
123+
116124
} // namespace alaska

‎runtime/include/alaska/SizedAllocator.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ namespace alaska {
6969

7070
long extend(long count);
7171

72+
73+
void reset_free_list(void) { free_list.reset(); }
74+
void reset_bump_allocator(void *next) { bump_next = next; }
75+
7276
private:
7377
void *alloc_slow(void);
7478

‎runtime/include/alaska/SizedPage.hpp

+6-4
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ namespace alaska {
3737

3838
void set_size_class(int cls);
3939
int get_size_class(void) const { return size_class; }
40+
size_t get_object_size(void) const { return object_size; }
4041

4142

42-
void compact(void);
43-
43+
// Compact the page.
44+
long compact(void);
4445
// Run through the page and validate as much info as possible w/ asserts.
4546
void validate(void);
46-
47+
// Move every object somewhere else in the page (for testing)
4748
long jumble(void);
4849

4950
private:
@@ -52,7 +53,8 @@ namespace alaska {
5253
uint32_t size_slack : ALASKA_SIZE_BITS;
5354

5455
inline void set_mapping(alaska::Mapping *m) { _mapping = (uint64_t)m / 8; }
55-
inline auto get_mapping(void) { return (alaska::Mapping *)(uint64_t)(_mapping * 8); }
56+
inline auto get_mapping(void) const { return (alaska::Mapping *)(uint64_t)(_mapping * 8); }
57+
inline bool is_free(void) const { return get_mapping() == NULL; }
5658
};
5759

5860
long header_to_ind(Header *h);

‎runtime/rt/barrier.cpp

+3-30
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,8 @@ static void record_handle(void* possible_handle, bool marked) {
180180

181181
if (m->is_free()) return;
182182

183-
if (marked) printf("pinned %p\n", possible_handle);
183+
// if (marked) printf("pinned %p\n", possible_handle);
184184
m->set_pinned(marked);
185-
// alaska::service::commit_lock_status(m, marked);
186185
}
187186

188187

@@ -195,32 +194,6 @@ static bool might_be_handle(void* possible_handle) {
195194

196195
static ck::mutex dump_lock;
197196

198-
// static Transition most_recent_transition(void) {
199-
// unw_cursor_t cursor;
200-
// unw_context_t uc;
201-
// unw_word_t pc;
202-
//
203-
// unw_getcontext(&uc);
204-
// unw_init_local(&cursor, &uc);
205-
// while (1) {
206-
// int res = unw_step(&cursor);
207-
// if (res == 0) {
208-
// break;
209-
// }
210-
// if (res < 0) {
211-
// printf("unknown libunwind error! %d\n", res);
212-
// abort();
213-
// }
214-
// unw_get_reg(&cursor, UNW_REG_IP, &pc);
215-
//
216-
// if (pin_map.contains(pc)) return Transition::Managed;
217-
// if (block_rets.contains(pc)) return Transition::Unmanaged;
218-
// }
219-
//
220-
// return Transition::Managed;
221-
// }
222-
223-
224197
static bool in_might_block_function(uintptr_t start_addr) {
225198
void* buffer[512];
226199

@@ -419,8 +392,8 @@ void alaska::barrier::begin(void) {
419392
(void)retries;
420393
(void)signals_sent;
421394
// printf("%10f ", (end - start) / 1000.0 / 1000.0 / 1000.0);
422-
dump_thread_states();
423-
printf(" retries = %d, signals = %d\n", retries, signals_sent);
395+
// dump_thread_states();
396+
// printf(" retries = %d, signals = %d\n", retries, signals_sent);
424397
}
425398

426399

‎tools/build_yukon.sh

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ cmake ../ \
1717
-DALASKA_CORE_ONLY=ON \
1818
-DALASKA_YUKON=ON \
1919
-DALASKA_SIZE_BITS=32 \
20+
-DCMAKE_BUILD_TYPE=Release \
2021
-DCMAKE_SYSROOT=$ROOT/sysroot
2122

2223
make -j

0 commit comments

Comments
 (0)
Please sign in to comment.