Initial commit of snmalloc
History squashed from internal development. Internal history has commit hash: e27a0e485c44a5003a802de2661ce3b21e120316
This commit is contained in:
52
src/test/func/fixed_region/fixed_region.cc
Normal file
52
src/test/func/fixed_region/fixed_region.cc
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
#define OPEN_ENCLAVE
|
||||
#define OPEN_ENCLAVE_SIMULATION
|
||||
#define USE_RESERVE_MULTIPLE 1
|
||||
#include <iostream>
|
||||
#include <snmalloc.h>
|
||||
|
||||
void* oe_base;
|
||||
void* oe_end;
|
||||
extern "C" const void* __oe_get_heap_base()
|
||||
{
|
||||
return oe_base;
|
||||
}
|
||||
|
||||
extern "C" const void* __oe_get_heap_end()
|
||||
{
|
||||
return oe_end;
|
||||
}
|
||||
|
||||
extern "C" void* oe_memset(void* p, int c, size_t size)
|
||||
{
|
||||
return memset(p, c, size);
|
||||
}
|
||||
|
||||
extern "C" void oe_abort()
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
using namespace snmalloc;
|
||||
int main()
|
||||
{
|
||||
DefaultPal pal;
|
||||
|
||||
size_t size = 1ULL << 28;
|
||||
oe_base = pal.reserve<true>(&size, 0);
|
||||
oe_end = (uint8_t*)oe_base + size;
|
||||
std::cout << "Allocated region " << oe_base << " - " << oe_end << std::endl;
|
||||
|
||||
auto a = ThreadAlloc::get();
|
||||
|
||||
for (size_t i = 0; i < 1000; i++)
|
||||
{
|
||||
auto r1 = a->alloc(100);
|
||||
std::cout << "Allocated object " << r1 << std::endl;
|
||||
|
||||
if (oe_base > r1)
|
||||
abort();
|
||||
if (oe_end < r1)
|
||||
abort();
|
||||
}
|
||||
}
|
||||
275
src/test/func/memory/memory.cc
Normal file
275
src/test/func/memory/memory.cc
Normal file
@@ -0,0 +1,275 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/opt.h>
|
||||
#include <test/xoroshiro.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
void test_alloc_dealloc_64k()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
constexpr size_t count = 1 << 12;
|
||||
constexpr size_t outer_count = 12;
|
||||
void* garbage[count];
|
||||
void* keep_alive[outer_count];
|
||||
|
||||
for (size_t j = 0; j < outer_count; j++)
|
||||
{
|
||||
// Allocate 64k of 16byte allocs
|
||||
// This will fill the short slab, and then start a new slab.
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
garbage[i] = alloc->alloc(16);
|
||||
}
|
||||
|
||||
// Allocate one object on the second slab
|
||||
keep_alive[j] = alloc->alloc(16);
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
alloc->dealloc(garbage[i]);
|
||||
}
|
||||
}
|
||||
for (size_t j = 0; j < outer_count; j++)
|
||||
{
|
||||
alloc->dealloc(keep_alive[j]);
|
||||
}
|
||||
}
|
||||
|
||||
void test_random_allocation()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
std::unordered_set<void*> allocated;
|
||||
|
||||
constexpr size_t count = 10000;
|
||||
constexpr size_t outer_count = 10;
|
||||
void* objects[count];
|
||||
for (size_t i = 0; i < count; i++)
|
||||
objects[i] = nullptr;
|
||||
|
||||
// Randomly allocate and deallocate objects
|
||||
xoroshiro::p128r32 r;
|
||||
size_t alloc_count = 0;
|
||||
for (size_t j = 0; j < outer_count; j++)
|
||||
{
|
||||
auto just_dealloc = r.next() % 2 == 1;
|
||||
auto duration = r.next() % count;
|
||||
for (size_t i = 0; i < duration; i++)
|
||||
{
|
||||
auto index = r.next();
|
||||
auto& cell = objects[index % count];
|
||||
if (cell != nullptr)
|
||||
{
|
||||
alloc->dealloc(cell);
|
||||
allocated.erase(cell);
|
||||
cell = nullptr;
|
||||
alloc_count--;
|
||||
}
|
||||
if (!just_dealloc)
|
||||
{
|
||||
cell = alloc->alloc(16);
|
||||
auto pair = allocated.insert(cell);
|
||||
// Check not already allocated
|
||||
assert(pair.second);
|
||||
UNUSED(pair);
|
||||
alloc_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alloc_count == 0 && just_dealloc)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deallocate all the remaining objects
|
||||
for (size_t i = 0; i < count; i++)
|
||||
if (objects[i] != nullptr)
|
||||
alloc->dealloc(objects[i]);
|
||||
}
|
||||
|
||||
void test_calloc()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
for (size_t size = 16; size <= (1 << 24); size <<= 1)
|
||||
{
|
||||
void* p = alloc->alloc(size);
|
||||
memset(p, 0xFF, size);
|
||||
alloc->dealloc(p, size);
|
||||
|
||||
p = alloc->alloc<YesZero>(size);
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
{
|
||||
if (((char*)p)[i] != 0)
|
||||
abort();
|
||||
}
|
||||
|
||||
alloc->dealloc(p, size);
|
||||
}
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
void test_double_alloc()
|
||||
{
|
||||
auto* a1 = current_alloc_pool()->acquire();
|
||||
auto* a2 = current_alloc_pool()->acquire();
|
||||
|
||||
const size_t n = (1 << 16) / 32;
|
||||
|
||||
for (size_t k = 0; k < 4; k++)
|
||||
{
|
||||
std::unordered_set<void*> set1;
|
||||
std::unordered_set<void*> set2;
|
||||
|
||||
for (size_t i = 0; i < (n * 2); i++)
|
||||
{
|
||||
void* p = a1->alloc(20);
|
||||
assert(set1.find(p) == set1.end());
|
||||
set1.insert(p);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (n * 2); i++)
|
||||
{
|
||||
void* p = a2->alloc(20);
|
||||
assert(set2.find(p) == set2.end());
|
||||
set2.insert(p);
|
||||
}
|
||||
|
||||
while (!set1.empty())
|
||||
{
|
||||
auto it = set1.begin();
|
||||
a2->dealloc(*it, 20);
|
||||
set1.erase(it);
|
||||
}
|
||||
|
||||
while (!set2.empty())
|
||||
{
|
||||
auto it = set2.begin();
|
||||
a1->dealloc(*it, 20);
|
||||
set2.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
current_alloc_pool()->release(a1);
|
||||
current_alloc_pool()->release(a2);
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
void test_external_pointer()
|
||||
{
|
||||
// Malloc does not have an external pointer querying mechanism.
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
for (uint8_t sc = 0; sc < NUM_SIZECLASSES; sc++)
|
||||
{
|
||||
size_t size = sizeclass_to_size(sc);
|
||||
void* p1 = alloc->alloc(size);
|
||||
|
||||
for (size_t offset = 0; offset < size; offset += 17)
|
||||
{
|
||||
void* p2 = (void*)((size_t)p1 + offset);
|
||||
void* p3 = Alloc::external_pointer(p2);
|
||||
void* p4 = Alloc::external_pointer<End>(p2);
|
||||
UNUSED(p3);
|
||||
UNUSED(p4);
|
||||
assert(p1 == p3);
|
||||
assert((size_t)p4 == (size_t)p1 + size - 1);
|
||||
}
|
||||
|
||||
alloc->dealloc(p1, size);
|
||||
}
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
};
|
||||
|
||||
void check_offset(void* base, void* interior)
|
||||
{
|
||||
void* calced_base = Alloc::external_pointer((void*)interior);
|
||||
if (calced_base != (void*)base)
|
||||
abort();
|
||||
}
|
||||
|
||||
void check_external_pointer_large(size_t* base)
|
||||
{
|
||||
size_t size = *base;
|
||||
char* curr = (char*)base;
|
||||
for (size_t offset = 0; offset < size; offset += 1 << 24)
|
||||
{
|
||||
check_offset(base, (void*)(curr + offset));
|
||||
check_offset(base, (void*)(curr + offset + (1 << 24) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void test_external_pointer_large()
|
||||
{
|
||||
xoroshiro::p128r64 r;
|
||||
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
constexpr size_t count_log = 5;
|
||||
constexpr size_t count = 1 << count_log;
|
||||
// Pre allocate all the objects
|
||||
size_t* objects[count];
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
size_t rand = r.next() & ((1 << 28) - 1);
|
||||
size_t size = (1 << 24) + rand;
|
||||
// store object
|
||||
objects[i] = (size_t*)alloc->alloc(size);
|
||||
// Store allocators size for this object
|
||||
*objects[i] = Alloc::alloc_size(objects[i]);
|
||||
|
||||
check_external_pointer_large(objects[i]);
|
||||
if (i > 0)
|
||||
check_external_pointer_large(objects[i - 1]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
check_external_pointer_large(objects[i]);
|
||||
}
|
||||
|
||||
// Deallocate everything
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
alloc->dealloc(objects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void test_alloc_16M()
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
// sizes >= 16M use large_alloc
|
||||
const size_t size = 16'000'000;
|
||||
|
||||
void* p1 = alloc->alloc(size);
|
||||
assert(Alloc::alloc_size(Alloc::external_pointer(p1)) >= size);
|
||||
alloc->dealloc(p1);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
#ifdef USE_SYSTEMATIC_TESTING
|
||||
opt::Opt opt(argc, argv);
|
||||
size_t seed = opt.is<size_t>("--seed", 0);
|
||||
Virtual::systematic_bump_ptr() += seed << 17;
|
||||
#else
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
#endif
|
||||
|
||||
test_external_pointer_large();
|
||||
test_alloc_dealloc_64k();
|
||||
test_random_allocation();
|
||||
test_calloc();
|
||||
test_double_alloc();
|
||||
test_external_pointer();
|
||||
test_alloc_16M();
|
||||
|
||||
return 0;
|
||||
}
|
||||
36
src/test/func/rounding/rounding.cc
Normal file
36
src/test/func/rounding/rounding.cc
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/opt.h>
|
||||
#include <test/xoroshiro.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
// Check for all sizeclass that we correctly round every offset within
|
||||
// a superslab to the correct value, by comparing with the standard
|
||||
// unoptimised version using division.
|
||||
// Also check we correctly determine multiples using optimized check.
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
||||
for (size_t size_class = 0; size_class < NUM_SIZECLASSES; size_class++)
|
||||
{
|
||||
size_t rsize = sizeclass_to_size((uint8_t)size_class);
|
||||
for (size_t offset = 0; offset < SUPERSLAB_SIZE; offset++)
|
||||
{
|
||||
size_t rounded = (offset / rsize) * rsize;
|
||||
bool mod_0 = (offset % rsize) == 0;
|
||||
|
||||
size_t opt_rounded = round_by_sizeclass(rsize, offset);
|
||||
if (rounded != opt_rounded)
|
||||
abort();
|
||||
|
||||
bool opt_mod_0 = is_multiple_of_sizeclass(rsize, offset);
|
||||
if (opt_mod_0 != mod_0)
|
||||
abort();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
249
src/test/histogram.h
Normal file
249
src/test/histogram.h
Normal file
@@ -0,0 +1,249 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_MEASURE
|
||||
# include "../ds/flaglock.h"
|
||||
|
||||
# include <algorithm>
|
||||
# include <iomanip>
|
||||
# include <iostream>
|
||||
# define MEASURE_TIME_MARKERS(id, minbits, maxbits, markers) \
|
||||
static constexpr const char* const id##_time_markers[] = markers; \
|
||||
static histogram::Global<histogram::Histogram<uint64_t, minbits, maxbits>> \
|
||||
id##_time_global(#id, __FILE__, __LINE__, id##_time_markers); \
|
||||
static thread_local histogram::Histogram<uint64_t, minbits, maxbits> \
|
||||
id##_time_local(id##_time_global); \
|
||||
histogram::MeasureTime<histogram::Histogram<uint64_t, minbits, maxbits>> \
|
||||
id##_time(id##_time_local);
|
||||
|
||||
# define MEASURE_TIME(id, minbits, maxbits) \
|
||||
MEASURE_TIME_MARKERS(id, minbits, maxbits, {nullptr})
|
||||
|
||||
# define MARKERS(...) \
|
||||
{ \
|
||||
__VA_ARGS__, nullptr \
|
||||
}
|
||||
|
||||
namespace histogram
|
||||
{
|
||||
using namespace snmalloc;
|
||||
|
||||
template<class H>
|
||||
class Global;
|
||||
|
||||
template<
|
||||
class V,
|
||||
size_t LOW_BITS,
|
||||
size_t HIGH_BITS,
|
||||
size_t INTERMEDIATE_BITS = LOW_BITS>
|
||||
class Histogram
|
||||
{
|
||||
public:
|
||||
using This = Histogram<V, LOW_BITS, HIGH_BITS, INTERMEDIATE_BITS>;
|
||||
friend Global<This>;
|
||||
|
||||
static_assert(LOW_BITS < HIGH_BITS, "LOW_BITS must be less than HIGH_BITS");
|
||||
|
||||
static constexpr V LOW = (V)((size_t)1 << LOW_BITS);
|
||||
static constexpr V HIGH = (V)((size_t)1 << HIGH_BITS);
|
||||
static constexpr size_t BUCKETS =
|
||||
((HIGH_BITS - LOW_BITS) << INTERMEDIATE_BITS) + 2;
|
||||
|
||||
private:
|
||||
V high = (std::numeric_limits<V>::min)();
|
||||
size_t overflow;
|
||||
size_t count[BUCKETS];
|
||||
|
||||
Global<This>* global;
|
||||
|
||||
public:
|
||||
Histogram() : global(nullptr) {}
|
||||
Histogram(Global<This>& g) : global(&g) {}
|
||||
|
||||
~Histogram()
|
||||
{
|
||||
if (global != nullptr)
|
||||
global->add(*this);
|
||||
}
|
||||
|
||||
void record(V value)
|
||||
{
|
||||
if (value > high)
|
||||
high = value;
|
||||
|
||||
if (value >= HIGH)
|
||||
{
|
||||
overflow++;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto i = get_index(value);
|
||||
assert(i < BUCKETS);
|
||||
count[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
V get_high()
|
||||
{
|
||||
return high;
|
||||
}
|
||||
|
||||
size_t get_overflow()
|
||||
{
|
||||
return overflow;
|
||||
}
|
||||
|
||||
size_t get_buckets()
|
||||
{
|
||||
return BUCKETS;
|
||||
}
|
||||
|
||||
size_t get_count(size_t index)
|
||||
{
|
||||
if (index >= BUCKETS)
|
||||
return 0;
|
||||
|
||||
return count[index];
|
||||
}
|
||||
|
||||
static std::pair<V, V> get_range(size_t index)
|
||||
{
|
||||
if (index >= BUCKETS)
|
||||
return std::make_pair(HIGH, HIGH);
|
||||
|
||||
if (index == 0)
|
||||
return std::make_pair(0, get_value(index));
|
||||
|
||||
return std::make_pair(get_value(index - 1) + 1, get_value(index));
|
||||
}
|
||||
|
||||
void add(This& that)
|
||||
{
|
||||
high = (std::max)(high, that.high);
|
||||
overflow += that.overflow;
|
||||
|
||||
for (size_t i = 0; i < BUCKETS; i++)
|
||||
count[i] += that.count[i];
|
||||
}
|
||||
|
||||
void print(std::ostream& o)
|
||||
{
|
||||
o << "\tHigh: " << high << std::endl
|
||||
<< "\tOverflow: " << overflow << std::endl;
|
||||
|
||||
size_t grand_total = overflow;
|
||||
for (size_t i = 0; i < BUCKETS; i++)
|
||||
grand_total += count[i];
|
||||
|
||||
size_t old_percentage = 0;
|
||||
size_t cumulative_total = 0;
|
||||
for (size_t i = 0; i < BUCKETS; i++)
|
||||
{
|
||||
auto r = get_range(i);
|
||||
|
||||
cumulative_total += count[i];
|
||||
|
||||
o << "\t" << std::setfill(' ') << std::setw(6) << std::get<0>(r) << ".."
|
||||
<< std::setfill(' ') << std::setw(6) << std::get<1>(r) << ": "
|
||||
<< std::setfill(' ') << std::setw(10) << count[i];
|
||||
|
||||
auto percentage = (cumulative_total * 100 / grand_total);
|
||||
if (percentage != old_percentage)
|
||||
{
|
||||
old_percentage = percentage;
|
||||
o << std::setfill(' ') << std::setw(20)
|
||||
<< (cumulative_total * 100 / grand_total) << "%";
|
||||
}
|
||||
|
||||
o << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t get_index(V value)
|
||||
{
|
||||
return bits::to_exp_mant<INTERMEDIATE_BITS, LOW_BITS - INTERMEDIATE_BITS>(
|
||||
value);
|
||||
}
|
||||
|
||||
static V get_value(size_t index)
|
||||
{
|
||||
return bits::
|
||||
from_exp_mant<INTERMEDIATE_BITS, LOW_BITS - INTERMEDIATE_BITS>(index);
|
||||
}
|
||||
};
|
||||
|
||||
template<class H>
|
||||
class Global
|
||||
{
|
||||
private:
|
||||
const char* name;
|
||||
const char* file;
|
||||
size_t line;
|
||||
const char* const* markers;
|
||||
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
H aggregate;
|
||||
|
||||
public:
|
||||
Global(
|
||||
const char* name_,
|
||||
const char* file_,
|
||||
size_t line_,
|
||||
const char* const* markers)
|
||||
: name(name_), file(file_), line(line_), markers(markers)
|
||||
{}
|
||||
|
||||
~Global()
|
||||
{
|
||||
print();
|
||||
}
|
||||
|
||||
void add(H& histogram)
|
||||
{
|
||||
FlagLock f(lock);
|
||||
aggregate.add(histogram);
|
||||
}
|
||||
|
||||
private:
|
||||
void print()
|
||||
{
|
||||
std::cout << name;
|
||||
|
||||
if (markers != nullptr)
|
||||
{
|
||||
std::cout << ": ";
|
||||
size_t i = 0;
|
||||
|
||||
while (markers[i] != nullptr)
|
||||
std::cout << markers[i++] << " ";
|
||||
}
|
||||
|
||||
std::cout << std::endl << file << ":" << line << std::endl;
|
||||
|
||||
aggregate.print(std::cout);
|
||||
}
|
||||
};
|
||||
|
||||
template<class H>
|
||||
class MeasureTime
|
||||
{
|
||||
private:
|
||||
H& histogram;
|
||||
uint64_t t;
|
||||
|
||||
public:
|
||||
MeasureTime(H& histogram_) : histogram(histogram_)
|
||||
{
|
||||
t = bits::benchmark_time_start();
|
||||
}
|
||||
|
||||
~MeasureTime()
|
||||
{
|
||||
histogram.record(bits::benchmark_time_end() - t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#else
|
||||
# define MEASURE_TIME(id, minbits, maxbits)
|
||||
# define MEASURE_TIME_MARKERS(id, minbits, maxbits, markers)
|
||||
#endif
|
||||
14
src/test/measuretime.h
Normal file
14
src/test/measuretime.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#define DO_TIME(name, code) \
|
||||
{ \
|
||||
auto start__ = std::chrono::high_resolution_clock::now(); \
|
||||
code auto finish__ = std::chrono::high_resolution_clock::now(); \
|
||||
auto diff__ = finish__ - start__; \
|
||||
std::cout << name << ": " << std::setw(12) << diff__.count() << " ns" \
|
||||
<< std::endl; \
|
||||
}
|
||||
91
src/test/opt.h
Normal file
91
src/test/opt.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
namespace opt
|
||||
{
|
||||
class Opt
|
||||
{
|
||||
private:
|
||||
int argc;
|
||||
char** argv;
|
||||
|
||||
public:
|
||||
Opt(int argc, char** argv) : argc(argc), argv(argv) {}
|
||||
|
||||
bool has(const char* opt)
|
||||
{
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (!strcmp(opt, argv[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T is(const char* opt, T def)
|
||||
{
|
||||
size_t len = strlen(opt);
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
const char* p = param(opt, len, i);
|
||||
|
||||
if (p != nullptr)
|
||||
{
|
||||
char* end = nullptr;
|
||||
T r;
|
||||
|
||||
if (std::is_unsigned<T>::value)
|
||||
r = (T)strtoull(p, &end, 10);
|
||||
else
|
||||
r = (T)strtoll(p, &end, 10);
|
||||
|
||||
if ((r == 0) && (end == p))
|
||||
return def;
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
const char* is(const char* opt, const char* def)
|
||||
{
|
||||
size_t len = strlen(opt);
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
const char* p = param(opt, len, i);
|
||||
|
||||
if (p != nullptr)
|
||||
return p;
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* param(const char* opt, size_t len, int i)
|
||||
{
|
||||
if (strncmp(opt, argv[i], len))
|
||||
return nullptr;
|
||||
|
||||
switch (argv[i][len])
|
||||
{
|
||||
case '\0':
|
||||
return (i < (argc - 1)) ? argv[i + 1] : nullptr;
|
||||
case '=':
|
||||
return &argv[i][len + 1];
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
176
src/test/perf/contention/contention.cc
Normal file
176
src/test/perf/contention/contention.cc
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "test/measuretime.h"
|
||||
#include "test/opt.h"
|
||||
#include "test/usage.h"
|
||||
#include "test/xoroshiro.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <snmalloc.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
bool use_malloc = false;
|
||||
|
||||
template<void f(size_t id)>
|
||||
class ParallelTest
|
||||
{
|
||||
private:
|
||||
std::atomic<bool> flag = false;
|
||||
std::atomic<size_t> ready = 0;
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
std::atomic<size_t> complete = 0;
|
||||
|
||||
size_t cores;
|
||||
|
||||
void run(size_t id)
|
||||
{
|
||||
auto prev = ready.fetch_add(1);
|
||||
if (prev + 1 == cores)
|
||||
{
|
||||
start = bits::tick();
|
||||
flag = true;
|
||||
}
|
||||
while (!flag)
|
||||
bits::pause();
|
||||
|
||||
f(id);
|
||||
|
||||
prev = complete.fetch_add(1);
|
||||
if (prev + 1 == cores)
|
||||
{
|
||||
end = bits::tick();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
ParallelTest(size_t cores) : cores(cores)
|
||||
{
|
||||
std::thread* t = new std::thread[cores];
|
||||
|
||||
for (size_t i = 0; i < cores; i++)
|
||||
{
|
||||
t[i] = std::thread(&ParallelTest::run, this, i);
|
||||
}
|
||||
// Wait for all the threads.
|
||||
for (size_t i = 0; i < cores; i++)
|
||||
{
|
||||
t[i].join();
|
||||
}
|
||||
|
||||
delete[] t;
|
||||
}
|
||||
|
||||
uint64_t time()
|
||||
{
|
||||
return end - start;
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<size_t*>* contention;
|
||||
size_t swapsize;
|
||||
size_t swapcount;
|
||||
|
||||
void test_tasks_f(size_t id)
|
||||
{
|
||||
Alloc* a = ThreadAlloc::get();
|
||||
xoroshiro::p128r32 r(id + 5000);
|
||||
|
||||
for (size_t n = 0; n < swapcount; n++)
|
||||
{
|
||||
size_t size = 16 + (r.next() % 1024);
|
||||
size_t* res = (size_t*)(use_malloc ? malloc(size) : a->alloc(size));
|
||||
|
||||
*res = size;
|
||||
size_t* out =
|
||||
contention[n % swapsize].exchange(res, std::memory_order_relaxed);
|
||||
|
||||
if (out != nullptr)
|
||||
{
|
||||
size = *out;
|
||||
if (use_malloc)
|
||||
free(out);
|
||||
else
|
||||
a->dealloc(out, size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void test_tasks(size_t num_tasks, size_t count, size_t size)
|
||||
{
|
||||
Alloc* a = ThreadAlloc::get();
|
||||
|
||||
contention = new std::atomic<size_t*>[size];
|
||||
xoroshiro::p128r32 r;
|
||||
|
||||
for (size_t n = 0; n < size; n++)
|
||||
{
|
||||
size_t alloc_size = 16 + (r.next() % 1024);
|
||||
size_t* res =
|
||||
(size_t*)(use_malloc ? malloc(alloc_size) : a->alloc(alloc_size));
|
||||
*res = alloc_size;
|
||||
contention[n] = res;
|
||||
}
|
||||
swapcount = count;
|
||||
swapsize = size;
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
Stats s0;
|
||||
current_alloc_pool()->aggregate_stats(s0);
|
||||
#endif
|
||||
|
||||
{
|
||||
ParallelTest<test_tasks_f> test(num_tasks);
|
||||
|
||||
std::cout << "Task test, " << num_tasks << " threads, " << count
|
||||
<< " swaps per thread " << test.time() << "ticks" << std::endl;
|
||||
|
||||
for (size_t n = 0; n < swapsize; n++)
|
||||
{
|
||||
if (contention[n] != nullptr)
|
||||
{
|
||||
if (use_malloc)
|
||||
free(contention[n]);
|
||||
else
|
||||
a->dealloc(contention[n], *contention[n]);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] contention;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
#endif
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
opt::Opt opt(argc, argv);
|
||||
size_t cores = opt.is<size_t>("--cores", 8);
|
||||
|
||||
size_t count = opt.is<size_t>("--swapcount", 1 << 20);
|
||||
size_t size = opt.is<size_t>("--swapsize", 1 << 18);
|
||||
use_malloc = opt.has("--use_malloc");
|
||||
|
||||
std::cout << "Allocator is " << (use_malloc ? "System" : "snmalloc")
|
||||
<< std::endl;
|
||||
|
||||
for (size_t i = cores; i > 0; i >>= 1)
|
||||
test_tasks(i, count, size);
|
||||
|
||||
if (opt.has("--stats"))
|
||||
{
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
Stats s;
|
||||
current_alloc_pool()->aggregate_stats(s);
|
||||
s.print<Alloc>(std::cout);
|
||||
#endif
|
||||
|
||||
usage::print_memory();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
78
src/test/perf/external_pointer/externalpointer.cc
Normal file
78
src/test/perf/external_pointer/externalpointer.cc
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/measuretime.h>
|
||||
#include <test/xoroshiro.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
constexpr size_t count_log = 20;
|
||||
constexpr size_t count = 1 << count_log;
|
||||
// Pre allocate all the objects
|
||||
size_t* objects[count];
|
||||
|
||||
NOINLINE void setup(xoroshiro::p128r64& r, Alloc* alloc)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
size_t rand = (size_t)r.next();
|
||||
size_t offset = bits::clz(rand);
|
||||
if (offset > 30)
|
||||
offset = 30;
|
||||
size_t size = (rand & 15) << offset;
|
||||
if (size < 16)
|
||||
size = 16;
|
||||
// store object
|
||||
objects[i] = (size_t*)alloc->alloc(size);
|
||||
// Store allocators size for this object
|
||||
*objects[i] = Alloc::alloc_size(objects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
NOINLINE void teardown(Alloc* alloc)
|
||||
{
|
||||
// Deallocate everything
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
alloc->dealloc(objects[i]);
|
||||
}
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
void test_external_pointer(xoroshiro::p128r64& r)
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
setup(r, alloc);
|
||||
|
||||
DO_TIME("External pointer queries ", {
|
||||
for (size_t i = 0; i < 10000000; i++)
|
||||
{
|
||||
size_t rand = (size_t)r.next();
|
||||
size_t oid = rand & (((size_t)1 << count_log) - 1);
|
||||
size_t* external_ptr = objects[oid];
|
||||
size_t size = *external_ptr;
|
||||
size_t offset = (size >> 4) * (rand & 15);
|
||||
size_t interior_ptr = ((size_t)external_ptr) + offset;
|
||||
void* calced_external = Alloc::external_pointer((void*)interior_ptr);
|
||||
if (calced_external != external_ptr)
|
||||
abort();
|
||||
}
|
||||
});
|
||||
|
||||
teardown(alloc);
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
xoroshiro::p128r64 r;
|
||||
#if NDEBUG
|
||||
size_t nn = 30;
|
||||
#else
|
||||
size_t nn = 3;
|
||||
#endif
|
||||
|
||||
for (size_t n = 0; n < nn; n++)
|
||||
test_external_pointer(r);
|
||||
return 0;
|
||||
}
|
||||
83
src/test/perf/singlethread/singlethread.cc
Normal file
83
src/test/perf/singlethread/singlethread.cc
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <snmalloc.h>
|
||||
#include <test/measuretime.h>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace snmalloc;
|
||||
|
||||
template<ZeroMem zero_mem>
|
||||
void test_alloc_dealloc(size_t count, size_t size, bool write)
|
||||
{
|
||||
auto* alloc = ThreadAlloc::get();
|
||||
|
||||
DO_TIME(
|
||||
"Count: " << std::setw(6) << count << ", Size: " << std::setw(6) << size
|
||||
<< ", ZeroMem: " << (zero_mem == YesZero) << ", Write: " << write,
|
||||
{
|
||||
std::unordered_set<void*> set;
|
||||
|
||||
// alloc 1.5x objects
|
||||
for (size_t i = 0; i < ((count * 3) / 2); i++)
|
||||
{
|
||||
void* p = alloc->alloc<zero_mem>(size);
|
||||
assert(set.find(p) == set.end());
|
||||
|
||||
if (write)
|
||||
*(int*)p = 4;
|
||||
|
||||
set.insert(p);
|
||||
}
|
||||
|
||||
// free 0.25x of the objects
|
||||
for (size_t i = 0; i < (count / 4); i++)
|
||||
{
|
||||
auto it = set.begin();
|
||||
void* p = *it;
|
||||
alloc->dealloc(p, size);
|
||||
set.erase(it);
|
||||
assert(set.find(p) == set.end());
|
||||
}
|
||||
|
||||
// alloc 1x objects
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
void* p = alloc->alloc<zero_mem>(size);
|
||||
assert(set.find(p) == set.end());
|
||||
|
||||
if (write)
|
||||
*(int*)p = 4;
|
||||
|
||||
set.insert(p);
|
||||
}
|
||||
|
||||
// free everything
|
||||
while (!set.empty())
|
||||
{
|
||||
auto it = set.begin();
|
||||
alloc->dealloc(*it, size);
|
||||
set.erase(it);
|
||||
}
|
||||
});
|
||||
|
||||
current_alloc_pool()->debug_check_empty();
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
for (size_t size = 16; size <= 128; size <<= 1)
|
||||
{
|
||||
test_alloc_dealloc<NoZero>(1 << 15, size, false);
|
||||
test_alloc_dealloc<NoZero>(1 << 15, size, true);
|
||||
test_alloc_dealloc<YesZero>(1 << 15, size, false);
|
||||
test_alloc_dealloc<YesZero>(1 << 15, size, true);
|
||||
}
|
||||
|
||||
for (size_t size = 1 << 12; size <= 1 << 17; size <<= 1)
|
||||
{
|
||||
test_alloc_dealloc<NoZero>(1 << 10, size, false);
|
||||
test_alloc_dealloc<NoZero>(1 << 10, size, true);
|
||||
test_alloc_dealloc<YesZero>(1 << 10, size, false);
|
||||
test_alloc_dealloc<YesZero>(1 << 10, size, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
42
src/test/usage.h
Normal file
42
src/test/usage.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# define NOMINMAX
|
||||
# include <windows.h>
|
||||
// Needs to be included after windows.h
|
||||
# include <psapi.h>
|
||||
#endif
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
namespace usage
|
||||
{
|
||||
void print_memory()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
PROCESS_MEMORY_COUNTERS_EX pmc;
|
||||
|
||||
if (!GetProcessMemoryInfo(
|
||||
GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)))
|
||||
return;
|
||||
|
||||
std::cout << "Memory info:" << std::endl
|
||||
<< "\tPageFaultCount: " << pmc.PageFaultCount << std::endl
|
||||
<< "\tPeakWorkingSetSize: " << pmc.PeakWorkingSetSize << std::endl
|
||||
<< "\tWorkingSetSize: " << pmc.WorkingSetSize << std::endl
|
||||
<< "\tQuotaPeakPagedPoolUsage: " << pmc.QuotaPeakPagedPoolUsage
|
||||
<< std::endl
|
||||
<< "\tQuotaPagedPoolUsage: " << pmc.QuotaPagedPoolUsage
|
||||
<< std::endl
|
||||
<< "\tQuotaPeakNonPagedPoolUsage: "
|
||||
<< pmc.QuotaPeakNonPagedPoolUsage << std::endl
|
||||
<< "\tQuotaNonPagedPoolUsage: " << pmc.QuotaNonPagedPoolUsage
|
||||
<< std::endl
|
||||
<< "\tPagefileUsage: " << pmc.PagefileUsage << std::endl
|
||||
<< "\tPeakPagefileUsage: " << pmc.PeakPagefileUsage << std::endl
|
||||
<< "\tPrivateUsage: " << pmc.PrivateUsage << std::endl;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
71
src/test/xoroshiro.h
Normal file
71
src/test/xoroshiro.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace xoroshiro
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template<typename STATE, typename RESULT, STATE A, STATE B, STATE C>
|
||||
class XorOshiro
|
||||
{
|
||||
private:
|
||||
static constexpr unsigned STATE_BITS = 8 * sizeof(STATE);
|
||||
static constexpr unsigned RESULT_BITS = 8 * sizeof(RESULT);
|
||||
|
||||
static_assert(
|
||||
STATE_BITS >= RESULT_BITS,
|
||||
"STATE must have at least as many bits as RESULT");
|
||||
|
||||
STATE x;
|
||||
STATE y;
|
||||
|
||||
static inline STATE rotl(STATE x, STATE k)
|
||||
{
|
||||
return (x << k) | (x >> (STATE_BITS - k));
|
||||
}
|
||||
|
||||
public:
|
||||
XorOshiro(STATE x_ = 5489, STATE y_ = 0) : x(x_), y(y_)
|
||||
{
|
||||
// If both zero, then this does not work
|
||||
if (x_ == 0 && y_ == 0)
|
||||
abort();
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
void set_state(STATE x_, STATE y_ = 0)
|
||||
{
|
||||
// If both zero, then this does not work
|
||||
if (x_ == 0 && y_ == 0)
|
||||
abort();
|
||||
|
||||
x = x_;
|
||||
y = y_;
|
||||
next();
|
||||
}
|
||||
|
||||
RESULT next()
|
||||
{
|
||||
STATE r = x + y;
|
||||
y ^= x;
|
||||
x = rotl(x, A) ^ y ^ (y << B);
|
||||
y = rotl(y, C);
|
||||
// If both zero, then this does not work
|
||||
if (x == 0 && y == 0)
|
||||
abort();
|
||||
return r >> (STATE_BITS - RESULT_BITS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
using p128r64 = detail::XorOshiro<uint64_t, uint64_t, 55, 14, 36>;
|
||||
using p128r32 = detail::XorOshiro<uint64_t, uint32_t, 55, 14, 36>;
|
||||
using p64r32 = detail::XorOshiro<uint32_t, uint32_t, 27, 7, 20>;
|
||||
using p64r16 = detail::XorOshiro<uint32_t, uint16_t, 27, 7, 20>;
|
||||
using p32r16 = detail::XorOshiro<uint16_t, uint16_t, 13, 5, 10>;
|
||||
using p32r8 = detail::XorOshiro<uint16_t, uint8_t, 13, 5, 10>;
|
||||
using p16r8 = detail::XorOshiro<uint8_t, uint8_t, 4, 7, 3>;
|
||||
}
|
||||
Reference in New Issue
Block a user