The primary aim for this refactor is to use a representation for sizeclasses that uniformly covers both large and small. This allows certain operations such as alloc_size and external_pointer to be uniformly implemented. The additional types make clear which kind of sizeclass is in use. This also tidies up the code for sizeclass based divisible by and modulus. It fixes a bug in rust_realloc that didn't correctly determine a realloc was required for large classes.
173 lines
5.0 KiB
C++
173 lines
5.0 KiB
C++
#pragma once
|
|
|
|
#include "../mem/allocconfig.h"
|
|
#include "../mem/freelist.h"
|
|
#include "../mem/metaslab.h"
|
|
#include "../mem/remoteallocator.h"
|
|
#include "../mem/sizeclasstable.h"
|
|
|
|
#include <array>
|
|
#include <atomic>
|
|
|
|
namespace snmalloc
|
|
{
|
|
/**
|
|
* Stores the remote deallocation to batch them before sending
|
|
*/
|
|
struct RemoteDeallocCache
|
|
{
|
|
std::array<freelist::Builder<false, false>, REMOTE_SLOTS> list;
|
|
|
|
/**
|
|
* The total amount of memory we are waiting for before we will dispatch
|
|
* to other allocators. Zero can mean we have not initialised the allocator
|
|
* yet. This is initialised to the 0 so that we always hit a slow path to
|
|
* start with, when we hit the slow path and need to dispatch everything, we
|
|
* can check if we are a real allocator and lazily provide a real allocator.
|
|
*/
|
|
int64_t capacity{0};
|
|
|
|
#ifndef NDEBUG
|
|
bool initialised = false;
|
|
#endif
|
|
|
|
/// Used to find the index into the array of queues for remote
|
|
/// deallocation
|
|
/// r is used for which round of sending this is.
|
|
template<size_t allocator_size>
|
|
inline size_t get_slot(size_t i, size_t r)
|
|
{
|
|
constexpr size_t initial_shift =
|
|
bits::next_pow2_bits_const(allocator_size);
|
|
// static_assert(
|
|
// initial_shift >= 8,
|
|
// "Can't embed sizeclass_t into allocator ID low bits");
|
|
SNMALLOC_ASSERT((initial_shift + (r * REMOTE_SLOT_BITS)) < 64);
|
|
return (i >> (initial_shift + (r * REMOTE_SLOT_BITS))) & REMOTE_MASK;
|
|
}
|
|
|
|
/**
|
|
* Checks if the capacity has enough to cache an entry from this
|
|
* slab. Returns true, if this does not overflow the budget.
|
|
*
|
|
* This does not require initialisation to be safely called.
|
|
*/
|
|
SNMALLOC_FAST_PATH bool reserve_space(const MetaEntry& entry)
|
|
{
|
|
auto size =
|
|
static_cast<int64_t>(sizeclass_full_to_size(entry.get_sizeclass()));
|
|
|
|
bool result = capacity > size;
|
|
if (result)
|
|
capacity -= size;
|
|
return result;
|
|
}
|
|
|
|
template<size_t allocator_size>
|
|
SNMALLOC_FAST_PATH void dealloc(
|
|
RemoteAllocator::alloc_id_t target_id,
|
|
capptr::Alloc<void> p,
|
|
const FreeListKey& key)
|
|
{
|
|
SNMALLOC_ASSERT(initialised);
|
|
auto r = p.template as_reinterpret<freelist::Object::T<>>();
|
|
|
|
list[get_slot<allocator_size>(target_id, 0)].add(r, key);
|
|
}
|
|
|
|
template<size_t allocator_size, typename SharedStateHandle>
|
|
bool post(
|
|
typename SharedStateHandle::LocalState* local_state,
|
|
RemoteAllocator::alloc_id_t id,
|
|
const FreeListKey& key)
|
|
{
|
|
SNMALLOC_ASSERT(initialised);
|
|
size_t post_round = 0;
|
|
bool sent_something = false;
|
|
auto domesticate =
|
|
[local_state](freelist::QueuePtr p) SNMALLOC_FAST_PATH_LAMBDA {
|
|
return capptr_domesticate<SharedStateHandle>(local_state, p);
|
|
};
|
|
|
|
while (true)
|
|
{
|
|
auto my_slot = get_slot<allocator_size>(id, post_round);
|
|
|
|
for (size_t i = 0; i < REMOTE_SLOTS; i++)
|
|
{
|
|
if (i == my_slot)
|
|
continue;
|
|
|
|
if (!list[i].empty())
|
|
{
|
|
auto [first, last] = list[i].extract_segment(key);
|
|
MetaEntry entry = SharedStateHandle::Pagemap::get_metaentry(
|
|
local_state, address_cast(first));
|
|
if constexpr (SharedStateHandle::Options.QueueHeadsAreTame)
|
|
{
|
|
auto domesticate_nop = [](freelist::QueuePtr p) {
|
|
return freelist::HeadPtr(p.unsafe_ptr());
|
|
};
|
|
entry.get_remote()->enqueue(first, last, key, domesticate_nop);
|
|
}
|
|
else
|
|
{
|
|
entry.get_remote()->enqueue(first, last, key, domesticate);
|
|
}
|
|
sent_something = true;
|
|
}
|
|
}
|
|
|
|
if (list[my_slot].empty())
|
|
break;
|
|
|
|
// Entries could map back onto the "resend" list,
|
|
// so take copy of the head, mark the last element,
|
|
// and clear the original list.
|
|
freelist::Iter<> resend;
|
|
list[my_slot].close(resend, key);
|
|
|
|
post_round++;
|
|
|
|
while (!resend.empty())
|
|
{
|
|
// Use the next N bits to spread out remote deallocs in our own
|
|
// slot.
|
|
auto r = resend.take(key, domesticate);
|
|
MetaEntry entry = SharedStateHandle::Pagemap::get_metaentry(
|
|
local_state, address_cast(r));
|
|
auto i = entry.get_remote()->trunc_id();
|
|
size_t slot = get_slot<allocator_size>(i, post_round);
|
|
list[slot].add(r, key);
|
|
}
|
|
}
|
|
|
|
// Reset capacity as we have empty everything
|
|
capacity = REMOTE_CACHE;
|
|
|
|
return sent_something;
|
|
}
|
|
|
|
/**
|
|
* Constructor design to allow constant init
|
|
*/
|
|
constexpr RemoteDeallocCache() = default;
|
|
|
|
/**
|
|
* Must be called before anything else to ensure actually initialised
|
|
* not just zero init.
|
|
*/
|
|
void init()
|
|
{
|
|
#ifndef NDEBUG
|
|
initialised = true;
|
|
#endif
|
|
for (auto& l : list)
|
|
{
|
|
l.init();
|
|
}
|
|
capacity = REMOTE_CACHE;
|
|
}
|
|
};
|
|
} // namespace snmalloc
|