// static best-fit allocator utilizing std::multimap.
// @ingroup experimental
#include <algorithm>
#include <cassert>
#include <map>
// @internal
// memory block header size in words.
#define HEADER_SIZE static_cast<word_type>(1)
// @internal
// extract block size from block header.
#define BLOCK_SIZE(location) \
(heap[(location) - HEADER_SIZE])
// heap types/members.
typedef unsigned int word_type;
constexpr word_type heap_size = 1000000;
word_type heap[heap_size];
// key: block size; data: block location.
std::multimap<word_type, word_type> free_list;
void heap_initialize()
{
word_type next = HEADER_SIZE;
BLOCK_SIZE(next) = heap_size;
extern void release(word_type);
release(next);
}
void release(word_type p)
{
if (p != 0)
free_list.insert(std::make_pair(BLOCK_SIZE(p), p));
}
word_type acquire(word_type request)
{
if (request == 0)
return 0;
// threshold minimum is (header + 1*data word).
word_type threshold = 8;
request = (request + HEADER_SIZE + (threshold-1))
/ threshold * threshold;
// locate best-fit.
auto best_iter = free_list.lower_bound(request);
// no suitable block found.
if (best_iter == free_list.end())
throw std::bad_alloc();
// extract index; update free list.
word_type best = best_iter->second;
free_list.erase(best_iter);
// split block.
if (BLOCK_SIZE(best) > request + threshold) {
word_type next = best + request;
BLOCK_SIZE(next) = BLOCK_SIZE(best) - request;
BLOCK_SIZE(best) = request;
release(next);
}
return best;
}
// TESTING
#include <iostream>
#include <sstream>
#include <memory>
#include <random>
#include <chrono>
#include <thread>
#include <mutex>
#include <vector>
#include <cstring>
// extract pointer from index.
// @note since heap memory is static we don't need to worry
// about pointer invalidation.
#define BLOCK_PTR(location) \
(& heap[(location)])
void thread_out(int id, const char* stage)
{
std::stringstream ss;
ss << "pid(" << id << "): " << stage << '\n';
std::cout << ss.str();
}
void spastic_alloc(int id, int count, int size, long* total_allocated)
{
// hopefully this is obnoxious enough to crash if something is wrong.
std::default_random_engine random(std::random_device().operator()());
auto garbage = std::make_unique<word_type[]>(size);
std::generate(garbage.get(), garbage.get() + size, random);
// this randomization is an attempt to increase the likelyhood
// acquire and release are interleaved.
int msectick = std::uniform_int_distribution<>(0, 250)(random);
std::this_thread::sleep_for(std::chrono::milliseconds(msectick));
int allocations = std::uniform_int_distribution<>(count/8, count)(random);
// first: address; second: size.
std::vector<std::pair<word_type, word_type>> result(allocations);
static std::mutex mutex;
thread_out(id, "acquire");
for ( auto& p : result ) {
p.second = std::uniform_int_distribution<>(1, size)(random);
*total_allocated += p.second;
// the allocator is not thread safe so we hold the lock while
// acquire is active.
{
std::lock_guard<std::mutex> lock(mutex);
p.first = acquire(p.second);
}
std::copy(garbage.get(), garbage.get() + p.second,
BLOCK_PTR(p.first));
}
std::shuffle(result.begin(), result.end(), random);
thread_out(id, "release");
for ( auto p : result ) {
assert(std::equal(garbage.get(), garbage.get() + p.second,
BLOCK_PTR(p.first)));
std::memset(BLOCK_PTR(p.first), 0xA5, p.second * sizeof(word_type));
// same deal here.
std::lock_guard<std::mutex> lock(mutex);
release(p.first);
}
}
int main(int argc, char* argv[])
{
heap_initialize();
int thread_count = 4;
std::vector<std::thread> threads;
long total_allocated = 0;
for ( int i = 0; i < thread_count; i++ )
threads.emplace_back(spastic_alloc, i, 500, 1024, &total_allocated);
for ( auto& thread : threads )
thread.join();
std::cout << "Total words allocated: " << total_allocated << '\n';
std::cout << "Free block count: " << free_list.size() << '\n';
return 0;
}