flatten 20260225

This commit is contained in:
Timothy Prepscius
2026-02-25 12:25:20 -05:00
commit fdf8bae515
46 changed files with 2983 additions and 0 deletions

25
tjp/core/Precompile.pch Normal file
View File

@@ -0,0 +1,25 @@
#define USE_PRECOMPILED_HEADERS
#ifdef USE_PRECOMPILED_HEADERS
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
#include <list>
#include <map>
#include <vector>
#include <mutex>
#include <shared_mutex>
#include <condition_variable>
#include <thread>
#include <algorithm>
#include <memory>
#include <functional>
#endif

View File

@@ -0,0 +1,87 @@
// TJP COPYRIGHT HEADER
template <class T>
class allocator
{
public:
using value_type = T;
// using pointer = value_type*;
// using const_pointer = typename std::pointer_traits<pointer>::template
// rebind<value_type const>;
// using void_pointer = typename std::pointer_traits<pointer>::template
// rebind<void>;
// using const_void_pointer = typename std::pointer_traits<pointer>::template
// rebind<const void>;
// using difference_type = typename std::pointer_traits<pointer>::difference_type;
// using size_type = std::make_unsigned_t<difference_type>;
// template <class U> struct rebind {typedef allocator<U> other;};
allocator() noexcept {} // not required, unless used
template <class U> allocator(allocator<U> const&) noexcept {}
value_type* // Use pointer if pointer is not a value_type*
allocate(std::size_t n)
{
return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
}
void
deallocate(value_type* p, std::size_t) noexcept // Use pointer if pointer is not a value_type*
{
::operator delete(p);
}
// value_type*
// allocate(std::size_t n, const_void_pointer)
// {
// return allocate(n);
// }
// template <class U, class ...Args>
// void
// construct(U* p, Args&& ...args)
// {
// ::new(p) U(std::forward<Args>(args)...);
// }
// template <class U>
// void
// destroy(U* p) noexcept
// {
// p->~U();
// }
// std::size_t
// max_size() const noexcept
// {
// return std::numeric_limits<size_type>::max();
// }
// allocator
// select_on_container_copy_construction() const
// {
// return *this;
// }
// using propagate_on_container_copy_assignment = std::false_type;
// using propagate_on_container_move_assignment = std::false_type;
// using propagate_on_container_swap = std::false_type;
// using is_always_equal = std::is_empty<allocator>;
};
template <class T, class U>
bool
operator==(allocator<T> const&, allocator<U> const&) noexcept
{
return true;
}
template <class T, class U>
bool
operator!=(allocator<T> const& x, allocator<U> const& y) noexcept
{
return !(x == y);
}

View File

@@ -0,0 +1,32 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <utility>
namespace tjp::core {
struct AllocatorFunctions
{
template<typename T>
static void construct(T *__p)
{
::new((void*)__p) T();
}
template<typename U, typename... Args>
static void construct(U* p, Args&&... args)
{
::new((void*)p) U(std::forward<Args>(args)...);
}
template<typename T>
static void destroy(T *p)
{
p->~T();
}
} ;
} // namespace

View File

@@ -0,0 +1,88 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "StaticAllocatorPoolOf.hpp"
namespace tjp::core {
template<typename T>
class AllocatorPool
{
public:
typedef size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
public:
template<typename _Tp1> struct rebind {
typedef AllocatorPool<_Tp1> other;
};
using Pool = StaticAllocatorPoolOf<T>;
Pool pool;
public:
AllocatorPool() {}
AllocatorPool (const AllocatorPool &) {}
~AllocatorPool () {}
template <class _Up>
AllocatorPool (const _Up &) { }
pointer allocate (size_t size)
{
debug_assert(size == 1);
auto t = pool.allocate();
return (pointer)t;
}
void deallocate(T *o, size_t size)
{
debug_assert(size == 1);
pool.deallocate(o);
}
void construct(pointer __p)
{
AllocatorFunctions::construct(__p);
}
template<typename U, typename... Args>
void construct(U* p, Args&&... args)
{
AllocatorFunctions::construct(p, std::forward<Args>(args)...);
}
void destroy(pointer p)
{
AllocatorFunctions::destroy(p);
}
template<typename U>
void destroy(U* p)
{
AllocatorFunctions::destroy(p);
}
} ;
template<class T, class U>
bool operator ==(const AllocatorPool<T> &lhs, const AllocatorPool<U> &rhs) noexcept
{
return true;
}
template<class T, class U>
bool operator !=(const AllocatorPool<T> &lhs, const AllocatorPool<U> &rhs) noexcept
{
return !(lhs == rhs);
}
} // namespace

View File

@@ -0,0 +1,77 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "SizeAllocatorPool.hpp"
namespace tjp::core {
template<typename T>
struct ClassAllocatorPool
{
using Allocator = SizeAllocatorPool<sizeof(T), alignof(T)>;
Allocator allocator;
typedef T value_type;
template<typename _Tp1> struct rebind { typedef ClassAllocatorPool<_Tp1> other; };
template<typename ...Args>
ClassAllocatorPool(Args&& ...args) {}
T *allocate ()
{
return (T *)allocator.allocate();
}
void deallocate (T *t)
{
return allocator.deallocate(t);
}
auto &getSize()
{
return allocator.getSize();
}
void prune ()
{
allocator.prune();
}
};
template<typename T>
struct ClassAllocatorPoolPartitioned
{
SizeAllocatorPool<sizeof(T), alignof(T), T> allocator;
typedef T value_type;
template<typename _Tp1> struct rebind { typedef ClassAllocatorPoolPartitioned<_Tp1> other; };
template<typename ...Args>
ClassAllocatorPoolPartitioned(Args&& ...args) {}
T *allocate ()
{
return (T *)allocator.allocate();
}
void deallocate (T *t)
{
return allocator.deallocate(t);
}
auto &getSize()
{
return allocator.getSize();
}
void prune ()
{
allocator.prune();
}
};
} // namespace

