Files
snmalloc/src/mem/remotecache.h
Matthew Parkinson 3d403aef7f Refactor use of sizeclasses (#415)
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.
2021-11-10 16:35:44 +00:00

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