futhark-0.22.7: rts/c/free_list.h
// Start of free_list.h.
typedef uintptr_t fl_mem;
// An entry in the free list. May be invalid, to avoid having to
// deallocate entries as soon as they are removed. There is also a
// tag, to help with memory reuse.
struct free_list_entry {
size_t size;
fl_mem mem;
const char *tag;
unsigned char valid;
};
struct free_list {
struct free_list_entry *entries; // Pointer to entries.
int capacity; // Number of entries.
int used; // Number of valid entries.
lock_t lock; // Thread safety.
};
static void free_list_init(struct free_list *l) {
l->capacity = 30; // Picked arbitrarily.
l->used = 0;
l->entries = (struct free_list_entry*) malloc(sizeof(struct free_list_entry) * l->capacity);
for (int i = 0; i < l->capacity; i++) {
l->entries[i].valid = 0;
}
create_lock(&l->lock);
}
// Remove invalid entries from the free list.
static void free_list_pack(struct free_list *l) {
lock_lock(&l->lock);
int p = 0;
for (int i = 0; i < l->capacity; i++) {
if (l->entries[i].valid) {
l->entries[p] = l->entries[i];
if (i > p) {
l->entries[i].valid = 0;
}
p++;
}
}
// Now p is the number of used elements. We don't want it to go
// less than the default capacity (although in practice it's OK as
// long as it doesn't become 1).
if (p < 30) {
p = 30;
}
l->entries = realloc(l->entries, p * sizeof(struct free_list_entry));
l->capacity = p;
lock_unlock(&l->lock);
}
static void free_list_destroy(struct free_list *l) {
assert(l->used == 0);
free(l->entries);
free_lock(&l->lock);
}
// Not part of the interface, so no locking.
static int free_list_find_invalid(struct free_list *l) {
int i;
for (i = 0; i < l->capacity; i++) {
if (!l->entries[i].valid) {
break;
}
}
return i;
}
static void free_list_insert(struct free_list *l, size_t size, fl_mem mem, const char *tag) {
lock_lock(&l->lock);
int i = free_list_find_invalid(l);
if (i == l->capacity) {
// List is full; so we have to grow it.
int new_capacity = l->capacity * 2 * sizeof(struct free_list_entry);
l->entries = realloc(l->entries, new_capacity);
for (int j = 0; j < l->capacity; j++) {
l->entries[j+l->capacity].valid = 0;
}
l->capacity *= 2;
}
// Now 'i' points to the first invalid entry.
l->entries[i].valid = 1;
l->entries[i].size = size;
l->entries[i].mem = mem;
l->entries[i].tag = tag;
l->used++;
lock_unlock(&l->lock);
}
// Determine whether this entry in the free list is acceptable for
// satisfying the request. Not public, so no locking.
static bool free_list_acceptable(size_t size, const char* tag, struct free_list_entry *entry) {
// We check not just the hard requirement (is the entry acceptable
// and big enough?) but also put a cap on how much wasted space
// (internal fragmentation) we allow. This is necessarily a
// heuristic, and a crude one.
if (!entry->valid) {
return false;
}
if (size > entry->size) {
return false;
}
// We know the block fits. Now the question is whether it is too
// big. Our policy is as follows:
//
// 1) We don't care about wasted space below 4096 bytes (to avoid
// churn in tiny allocations).
//
// 2) If the tag matches, we allow _any_ amount of wasted space.
//
// 3) Otherwise we allow up to 50% wasted space.
if (entry->size < 4096) {
return true;
}
if (entry->tag == tag) {
return true;
}
if (entry->size < size * 2) {
return true;
}
return false;
}
// Find and remove a memory block of the indicated tag, or if that
// does not exist, another memory block with exactly the desired size.
// Returns 0 on success.
static int free_list_find(struct free_list *l, size_t size, const char *tag,
size_t *size_out, fl_mem *mem_out) {
lock_lock(&l->lock);
int size_match = -1;
int i;
int ret = 1;
for (i = 0; i < l->capacity; i++) {
if (free_list_acceptable(size, tag, &l->entries[i]) &&
(size_match < 0 || l->entries[i].size < l->entries[size_match].size)) {
// If this entry is valid, has sufficient size, and is smaller than the
// best entry found so far, use this entry.
size_match = i;
}
}
if (size_match >= 0) {
l->entries[size_match].valid = 0;
*size_out = l->entries[size_match].size;
*mem_out = l->entries[size_match].mem;
l->used--;
ret = 0;
}
lock_unlock(&l->lock);
return ret;
}
// Remove the first block in the free list. Returns 0 if a block was
// removed, and nonzero if the free list was already empty.
static int free_list_first(struct free_list *l, fl_mem *mem_out) {
lock_lock(&l->lock);
int ret = 1;
for (int i = 0; i < l->capacity; i++) {
if (l->entries[i].valid) {
l->entries[i].valid = 0;
*mem_out = l->entries[i].mem;
l->used--;
ret = 0;
break;
}
}
lock_unlock(&l->lock);
return ret;
}
// End of free_list.h.