View File

@@ -0,0 +1,18 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "StrongAllocator.hpp"
namespace tjp::core::allocators {
struct IOAllocator : StrongAllocator
{
template<typename IO, typename T>
void allocate(IO &io, StrongPtr<T> &t)
{
t = this->strong<T>();
}
} ;
} // namespace

View File

@@ -0,0 +1,14 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "AllocatorPool.hpp"
namespace tjp::core {
template<typename K, typename V>
using MapAllocator = AllocatorPool<std::pair<const K, V>>;
} // namespace

View File

@@ -0,0 +1,59 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/sfinae/if_or.hpp>
namespace tjp::core::allocators {
template <typename T>
class has_allocator
{
typedef char yes;
struct no {char x[2];};
template<typename C> static yes test(typename C::Allocator*);
template<typename C> static no test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof (char)};
};
// This should use invocable, but I haven't figured it out yet
template<typename T> struct is_strong_allocator : std::false_type {};
template <typename T>
class has_strong_allocator_flag
{
typedef char yes;
struct no { char x[2];};
template<typename C> static yes test(typename C::is_strong_allocator*);
template<typename C> static no test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof (char)};
};
template <
typename T,
typename T2=void
// typename std::enable_if<!has_allocator<T>::value, char>::type
>
struct has_strong_allocator {
enum { value = false };
};
template<typename T>
struct has_strong_allocator<
T,
typename std::enable_if<has_allocator<T>::value>::type
>
{
typedef typename sfinae::if_or<
has_strong_allocator_flag<typename T::Allocator>::value,
std::true_type,
std::false_type>::type type;
public:
static constexpr bool value = type::value;
};
} // namespace

11
tjp/core/allocator/Size.h Executable file
View File

@@ -0,0 +1,11 @@
// TJP COPYRIGHT HEADER
#pragma once
namespace tjp::core::allocator_pool {
struct Size;
} // namespace

16
tjp/core/allocator/Size.hpp Executable file
View File

@@ -0,0 +1,16 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/threads/Atomic.hpp>
namespace tjp::core::allocator_pool {
struct Size {
Atomic<size_t> used = 0;
Atomic<size_t> freed = 0;
} ;
} // namespace

View File

@@ -0,0 +1,45 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "Stack_locking.hpp"
#include "Stack_lockfree.hpp"
#include "StackAllocatorPool.hpp"
#include <tjp/core/threads/SpinMutex.hpp>
namespace tjp::core {
template<size_t size, size_t alignment, typename C=void>
using SizeAllocatorPool_lockfree =
allocator_pool::StackAllocatorPool<
allocator_pool::Stack_lockfree<
allocator_pool::Allocation<size, alignment>
>,
C
>;
template<size_t size, size_t alignment, typename C=void>
using SizeAllocatorPool_locking =
allocator_pool::StackAllocatorPool<
allocator_pool::Stack_locking<
allocator_pool::Allocation<size, alignment>
>,
C
>;
template<size_t size, size_t alignment, typename C=void>
using SizeAllocatorPool_locking_spinlock =
allocator_pool::StackAllocatorPool<
allocator_pool::Stack_locking<
allocator_pool::Allocation<size, alignment>,
SpinMutex
>,
C
>;
template<size_t size, size_t alignment, typename C=void>
using SizeAllocatorPool = SizeAllocatorPool_locking_spinlock<size, alignment, C>;
} // namespace

View File

@@ -0,0 +1,160 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/threads/Lock.hpp>
#include <tjp/core/threads/Atomic.hpp>
#include <tjp/core/debug/Debug.h>
#include "Size.hpp"
namespace tjp::core::allocator_pool {
template<size_t size_, size_t alignment_>
struct Allocation
{
static constexpr size_t size = size_;
static constexpr size_t alignment = alignment_;
char t[size_];
} __attribute__ ((aligned(alignment_)));
template<typename T, typename C=void>
class StackAllocatorPool_Real
{
private:
using Stack = T;
using Node = typename T::node_type;
Stack stack;
Size size;
public:
using value_type = void;
public:
~StackAllocatorPool_Real ()
{
while (!stack.empty())
prune();
}
void *allocate ()
{
auto node = stack.pop();
if (node)
{
size.freed--;
}
else
{
node = new Node();
}
size.used++;
return &node->t;
}
void deallocate(void *v)
{
if (!v)
return;
static_assert(offsetof(Node, t) == 0);
constexpr size_t offset_t = offsetof(Node, t);
Node *node;
if constexpr(offset_t == 0)
node = (Node *)v;
else
node = (Node *)((char *)v - offset_t);
stack.push(node);
size.used--;
size.freed++;
}
const Size &getSize ()
{
return size;
}
void prune()
{
auto node = stack.pop();
if (node)
{
delete node;
size.freed--;
}
}
} ;
template<typename T=void>
struct StackAllocatorPool_Fake_Debug_Variables_ {
static bool use_real_pool;
} ;
template<typename T>
bool StackAllocatorPool_Fake_Debug_Variables_<T>::use_real_pool = false;
using StackAllocatorPool_Fake_Debug_Variables = StackAllocatorPool_Fake_Debug_Variables_<void>;
template<typename T_, typename C=void>
struct StackAllocatorPool_Fake
{
using T = T_;
Size size;
using value_type = void;
using RealPool = StackAllocatorPool_Real<T_, C>;
RealPool pool;
void *allocate ()
{
if (StackAllocatorPool_Fake_Debug_Variables::use_real_pool)
return pool.allocate();
size.used += T::Type::size;
auto alignment = T::Type::alignment;
(void)alignment;
auto size = T::Type::size;
auto result = malloc(size);
// auto result = std::aligned_alloc(alignment, size);
debug_assert(result);
return result;
}
void deallocate(void *v)
{
if (StackAllocatorPool_Fake_Debug_Variables::use_real_pool)
return pool.deallocate(v);
std::free(v);
size.used -= T::Type::size;
}
const Size &getSize ()
{
return size;
}
void prune()
{
}
} ;
template<typename T, typename C=void>
using StackAllocatorPool = StackAllocatorPool_Fake<T, C>;
//template<typename T, typename C=void>
//using StackAllocatorPool = StackAllocatorPool_Real<T, C>;
} // namespace

