Merge pull request #53 from microsoft/new_link
Used start of a slab for link.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
release*/
|
||||
debug*/
|
||||
build*/
|
||||
CMakeFiles/
|
||||
.vscode/
|
||||
|
||||
20
difference.md
Normal file
20
difference.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Difference from published paper
|
||||
|
||||
This document outlines the changes that have diverged from the published paper
|
||||
on `snmalloc`.
|
||||
|
||||
1. Link no longer terminates the bump-free list. The paper describes a
|
||||
complex invariant for how the final element of the bump-free list can
|
||||
also be the link node.
|
||||
|
||||
We now have a much simpler invariant. The link is either 1, signifying
|
||||
the block is completely full. Or not 1, signifying it has at least one
|
||||
free element at the offset contained in link, and that contains the DLL
|
||||
node for this sizeclass.
|
||||
|
||||
The bump-free list contains additional free elements, and the remaining
|
||||
bump allocated space.
|
||||
|
||||
The value 1, is never a valid bump allocation value, as we initially
|
||||
allocate the first entry as the link, so we can use 1 as the no more bump
|
||||
space value.
|
||||
@@ -19,15 +19,12 @@ namespace snmalloc
|
||||
}
|
||||
};
|
||||
|
||||
using SlabList = DLList<SlabLink, InvalidPointer<UINTPTR_MAX>>;
|
||||
using SlabList = DLList<SlabLink>;
|
||||
|
||||
static_assert(
|
||||
sizeof(SlabLink) <= MIN_ALLOC_SIZE,
|
||||
"Need to be able to pack a SlabLink into any free small alloc");
|
||||
|
||||
static constexpr uint16_t SLABLINK_INDEX =
|
||||
static_cast<uint16_t>(SLAB_SIZE - sizeof(SlabLink));
|
||||
|
||||
// The Metaslab represent the status of a single slab.
|
||||
// This can be either a short or a standard slab.
|
||||
class Metaslab
|
||||
@@ -42,16 +39,11 @@ namespace snmalloc
|
||||
// of where we have allocated up to in this slab. Otherwise,
|
||||
// it represents the location of the first block in the free
|
||||
// list. The free list is chained through deallocated blocks.
|
||||
// It either terminates with a bump ptr, or if all the space is in
|
||||
// the free list, then the last block will be also referenced by
|
||||
// link.
|
||||
// Note that, in the case that this is the first block in the size
|
||||
// class list, where all the unused memory is in the free list,
|
||||
// then the last block can both be interpreted as a final bump
|
||||
// pointer entry, and the first entry in the doubly linked list.
|
||||
// The terminal value in the free list, and the terminal value in
|
||||
// the SlabLink previous field will alias. The SlabLink uses ~0 for
|
||||
// its terminal value to be a valid terminal bump ptr.
|
||||
// It is terminated with a bump ptr.
|
||||
//
|
||||
// Note that, the first entry in a slab is never bump allocated
|
||||
// but is used for the link. This means that 1 represents the fully
|
||||
// bump allocated slab.
|
||||
Mod<SLAB_SIZE, uint16_t> head;
|
||||
// When a slab has free space it will be on the has space list for
|
||||
// that size class. We use an empty block in this slab to be the
|
||||
@@ -84,13 +76,14 @@ namespace snmalloc
|
||||
|
||||
bool is_full()
|
||||
{
|
||||
return (head & 2) != 0;
|
||||
return link == 1;
|
||||
}
|
||||
|
||||
void set_full()
|
||||
{
|
||||
assert(head == 1);
|
||||
head = static_cast<uint16_t>(~0);
|
||||
assert(link != 1);
|
||||
link = 1;
|
||||
}
|
||||
|
||||
SlabLink* get_link(Slab* slab)
|
||||
@@ -101,12 +94,11 @@ namespace snmalloc
|
||||
bool valid_head(bool is_short)
|
||||
{
|
||||
size_t size = sizeclass_to_size(sizeclass);
|
||||
size_t offset = get_slab_offset(sizeclass, is_short);
|
||||
size_t slab_start = get_initial_link(sizeclass, is_short);
|
||||
size_t all_high_bits = ~static_cast<size_t>(1);
|
||||
|
||||
size_t head_start =
|
||||
remove_cache_friendly_offset(head & all_high_bits, sizeclass);
|
||||
size_t slab_start = offset & all_high_bits;
|
||||
|
||||
return ((head_start - slab_start) % size) == 0;
|
||||
}
|
||||
@@ -115,7 +107,7 @@ namespace snmalloc
|
||||
{
|
||||
#if !defined(NDEBUG) && !defined(SNMALLOC_CHEAP_CHECKS)
|
||||
size_t size = sizeclass_to_size(sizeclass);
|
||||
size_t offset = get_slab_offset(sizeclass, is_short) - 1;
|
||||
size_t offset = get_initial_link(sizeclass, is_short);
|
||||
|
||||
size_t accounted_for = used * size + offset;
|
||||
|
||||
@@ -141,28 +133,31 @@ namespace snmalloc
|
||||
// Account for free elements in free list
|
||||
accounted_for += size;
|
||||
assert(SLAB_SIZE >= accounted_for);
|
||||
// We are not guaranteed to hit a bump ptr unless
|
||||
// we are the top element on the size class, so treat as
|
||||
// a list segment.
|
||||
if (curr == link)
|
||||
break;
|
||||
// We should never reach the link node in the free list.
|
||||
assert(curr != link);
|
||||
|
||||
// Iterate bump/free list segment
|
||||
curr = *reinterpret_cast<uint16_t*>(pointer_offset(slab, curr));
|
||||
}
|
||||
|
||||
// Check we terminated traversal on a correctly aligned block
|
||||
uint16_t start = remove_cache_friendly_offset(curr & ~1, sizeclass);
|
||||
assert((start - offset) % size == 0);
|
||||
|
||||
if (curr != link)
|
||||
if (curr != 1)
|
||||
{
|
||||
// The link should be at the special end location as we
|
||||
// haven't completely filled this block at any point.
|
||||
assert(link == SLABLINK_INDEX);
|
||||
// Check we terminated traversal on a correctly aligned block
|
||||
uint16_t start = remove_cache_friendly_offset(curr & ~1, sizeclass);
|
||||
assert((start - offset) % size == 0);
|
||||
|
||||
// Account for to be bump allocated space
|
||||
accounted_for += SLAB_SIZE - (curr - 1);
|
||||
|
||||
// The link should be the first allocation as we
|
||||
// haven't completely filled this block at any point.
|
||||
assert(link == get_initial_link(sizeclass, is_short));
|
||||
}
|
||||
|
||||
assert(!is_full());
|
||||
// Add the link node.
|
||||
accounted_for += size;
|
||||
|
||||
// All space accounted for
|
||||
assert(SLAB_SIZE == accounted_for);
|
||||
#else
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
namespace snmalloc
|
||||
{
|
||||
constexpr static uint16_t get_slab_offset(uint8_t sc, bool is_short);
|
||||
constexpr static uint16_t get_initial_bumpptr(uint8_t sc, bool is_short);
|
||||
constexpr static uint16_t get_initial_link(uint8_t sc, bool is_short);
|
||||
constexpr static size_t sizeclass_to_size(uint8_t sizeclass);
|
||||
constexpr static size_t sizeclass_to_cache_friendly_mask(uint8_t sizeclass);
|
||||
constexpr static size_t sizeclass_to_inverse_cache_friendly_mask(uint8_t sc);
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace snmalloc
|
||||
ModArray<NUM_SIZECLASSES, size_t> inverse_cache_friendly_mask;
|
||||
ModArray<NUM_SMALL_CLASSES, uint16_t> bump_ptr_start;
|
||||
ModArray<NUM_SMALL_CLASSES, uint16_t> short_bump_ptr_start;
|
||||
ModArray<NUM_SMALL_CLASSES, uint16_t> count_per_slab;
|
||||
ModArray<NUM_SMALL_CLASSES, uint16_t> initial_link_ptr;
|
||||
ModArray<NUM_SMALL_CLASSES, uint16_t> short_initial_link_ptr;
|
||||
ModArray<NUM_MEDIUM_CLASSES, uint16_t> medium_slab_slots;
|
||||
|
||||
constexpr SizeClassTable()
|
||||
@@ -21,7 +22,8 @@ namespace snmalloc
|
||||
inverse_cache_friendly_mask(),
|
||||
bump_ptr_start(),
|
||||
short_bump_ptr_start(),
|
||||
count_per_slab(),
|
||||
initial_link_ptr(),
|
||||
short_initial_link_ptr(),
|
||||
medium_slab_slots()
|
||||
{
|
||||
for (uint8_t sizeclass = 0; sizeclass < NUM_SIZECLASSES; sizeclass++)
|
||||
@@ -40,10 +42,25 @@ namespace snmalloc
|
||||
|
||||
for (uint8_t i = 0; i < NUM_SMALL_CLASSES; i++)
|
||||
{
|
||||
// We align to the end of the block to remove special cases for the
|
||||
// short block. Calculate remainders
|
||||
size_t short_correction = short_slab_size % size[i];
|
||||
size_t correction = SLAB_SIZE % size[i];
|
||||
|
||||
// First element in the block is the link
|
||||
initial_link_ptr[i] = static_cast<uint16_t>(correction);
|
||||
short_initial_link_ptr[i] =
|
||||
static_cast<uint16_t>(header_size + short_correction);
|
||||
|
||||
// Move to object after link.
|
||||
auto short_after_link = short_initial_link_ptr[i] + size[i];
|
||||
size_t after_link = initial_link_ptr[i] + size[i];
|
||||
|
||||
// Bump ptr has bottom bit set.
|
||||
// In case we only have one object on this slab check for wrap around.
|
||||
short_bump_ptr_start[i] =
|
||||
static_cast<uint16_t>(1 + (short_slab_size % size[i]) + header_size);
|
||||
bump_ptr_start[i] = static_cast<uint16_t>(1 + (SLAB_SIZE % size[i]));
|
||||
count_per_slab[i] = static_cast<uint16_t>(SLAB_SIZE / size[i]);
|
||||
static_cast<uint16_t>((short_after_link + 1) % SLAB_SIZE);
|
||||
bump_ptr_start[i] = static_cast<uint16_t>((after_link + 1) % SLAB_SIZE);
|
||||
}
|
||||
|
||||
for (uint8_t i = NUM_SMALL_CLASSES; i < NUM_SIZECLASSES; i++)
|
||||
@@ -56,7 +73,8 @@ namespace snmalloc
|
||||
|
||||
static constexpr SizeClassTable sizeclass_metadata = SizeClassTable();
|
||||
|
||||
static inline constexpr uint16_t get_slab_offset(uint8_t sc, bool is_short)
|
||||
static inline constexpr uint16_t
|
||||
get_initial_bumpptr(uint8_t sc, bool is_short)
|
||||
{
|
||||
if (is_short)
|
||||
return sizeclass_metadata.short_bump_ptr_start[sc];
|
||||
@@ -64,6 +82,14 @@ namespace snmalloc
|
||||
return sizeclass_metadata.bump_ptr_start[sc];
|
||||
}
|
||||
|
||||
static inline constexpr uint16_t get_initial_link(uint8_t sc, bool is_short)
|
||||
{
|
||||
if (is_short)
|
||||
return sizeclass_metadata.short_initial_link_ptr[sc];
|
||||
|
||||
return sizeclass_metadata.initial_link_ptr[sc];
|
||||
}
|
||||
|
||||
constexpr static inline size_t sizeclass_to_size(uint8_t sizeclass)
|
||||
{
|
||||
return sizeclass_metadata.size[sizeclass];
|
||||
@@ -81,11 +107,6 @@ namespace snmalloc
|
||||
return sizeclass_metadata.inverse_cache_friendly_mask[sizeclass];
|
||||
}
|
||||
|
||||
constexpr static inline size_t sizeclass_to_count(uint8_t sizeclass)
|
||||
{
|
||||
return sizeclass_metadata.count_per_slab[sizeclass];
|
||||
}
|
||||
|
||||
constexpr static inline uint16_t medium_slab_free(uint8_t sizeclass)
|
||||
{
|
||||
return sizeclass_metadata
|
||||
|
||||
@@ -58,19 +58,20 @@ namespace snmalloc
|
||||
}
|
||||
else
|
||||
{
|
||||
// This slab is being bump allocated.
|
||||
p = pointer_offset(this, head - 1);
|
||||
meta.head = (head + static_cast<uint16_t>(rsize)) & (SLAB_SIZE - 1);
|
||||
if (meta.head == 1)
|
||||
{
|
||||
p = pointer_offset(this, meta.link);
|
||||
sc->pop();
|
||||
meta.set_full();
|
||||
}
|
||||
else
|
||||
{
|
||||
// This slab is being bump allocated.
|
||||
p = pointer_offset(this, head - 1);
|
||||
meta.head = (head + static_cast<uint16_t>(rsize)) & (SLAB_SIZE - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If we're full, we're no longer the current slab for this sizeclass
|
||||
if (meta.is_full())
|
||||
sc->pop();
|
||||
|
||||
meta.debug_slab_invariant(is_short(), this);
|
||||
|
||||
if constexpr (zero_mem == YesZero)
|
||||
@@ -110,8 +111,7 @@ namespace snmalloc
|
||||
{
|
||||
// Update the head and the sizeclass link.
|
||||
uint16_t index = pointer_to_index(p);
|
||||
meta.head = index;
|
||||
assert(meta.valid_head(is_short()));
|
||||
assert(meta.head == 1);
|
||||
meta.link = index;
|
||||
|
||||
// Push on the list of slabs for this sizeclass.
|
||||
|
||||
@@ -159,9 +159,9 @@ namespace snmalloc
|
||||
if ((used & 1) == 1)
|
||||
return alloc_slab(sizeclass, memory_provider);
|
||||
|
||||
meta[0].head = get_slab_offset(sizeclass, true);
|
||||
meta[0].head = get_initial_bumpptr(sizeclass, true);
|
||||
meta[0].sizeclass = sizeclass;
|
||||
meta[0].link = SLABLINK_INDEX;
|
||||
meta[0].link = get_initial_link(sizeclass, true);
|
||||
|
||||
if constexpr (decommit_strategy == DecommitAll)
|
||||
{
|
||||
@@ -182,9 +182,9 @@ namespace snmalloc
|
||||
|
||||
uint8_t n = meta[h].next;
|
||||
|
||||
meta[h].head = get_slab_offset(sizeclass, false);
|
||||
meta[h].head = get_initial_bumpptr(sizeclass, false);
|
||||
meta[h].sizeclass = sizeclass;
|
||||
meta[h].link = SLABLINK_INDEX;
|
||||
meta[h].link = get_initial_link(sizeclass, false);
|
||||
|
||||
head = h + n + 1;
|
||||
used += 2;
|
||||
|
||||
Reference in New Issue
Block a user