Skip to content

Commit 8d2d271

Browse files
committed
runtime: add a templated "Object Allocator"
This class takes the size it is allocating as a template parameter, which should let us speedup allocation by removing a divide in the fast path
1 parent 4ac2555 commit 8d2d271

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed

runtime/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ if (ALASKA_ENABLE_TESTING)
213213
test/threadcache_test.cpp
214214
test/hugeobject_test.cpp
215215
test/sizedalloc_test.cpp
216+
test/object_allocator_test.cpp
216217
)
217218

218219
target_link_libraries(
+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* This file is part of the Alaska Handle-Based Memory Management System
3+
*
4+
* Copyright (c) 2024, Nick Wanninger <ncw@u.northwestern.edu>
5+
* Copyright (c) 2024, The Constellation Project
6+
* All rights reserved.
7+
*
8+
* This is free software. You are permitted to use, redistribute,
9+
* and modify it as specified in the file "LICENSE".
10+
*/
11+
12+
#pragma once
13+
14+
#include <stdlib.h>
15+
#include <sys/cdefs.h>
16+
#include <alaska/ShardedFreeList.hpp>
17+
#include <alaska/HeapPage.hpp>
18+
#include <alaska/track.hpp>
19+
20+
namespace alaska {
21+
22+
template <typename T>
23+
class ObjectAllocator {
24+
public:
25+
ObjectAllocator(void) = default;
26+
ObjectAllocator(void *objects, long object_count) { configure(objects, object_count); }
27+
28+
T *alloc(void);
29+
30+
// Free objects. NOTE: no checks are made that this pointer is valid!
31+
inline void release_local(void *ptr) {
32+
free_list.free_local(ptr);
33+
alaska_track_free(ptr, 0);
34+
}
35+
inline void release_remote(void *ptr) {
36+
free_list.free_remote(ptr);
37+
alaska_track_free(ptr, 0);
38+
}
39+
40+
void configure(void *objects, long object_count);
41+
42+
inline bool some_available(void) {
43+
return free_list.has_local_free() || free_list.has_remote_free() ||
44+
(bump_next != objects_end);
45+
}
46+
47+
48+
inline long num_free(void) const {
49+
return num_free_in_free_list() + num_free_in_bump_allocator();
50+
}
51+
52+
inline long num_free_in_free_list(void) const { return free_list.num_free(); }
53+
54+
inline long num_free_in_bump_allocator(void) const {
55+
return (((uintptr_t)objects_end - (uintptr_t)bump_next) / sizeof(T));
56+
}
57+
58+
59+
// return the index of the object
60+
inline long object_index(void *ob) {
61+
return ((uintptr_t)ob - (uintptr_t)this->objects_start) / this->object_size;
62+
}
63+
64+
long extend(long count);
65+
66+
private:
67+
T *alloc_slow(void);
68+
69+
void *objects_start; // The start of the object memory
70+
void *objects_end; // The end of the object memory (exclusive)
71+
void *bump_next; // The next object to be bump allocated.
72+
alaska::ShardedFreeList free_list; // A free list for tracking releases
73+
};
74+
75+
76+
77+
78+
template <typename T>
79+
__attribute__((always_inline)) inline T *ObjectAllocator<T>::alloc(void) {
80+
void *object = free_list.pop();
81+
82+
if (unlikely(object == nullptr)) {
83+
return alloc_slow();
84+
}
85+
86+
alaska_track_malloc_size(object, sizeof(T), object_size, 0);
87+
88+
return (T*)object;
89+
}
90+
91+
92+
template <typename T>
93+
__attribute__((noinline)) inline T *ObjectAllocator<T>::alloc_slow(void) {
94+
long extended_count = extend(128);
95+
96+
// 1. If we managed to extend the list, return one of the blocks from it.
97+
if (extended_count > 0) {
98+
// Fall back into the alloc function to do the heavy lifting of actually allocating
99+
// one of the blocks we just extended the list with.
100+
return alloc();
101+
}
102+
103+
// 2. If the list was not extended, try swapping the remote_free list and the local_free list.
104+
// This is a little tricky because we need to worry about atomics here.
105+
free_list.swap();
106+
107+
// If local-free is still null, return null
108+
if (not free_list.has_local_free()) return nullptr;
109+
110+
// Otherwise, fall back to alloc
111+
return alloc();
112+
}
113+
114+
115+
template <typename T>
116+
inline long ObjectAllocator<T>::extend(long count) {
117+
long e = 0;
118+
for (; e < count && bump_next != objects_end; e++) {
119+
free_list.free_local(bump_next);
120+
bump_next = (void *)((uintptr_t)bump_next + sizeof(T));
121+
}
122+
return e;
123+
}
124+
125+
126+
template <typename T>
127+
inline void ObjectAllocator<T>::configure(void *objects, long object_count) {
128+
this->objects_start = this->bump_next = objects;
129+
this->objects_end = (void *)((uintptr_t)objects + (object_count * sizeof(T)));
130+
// Re-construct the free list just in case
131+
this->free_list = ShardedFreeList();
132+
}
133+
134+
}; // namespace alaska
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// This file tests alaska::HugeObjectAllocator
2+
3+
#include <alaska/ObjectAllocator.hpp>
4+
#include "alaska/utils.h"
5+
#include <gtest/gtest.h>
6+
#include "alaska/Logger.hpp"
7+
8+
9+
10+
class ObjectAllocatorTest : public ::testing::Test {
11+
public:
12+
void SetUp() override {
13+
// Make it so we only get warnings
14+
alaska::set_log_level(LOG_WARN);
15+
16+
17+
18+
buffer = calloc(object_count, object_size);
19+
salloc.configure(buffer, object_count);
20+
}
21+
void TearDown() override { free(buffer); }
22+
23+
24+
long object_size = 16;
25+
long object_count = 512;
26+
void *buffer;
27+
alaska::ObjectAllocator<uint64_t> salloc;
28+
};
29+
30+
31+
32+
33+
TEST_F(ObjectAllocatorTest, Sanity) {
34+
// Out of the gate, the number of free objects must be equal to the number of objects
35+
ASSERT_EQ(salloc.num_free(), object_count);
36+
}
37+
38+
TEST_F(ObjectAllocatorTest, Extend) {
39+
ASSERT_EQ(salloc.num_free_in_bump_allocator(), object_count);
40+
long count = salloc.extend(1);
41+
42+
ASSERT_EQ(salloc.num_free_in_free_list(), 1);
43+
// out of the gate, the number of free objects must be equal to the number of objects
44+
ASSERT_EQ(salloc.num_free_in_bump_allocator(), object_count - count);
45+
}
46+
47+
48+
TEST_F(ObjectAllocatorTest, Allocate) {
49+
auto *b = salloc.alloc();
50+
ASSERT_NE(b, nullptr);
51+
}
52+
53+
54+
TEST_F(ObjectAllocatorTest, AllocateNumFree) {
55+
auto initial_nfree = salloc.num_free();
56+
salloc.alloc();
57+
ASSERT_EQ(salloc.num_free(), initial_nfree - 1);
58+
}
59+
60+
61+
TEST_F(ObjectAllocatorTest, ReleaseLocalNumFree) {
62+
auto *b = salloc.alloc();
63+
ASSERT_NE(b, nullptr);
64+
ASSERT_EQ(salloc.num_free(), object_count - 1);
65+
66+
salloc.release_local(b);
67+
ASSERT_EQ(salloc.num_free(), object_count);
68+
}
69+
70+
TEST_F(ObjectAllocatorTest, ReleaseRemoteNumFree) {
71+
auto *b = salloc.alloc();
72+
ASSERT_NE(b, nullptr);
73+
ASSERT_EQ(salloc.num_free(), object_count - 1);
74+
75+
salloc.release_local(b);
76+
ASSERT_EQ(salloc.num_free(), object_count);
77+
}

0 commit comments

Comments
 (0)