View File

@@ -0,0 +1,26 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/threads/Atomic.hpp>
namespace tjp::core::allocator_pool {
template<typename T>
struct StackNode {
using value_type = T;
T t;
StackNode *next;
} ;
template<>
struct StackNode<void> {
using value_type = void;
StackNode *next;
} ;
} // namespace

View File

@@ -0,0 +1,81 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/assert/debug_assert.h>
#include <tjp/core/threads/Atomic.hpp>
#include "StackNode.hpp"
namespace tjp::core::allocator_pool {
template<typename T>
class Stack_lockfree
{
public:
using Type = T;
using Node = StackNode<T>;
using node_type = Node;
private:
Atomic<Node *> head = nullptr;
public:
Node *pop()
{
// this method DOES NOT WORK
// because:
// thread A:
// next = top->next (Y)
// thread B:
// X = pop top, Y = pop top
// push X
// thread A:
// exchange pop X
// now top == Y, even though Y is not there
// there has to be a double test, for instance, is(top == head and top->next still equal to next)
debug_assert(false);
Node *top = head.load();
while (top != nullptr)
{
Node *next = top->next;
if (head.compare_exchange_weak(top, next))
break;
}
return top;
}
void push(Node *node)
{
Node *expected_head = head.load();
do
{
node->next = expected_head;
}
while(!head.compare_exchange_weak(expected_head, node));
}
bool empty() const
{
return head == nullptr;
}
size_t size_not_thread_safe() const
{
size_t result = 0;
Node *node = head;
while (node)
{
result++;
node = node->next;
}
return result;
}
} ;
} // namespace

View File

@@ -0,0 +1,65 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/threads/Lock.hpp>
#include "StackNode.hpp"
namespace tjp::core::allocator_pool {
template<typename T, typename M=Mutex>
class Stack_locking
{
public:
using Type = T;
using Node = StackNode<T>;
using node_type = Node;
using mutex_type = M;
private:
mutable M mutex;
Node *head = nullptr;
public:
Node *pop()
{
auto lock = lock_of(mutex);
auto *a = head;
if (a != nullptr)
head = a->next;
return a;
}
void push(Node *a)
{
auto lock = lock_of(mutex);
a->next = head;
head = a;
}
bool empty() const
{
auto lock = lock_of(mutex);
return head == nullptr;
}
size_t size_not_thread_safe() const
{
size_t result = 0;
Node *node = head;
while (node)
{
result++;
node = node->next;
}
return result;
}
} ;
} // namespace

View File

@@ -0,0 +1,19 @@
// TJP COPYRIGHT HEADER
#pragma once
namespace tjp::core {
template<typename T>
class StaticAllocator
{
public:
typedef T Allocator;
static Allocator allocator;
} ;
template<typename T> typename StaticAllocator<T>::Allocator StaticAllocator<T>::allocator;
} // namespace

View File

@@ -0,0 +1,37 @@
// TJP COPYRIGHT HEADER
#pragma once
namespace tjp {
namespace core {
template<typename T>
class StaticAllocatorPool
{
public:
typedef T Allocator;
static Allocator allocator;
static auto allocate ()
{
return allocator.allocate();
}
template<typename U>
static void deallocate(U *o)
{
allocator.deallocate(o);
}
static void prune()
{
allocator.prune();
}
} ;
template<typename T> typename StaticAllocatorPool<T>::Allocator StaticAllocatorPool<T>::allocator;
} // namespace
} // namespace

View File

@@ -0,0 +1,70 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "StaticAllocatorPool.hpp"
#include "AllocatorFunctions.hpp"
#include "SizeAllocatorPool.hpp"
#include "prune/Pruning.hpp"
namespace tjp::core {
template<size_t size, size_t alignment, typename C=void>
struct StaticAllocatorPoolOf_ {
using SizeAllocator = SizeAllocatorPool<size, alignment, C>;
using Allocator = allocator_pool::prune::Pruning<SizeAllocator>;
static Allocator allocator;
static auto allocate ()
{
return allocator.allocate();
}
template<typename U>
static void deallocate(U *o)
{
allocator.deallocate(o);
}
static void prune()
{
allocator.prune();
}
} ;
template<size_t size, size_t alignment, typename C> typename
StaticAllocatorPoolOf_<size, alignment, C>::Allocator StaticAllocatorPoolOf_<size, alignment, C>::allocator;
template<typename T>
class StaticAllocatorPoolOf
{
public:
using Allocator = StaticAllocatorPoolOf_<sizeof(T), alignof(T)>;
Allocator allocator;
using value_type = T;
auto allocate ()
{
return (T *)allocator.allocate();
}
template<typename U>
void deallocate(U *o)
{
allocator.deallocate(o);
}
void prune()
{
allocator.prune();
}
} ;
} // namespace

View File

@@ -0,0 +1,25 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "AllocatorPool.hpp"
#include <tjp/core/ptr/Ptr.hpp>
namespace tjp::core::allocators {
struct StrongAllocator
{
typedef void is_strong_allocator;
template<typename T, typename ... Args>
auto strong(Args && ...args)
{
return strong_with_allocator<T>(
core::AllocatorPool<T>(),
std::forward<Args>(args)...
);
}
} ;
} // namespace

