// 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.