159
tjp/core/allocator/VectorPool.hpp Executable file
View File

@@ -0,0 +1,159 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <vector>
#include "../allocator/StaticAllocator.hpp"
#include "../allocator/SizeAllocatorPool.hpp"
#include <tjp/core/log/Log.h>
#include <tjp/core/log/LogOf.h>
namespace tjp::core {
namespace vectorpool {
//https://stackoverflow.com/questions/994593/how-to-do-an-integer-log2-in-c
inline
int uint64_log2(uint64_t n)
{
#define S(k) if (n >= (UINT64_C(1) << k)) { i += k; n >>= k; }
int i = -(n == 0); S(32); S(16); S(8); S(4); S(2); S(1); return i;
#undef S
}
inline
int scale(uint64_t n)
{
return n ?
uint64_log2(n-1)+2 :
0;
}
inline
constexpr size_t unscale(int n)
{
return n ?
1 << (n-1) :
0;
}
template<typename T>
constexpr size_t unscale(int n)
{
return unscale(n) * sizeof(T);
}
template <typename T, typename DefaultAllocator=std::allocator<T>>
class Allocator {
public:
// The type definitions required for an allocator
using value_type = T;
using pointer = value_type*;
using const_pointer =
typename std::pointer_traits<pointer>::template
rebind<value_type const>;
using void_pointer =
typename std::pointer_traits<pointer>::template
rebind<void>;
using const_void_pointer =
typename std::pointer_traits<pointer>::template
rebind<const void>;
using difference_type = typename std::pointer_traits<pointer>::difference_type;
using size_type = std::make_unsigned_t<difference_type>;
using propagate_on_container_move_assignment = std::true_type;
[[no_unique_address]]
DefaultAllocator defaultAllocator;
// Default constructor (no state to maintain in this basic example)
Allocator() noexcept {}
// Allocate memory for n objects of type T
T* allocate(std::size_t n)
{
auto s = scale(n);
sLogDebug("core::vectorpool", logVar(n) << logVar(s) << logVar(unscale(s)) << logVar(unscale<T>(s)));
void *result = nullptr;
switch(s) {
case 0: result = nullptr;
break;
case 1: result = StaticAllocator<SizeAllocatorPool<unscale<T>(1),alignof(T)>>::allocator.allocate();
break;
case 2: result = StaticAllocator<SizeAllocatorPool<unscale<T>(2),alignof(T)>>::allocator.allocate();
break;
case 3: result = StaticAllocator<SizeAllocatorPool<unscale<T>(3),alignof(T)>>::allocator.allocate();
break;
case 4: result = StaticAllocator<SizeAllocatorPool<unscale<T>(4),alignof(T)>>::allocator.allocate();
break;
default: result = defaultAllocator.allocate(n);
}
return static_cast<T *>(result);
}
// Deallocate memory for n objects of type T
void deallocate(T* p, std::size_t n)
{
auto s = scale(n);
sLogDebug("core::vectorpool", logVar(n) << logVar(s) << logVar(unscale(s)) << logVar(unscale<T>(s)));
switch(s) {
case 0: return;
case 1: return StaticAllocator<SizeAllocatorPool<unscale<T>(1),alignof(T)>>::allocator.deallocate(p);
case 2: return StaticAllocator<SizeAllocatorPool<unscale<T>(2),alignof(T)>>::allocator.deallocate(p);
case 3: return StaticAllocator<SizeAllocatorPool<unscale<T>(3),alignof(T)>>::allocator.deallocate(p);
case 4: return StaticAllocator<SizeAllocatorPool<unscale<T>(4),alignof(T)>>::allocator.deallocate(p);
default: return defaultAllocator.deallocate(p, n);
}
}
// Copy constructor (not strictly needed for std::vector)
template <typename U>
Allocator(const Allocator<U>&) noexcept {}
// Move constructor (not strictly needed for std::vector)
template <typename U>
Allocator(Allocator<U>&&) noexcept {}
// Copy assignment (not strictly needed for std::vector)
template <typename U>
Allocator& operator=(const Allocator<U>&) noexcept { return *this; }
// Move assignment (not strictly needed for std::vector)
template <typename U>
Allocator& operator=(Allocator<U>&&) noexcept { return *this; }
};
template<class T, class U, class V, class W>
bool operator ==(const Allocator<T, U> &lhs, const Allocator<V, W> &rhs) noexcept
{
return true;
}
template<class T, class U, class V, class W>
bool operator !=(const Allocator<T, U> &lhs, const Allocator<V, W> &rhs) noexcept
{
return !(lhs == rhs);
}
} // namespace
template <typename T, typename DefaultAllocator=std::allocator<T>>
using VectorPool = vectorpool::Allocator<T,DefaultAllocator>;
} // namespace

View File

@@ -0,0 +1,23 @@
// TJP COPYRIGHT HEADER
#include <tjp/core/allocator/AllocatorPool.hpp>
#include <tjp/core/testing/catch.hpp>
namespace tjp::core {
namespace {
SCENARIO("core::allocator")
{
GIVEN("two allocators")
{
AllocatorPool<char> a;
AllocatorPool<int> b;
REQUIRE(a == b);
REQUIRE((a != b) == false);
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,72 @@
// TJP COPYRIGHT HEADER
#include <tjp/core/allocator/ClassAllocatorPool.hpp>
#include <tjp/core/testing/catch.hpp>
#include <tjp/core/iterators/range.hpp>
#include <tjp/core/algorithm/unused.hpp>
#include <tjp/core/containers/Set.hpp>
namespace tjp::core::allocator {
namespace {
SCENARIO("ClassAllocatorPool")
{
GIVEN("allocator")
{
ClassAllocatorPool<int> p;
auto num = 10;
std::set<int *> a0;
for (auto i : range(num))
{
unused(i);
int *a = p.allocate();
a0.insert(a);
}
REQUIRE(p.getSize().freed == 0);
REQUIRE(p.getSize().used == num);
for (auto *a : a0)
p.deallocate(a);
REQUIRE(p.getSize().freed == num);
REQUIRE(p.getSize().used == 0);
std::set<int *> a1;
for (auto i : range(num))
{
unused(i);
int *a = p.allocate();
a1.insert(a);
}
REQUIRE(p.getSize().freed == 0);
REQUIRE(p.getSize().used == num);
REQUIRE(a0 == a1);
for (auto *a : a1)
p.deallocate(a);
REQUIRE(p.getSize().freed == num);
REQUIRE(p.getSize().used == 0);
for (auto i : range(num))
{
p.prune();
}
REQUIRE(p.getSize().freed == 0);
REQUIRE(p.getSize().used == 0);
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,150 @@
// TJP COPYRIGHT HEADER
#include <tjp/core/allocator/SizeAllocatorPool.hpp>
#include <tjp/core/testing/catch.hpp>
#include <tjp/core/testing/NO_WHEN.h>
#include <tjp/core/iterators/range.hpp>
#include <tjp/core/containers/Set.hpp>
#include <tjp/core/algorithm/unused.hpp>
#include <tjp/core/containers/Vector.hpp>
namespace tjp::core::allocator {
namespace {
SCENARIO("SizeAllocatorPool")
{
GIVEN("allocator locking")
{
SizeAllocatorPool_locking_spinlock<1, 1> p;
WHEN("allocate a deallocate and allocate again")
{
auto num = 10;
std::set<void *> a0;
for (auto i : range(num))
{
unused(i);
a0.insert(p.allocate());
}
for (auto *a : a0)
p.deallocate(a);
Set<void *> a1;
for (auto i : range(num))
{
unused(i);
a1.insert(p.allocate());
}
REQUIRE(a0 == a1);
}
WHEN("allocate and deallocate with threads")
{
using namespace std::literals;
Vector<std::thread> threads;
auto numThreads = 128;
threads.reserve(numThreads);
auto numNodesPerThread = 2048;
const std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
auto future = now + 200ms;
for (auto threadID : range(numThreads))
{
threads.emplace_back(
[&]() {
std::this_thread::sleep_until(future);
for (auto i: range(numNodesPerThread))
{
auto *a = p.allocate();
if (!a)
abort();
p.deallocate(a);
}
}
);
}
for (auto &thread: threads)
thread.join();
}
}
NO_GIVEN("allocator lockfree")
{
SizeAllocatorPool_lockfree<1, 1> p;
NO_WHEN("allocate a deallocate and allocate again")
{
auto num = 10;
std::set<void *> a0;
for (auto i : range(num))
{
unused(i);
a0.insert(p.allocate());
}
for (auto *a : a0)
p.deallocate(a);
Set<void *> a1;
for (auto i : range(num))
{
unused(i);
a1.insert(p.allocate());
}
REQUIRE(a0 == a1);
}
WHEN("allocate and deallocate with threads")
{
using namespace std::literals;
Vector<std::thread> threads;
auto numThreads = 128;
threads.reserve(numThreads);
auto numNodesPerThread = 2048;
const std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
auto future = now + 200ms;
for (auto threadID : range(numThreads))
{
threads.emplace_back(
[&]() {
std::this_thread::sleep_until(future);
for (auto i: range(numNodesPerThread))
{
auto *a = p.allocate();
if (!a)
abort();
p.deallocate(a);
}
}
);
}
for (auto &thread: threads)
thread.join();
}
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,207 @@
// TJP COPYRIGHT HEADER
#include <tjp/core/allocator/SizeAllocatorPool.hpp>
#include <tjp/core/testing/catch.hpp>
#include <tjp/core/testing/NO_WHEN.h>
#include <tjp/core/iterators/range.hpp>
#include <tjp/core/containers/Set.hpp>
#include <tjp/core/algorithm/unused.hpp>
#include <tjp/core/containers/Vector.hpp>
namespace tjp::core::allocator_pool {
namespace {
SCENARIO("allocator_pool::Stack")
{
GIVEN("locking")
{
using Stack = Stack_locking<void>;
using Node = Stack::node_type;
Stack p;
WHEN("allocate and deallocate with threads")
{
using namespace std::literals;
Vector<std::thread> threads;
threads.reserve(128);
const std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
auto futurePush = now + 200ms;
auto numThreads = 512;
auto numNodesPerThread = 1024;
auto totalNodes = numThreads * numNodesPerThread;
Vector<Node> memory(totalNodes);
Atomic<size_t> numPushed = 0;
for (auto threadID : range(numThreads))
{
threads.emplace_back(
[&, id=threadID]() {
Node *nodes = memory.data() + (id * numNodesPerThread);
std::this_thread::sleep_until(futurePush);
for (auto i: range(numNodesPerThread))
{
p.push(&nodes[i]);
numPushed++;
}
}
);
}
for (auto &thread: threads)
thread.join();
threads.clear();
auto size = p.size_not_thread_safe();
REQUIRE(numPushed == size);
REQUIRE(size == totalNodes);
WHEN("the nodes are popped")
{
const std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
auto futurePop = now + 200ms;
Atomic<size_t> numPopped = 0;
Vector<Node *> popped(totalNodes);
for (auto threadID : range(numThreads))
{
threads.emplace_back(
[&, id=threadID]() {
Node **popped_ = popped.data () + (id * numNodesPerThread);
std::this_thread::sleep_until(futurePop);
for (auto i : range(numNodesPerThread))
{
popped_[i] = p.pop();
numPopped++;
}
}
);
}
for (auto &thread: threads)
thread.join();
threads.clear();
auto size = p.size_not_thread_safe();
REQUIRE(numPopped == totalNodes);
REQUIRE(size == 0);
THEN("the number of unique nodes matches")
{
Set<Node *> unique(popped.begin(), popped.end());
REQUIRE(unique.size() == totalNodes);
}
}
}
}
NO_GIVEN("allocator lockfree")
{
using Stack = Stack_lockfree<void>;
using Node = Stack::node_type;
Stack p;
WHEN("allocate and deallocate with threads")
{
using namespace std::literals;
Vector<std::thread> threads;
threads.reserve(128);
const std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
auto futurePush = now + 200ms;
auto numThreads = 512;
auto numNodesPerThread = 1024;
auto totalNodes = numThreads * numNodesPerThread;
Vector<Node> memory(totalNodes);
Atomic<size_t> numPushed = 0;
for (auto threadID : range(numThreads))
{
threads.emplace_back(
[&, id=threadID]() {
Node *nodes = memory.data() + (id * numNodesPerThread);
std::this_thread::sleep_until(futurePush);
for (auto i: range(numNodesPerThread))
{
p.push(&nodes[i]);
numPushed++;
}
}
);
}
for (auto &thread: threads)
thread.join();
threads.clear();
auto size = p.size_not_thread_safe();
REQUIRE(numPushed == size);
REQUIRE(size == totalNodes);
WHEN("the nodes are popped")
{
const std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
auto futurePop = now + 200ms;
Atomic<size_t> numPopped = 0;
Vector<Node *> popped(totalNodes);
for (auto threadID : range(numThreads))
{
threads.emplace_back(
[&, id=threadID]() {
Node **popped_ = popped.data () + (id * numNodesPerThread);
std::this_thread::sleep_until(futurePop);
for (auto i : range(numNodesPerThread))
{
popped_[i] = p.pop();
numPopped++;
}
}
);
}
for (auto &thread: threads)
thread.join();
threads.clear();
auto size = p.size_not_thread_safe();
REQUIRE(numPopped == totalNodes);
REQUIRE(size == 0);
THEN("the number of unique nodes matches")
{
Set<Node *> unique(popped.begin(), popped.end());
REQUIRE(unique.size() == totalNodes);
}
}
}
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,179 @@
// TJP COPYRIGHT HEADER
#include <tjp/core/ptr/Ptr.hpp>
#include <tjp/core/ptr/Ptr+IO.h>
#include <tjp/core/testing/catch.hpp>
#include <tjp/core/testing/NO_WHEN.h>
//#include "std/CustomControlBlock.h"
#include <tjp/core/allocator/StrongAllocator.hpp>
namespace tjp::core::allocator {
namespace {
struct A
{
int i;
~A ()
{
i = 2;
}
} ;
struct B
{
static int allocations;
int x = 5;
B () {
allocations++;
}
~B () {
allocations--;
}
};
int B::allocations = 0;
int num_c_deallocations = 0;
int num_c_destroys = 0;
struct C
{
int x= 5;
~C ()
{
num_c_destroys++;
x = 2;
}
} ;
void on_deallocate(C *p)
{
num_c_deallocations++;
}
struct CustomDeleter : StrongThis<CustomDeleter>
{
int called = 0;
template<typename T>
void deallocate(T *t)
{
called++;
delete t;
}
} ;
SCENARIO("core::allocator::ptr")
{
GIVEN("a ptr to normal")
{
WHEN("a ptr is created normal")
{
B::allocations = 0;
auto ptr = strong<B>();
REQUIRE(B::allocations == 1);
ptr = nullptr;
REQUIRE(B::allocations == 0);
}
}
GIVEN("a ptr")
{
WHEN("a ptr is created normal")
{
num_c_deallocations = num_c_destroys = 0;
auto ptr = strong<C>();
ptr = nullptr;
REQUIRE(num_c_deallocations == 1);
REQUIRE(num_c_destroys == 1);
}
WHEN("a ptr is created with a custom deleter")
{
CustomDeleter deleter;
num_c_deallocations = num_c_destroys = 0;
auto ptr = strong_with_delete<C>(
new C(), [&](C *t) { deleter.deallocate(t); }
);
ptr = nullptr;
REQUIRE(num_c_deallocations == 1);
REQUIRE(num_c_destroys == 1);
REQUIRE(deleter.called == 1);
}
WHEN("create a pointer with custom allcoator")
{
typedef AllocatorPool<C> Allocator;
num_c_deallocations = num_c_destroys = 0;
auto ptr = strong_with_allocator<C>(Allocator());
ptr = nullptr;
REQUIRE(num_c_deallocations == 1);
REQUIRE(num_c_destroys == 1);
}
}
GIVEN("a ptr with a custom deleter")
{
CustomDeleter deleter;
WHEN("create a pointer")
{
auto a = strong_with_delete<A>(
new A(), [&](A *t) { deleter.deallocate(t); }
);
WHEN("transfer the pointer elsewherez")
{
auto b = a;
a = nullptr;
WHEN("nullify the pointer")
{
b = nullptr;
REQUIRE(deleter.called == 1);
}
}
}
}
GIVEN("a ptr with an allocator")
{
WHEN("create a pointer")
{
typedef AllocatorPool<A> Allocator;
auto a = strong_with_allocator<A>(Allocator());
WHEN("transfer the pointer elsewherez")
{
auto b = a;
a = nullptr;
WHEN("nullify the pointer")
{
b = nullptr;
}
}
}
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,55 @@
// TJP COPYRIGHT HEADER
#include <tjp/core/allocator/VectorPool.hpp>
#include <tjp/core/testing/catch.hpp>
#include <tjp/core/iterators/range.hpp>
#include <list>
#include <tjp/core/log/Log.h>
#include <tjp/core/log/LogOf.h>
namespace tjp::core::vectorpool {
namespace {
template<typename T>
using MyVector = std::vector<T, Allocator<T>>;
SCENARIO("vectorpool" )
{
xLogActivateStory("core::vectorpool");
MyVector<int> myVec;
GIVEN("scales")
{
for (int i = 0; i < 256; ++i) {
sLogTest("testing", logVar(i) << logVar(scale(i)) << logVar(unscale(scale(i))));
auto s = scale(i);
auto u = unscale(s);
// REQUIRE(u >= i);
}
}
GIVEN("some numbers")
{
for (int i = 0; i < 1024; ++i) {
myVec.push_back(i);
}
sLogDebug("testing", logVar(myVec));
}
GIVEN("a move")
{
MyVector<int> b;
b = std::move(myVec);
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,20 @@
/**
* legal header
*/
#pragma once
#include <tjp/core/types/Types.h>
namespace tjp::core::allocator_pool {
namespace prune {
struct Data
{
size_t id = 0;
} ;
} // namespace
} // namespace

View File

@@ -0,0 +1,20 @@
/**
* legal header
*/
#pragma once
namespace tjp::core::allocator_pool::prune {
struct Registry;
struct Executor
{
virtual ~Executor() {}
virtual void start(Registry *) {}
virtual void stop(Registry *) {}
} ;
} // namespace

View File

@@ -0,0 +1,49 @@
/**
* legal header
*/
#pragma once
#include "Registry.hpp"
#include "Executor.hpp"
#include <tjp/core/assert/debug_assert.h>
#include <thread>
namespace tjp::core::allocator_pool::prune {
template<typename T>
struct ExecutorManual : Executor
{
T algorithm;
Registry *registry = nullptr;
template<typename ...Args>
ExecutorManual(Args&& ...args) :
algorithm(std::forward<Args>(args)...)
{
}
void start(Registry *registry_) override
{
registry = registry_;
}
void stop(Registry *registry_) override
{
debug_assert(registry == registry_);
registry = nullptr;
}
void cycle ()
{
auto lock = lock_of(registry->mutex);
for (auto prunable : registry->prunables)
{
algorithm.cycle(prunable);
}
}
} ;
} // namespace

View File

@@ -0,0 +1,71 @@
/**
* legal header
*/
#pragma once
#include "Registry.hpp"
#include "Executor.hpp"
#include <tjp/core/assert/debug_assert.h>
#include <thread>
namespace tjp::core::allocator_pool::prune {
template<typename T>
struct ExecutorThreaded : Executor
{
T algorithm;
Registry *registry = nullptr;
template<typename ...Args>
ExecutorThreaded(Args&& ...args) :
algorithm(std::forward<Args>(args)...)
{
}
void start(Registry *registry_) override
{
ended = false;
registry = registry_;
executor = std::thread([this]() {
run();
});
}
void stop(Registry *registry_) override
{
ended = true;
debug_assert(registry == registry_);
registry = nullptr;
}
std::thread executor;
bool ended = false;
int delayMS = 1000;
void run()
{
while (!ended)
{
if (registry)
cycle();
std::this_thread::sleep_for(std::chrono::milliseconds(delayMS));
}
}
void cycle ()
{
auto lock = lock_of(registry->mutex);
for (auto prunable : registry->prunables)
{
algorithm.cycle(prunable);
}
}
} ;
} // namespace

View File

@@ -0,0 +1,24 @@
/**
* legal header
*/
#pragma once
#include "Data.hpp"
#include "../Size.h"
namespace tjp::core::allocator_pool {
namespace prune {
struct Prunable
{
Data *pruneData = nullptr;
virtual const Size &getSize() = 0;
virtual void prune() = 0;
} ;
} // namespace
} // namespace

View File

@@ -0,0 +1,60 @@
/**
* legal header
*/
#pragma once
#include "Prunable.hpp"
#include "Registry.hpp"
#include <tjp/core/threads/Lock.hpp>
#include <tjp/core/algorithm/vector_erase_if_value.hpp>
namespace tjp::core::allocator_pool {
namespace prune {
template<typename A>
struct Pruning : Prunable
{
A allocator;
using value_type = typename A::value_type;
template<typename _Tp1> struct rebind { typedef Pruning<_Tp1> other; };
template<typename ...Args>
Pruning(Args&& ...args) :
allocator(std::forward<Args>(args)...)
{
Registry::shared()->insert(this);
}
virtual ~Pruning()
{
Registry::shared()->erase(this);
}
value_type *allocate ()
{
return allocator.allocate();
}
template<typename U>
void deallocate (U *t)
{
return allocator.deallocate(t);
}
const Size &getSize() override
{
return allocator.getSize();
}
void prune() override
{
allocator.prune();
}
};
} // namespace
} // namespace

View File

@@ -0,0 +1,19 @@
/**
* legal header
*/
#include "Registry.hpp"
namespace tjp::core::allocator_pool {
namespace prune {
Registry *Registry::shared()
{
static Registry *registry = new Registry();
return registry;
}
} // namespace
} // namespace

View File

@@ -0,0 +1,52 @@
/**
* legal header
*/
#pragma once
#include "Prunable.hpp"
#include "Executor.hpp"
#include <tjp/core/threads/Lock.hpp>
#include <tjp/core/algorithm/vector_erase_if_value.hpp>
#include <tjp/core/containers/Vector.h>
namespace tjp::core::allocator_pool {
namespace prune {
struct Registry
{
Executor *executor = nullptr;
Mutex mutex;
Vector<Prunable *> prunables;
void insert(Prunable *p)
{
auto lock = lock_of(mutex);
prunables.push_back(p);
}
void erase(Prunable *p)
{
auto lock = lock_of(mutex);
vector_erase_if_value(prunables, [p](auto *v) { return v == p; });
}
void setAlgorithm(Executor *executor_)
{
if (executor)
executor->stop(this);
executor = executor_;
executor->start(this);
}
static Registry *shared();
} ;
} // namespace
} // namespace

View File

@@ -0,0 +1,64 @@
/**
* legal header
*/
#include <tjp/core/allocator/prune/Pruning.hpp>
#include <tjp/core/allocator/prune/Registry.hpp>
#include <tjp/core/allocator/prune/ExecutorManual.hpp>
#include <tjp/core/allocator/prune/algorithm/Averaging.hpp>
#include <tjp/core/testing/catch.hpp>
#include <tjp/core/iterators/range.hpp>
#include <tjp/core/containers/Set.hpp>
#include <tjp/core/algorithm/unused.hpp>
#include <tjp/core/allocator/ClassAllocatorPool.hpp>
namespace tjp::core::allocator_pool::prune {
namespace {
SCENARIO("core::allocator_pool::prune::Pruning")
{
GIVEN("allocator")
{
auto *pruneExecutor = new ExecutorManual<algorithm::Averaging>(50, 1.4);
Registry::shared()->setAlgorithm(pruneExecutor);
Pruning<ClassAllocatorPool<int>> p;
auto &size = p.getSize();
WHEN("allocate a deallocate and allocate again")
{
auto num = 100;
std::set<int *> a0;
for (auto i : range(num))
{
unused(i);
a0.insert(p.allocate());
}
REQUIRE(size.freed == 0);
REQUIRE(size.used == num);
for (auto _: range(100))
pruneExecutor->cycle();
for (auto *a : a0)
p.deallocate(a);
REQUIRE(size.used == 0);
REQUIRE(size.freed == num);
for (auto _: range(100))
pruneExecutor->cycle();
REQUIRE(size.used == 0);
REQUIRE(size.freed < num);
}
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,96 @@
/**
* legal header
*/
#pragma once
#include "../Data.hpp"
#include "../../Size.hpp"
#include <tjp/core/log/Log.h>
#include <tjp/core/log/LogOf.h>
#include <algorithm>
#include <cmath>
namespace tjp::core::allocator_pool::prune::algorithm {
struct AverageData : Data
{
public:
using Real = double;
Real windowSize;
Real m;
Real averageUsed = 0;
size_t threshold = 0;
public:
static const size_t ID = 0xABCD;
AverageData(size_t windowSize_, float m_) :
windowSize(windowSize_),
m(m_)
{
id = ID;
}
void sample (size_t freed, size_t used)
{
if (used > averageUsed)
averageUsed = used;
else
averageUsed -= (averageUsed - used) / windowSize;
threshold = std::max((size_t)averageUsed, used);
threshold = std::round(m * threshold);
}
bool shouldPrune(size_t freed, size_t used)
{
return
freed > 0 &&
used + freed > threshold;
}
} ;
struct Averaging
{
using Real = double;
size_t windowSize;
Real m;
Averaging(size_t windowSize_, Real m_) :
windowSize(windowSize_),
m(m_)
{
}
void cycle(Prunable *pool)
{
if (!pool->pruneData || pool->pruneData->id != AverageData::ID)
{
delete pool->pruneData;
pool->pruneData = new AverageData(windowSize, m);
}
auto data = (AverageData *)pool->pruneData;
auto &size = pool->getSize();
data->sample(size.freed, size.used);
auto pruned = 0;
while (data->shouldPrune(size.freed, size.used))
{
pool->prune();
pruned++;
}
sLogDebugIf((size.freed + size.used > 0), "core::allocator_pool", logVar(pool) << logVar(size.freed) << logVar(size.used) << logVar(data->averageUsed) << logVar(data->threshold) << logVar(pruned));
}
} ;
} // namespace

View File

@@ -0,0 +1,23 @@
/**
* legal header
*/
#pragma once
#include "AllocatorPoolPrunable.hpp"
namespace tjp::core::allocator_pool {
namespace algorithm {
struct None
{
public:
void cycle(AllocatorPoolPrunable *)
{
}
} ;
} // namespace
} // namespace

View File

@@ -0,0 +1,129 @@
/**
* legal header
*/
#pragma once
#include "../threads/Lock.hpp"
#include "Size.hpp"
namespace tjp::core::allocator_pool {
namespace sizeallocatorpool_locking {
template<size_t allocationSize, size_t alignment, typename C=void>
class SizeAllocatorPool
{
public:
struct Allocation
{
char t[allocationSize];
Allocation *next;
} __attribute__ ((aligned(alignment)));
typedef Mutex MutexT;
static constexpr size_t multiples = 1;
private:
MutexT mutex;
Allocation *head = nullptr;
Size size;
public:
SizeAllocatorPool ()
{
static_assert(offsetof(Allocation, t) == 0);
}
~SizeAllocatorPool ()
{
while (head)
{
auto a = pop();
if (a)
delete a;
}
size.used = size.freed = 0;
}
Allocation *pop()
{
auto lock = lock_of(mutex);
Allocation *a = head;
if (a != nullptr)
head = a->next;
return a;
}
void push(Allocation *a)
{
auto lock = lock_of(mutex);
a->next = head;
head = a;
}
void *allocate ()
{
// sLogDebug("debug", logVar(allocationSize) << logVar(alignment));
auto a = pop();
if (a)
{
size.used++;
size.freed--;
return (void *)&a->t;
}
size.used++;
auto allocation = new Allocation();
return &allocation->t;
}
void deallocate(void *o)
{
// sLogDebug("debug", logVar(allocationSize) << logVar(alignment));
if (!o)
return;
static_assert(offsetof(Allocation, t) == 0);
auto allocation = (Allocation *)o;
push(allocation);
size.used--;
size.freed++;
}
const Size &getSize ()
{
return size;
}
void prune()
{
auto lock = lock_of(mutex);
if (head)
{
auto next = head->next;
delete head;
head = next;
size.freed--;
}
}
} ;
} // namespace
} // namespace