mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-07 01:06:45 +08:00
Add $new
This commit is contained in:
parent
edc1e80585
commit
89e9f87f36
@ -8,18 +8,173 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "libipc/imp/aligned.h"
|
#include "libipc/imp/aligned.h"
|
||||||
#include "libipc/imp/uninitialized.h"
|
#include "libipc/imp/uninitialized.h"
|
||||||
#include "libipc/imp/byte.h"
|
#include "libipc/imp/byte.h"
|
||||||
#include "libipc/imp/detect_plat.h"
|
#include "libipc/imp/detect_plat.h"
|
||||||
|
|
||||||
#include "libipc/imp/export.h"
|
#include "libipc/imp/export.h"
|
||||||
|
#include "libipc/mem/memory_resource.h"
|
||||||
|
#include "libipc/mem/block_pool.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace mem {
|
namespace mem {
|
||||||
|
|
||||||
|
/// \brief Defines the memory block collector interface.
|
||||||
|
class LIBIPC_EXPORT block_collector {
|
||||||
|
public:
|
||||||
|
virtual ~block_collector() noexcept = default;
|
||||||
|
virtual void recycle(void *p, std::size_t bytes, std::size_t alignment) noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(LIBIPC_CPP_17)
|
||||||
|
using get_block_collector_t = block_collector *(*)() noexcept;
|
||||||
|
#else
|
||||||
|
using get_block_collector_t = block_collector *(*)();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static constexpr std::size_t regular_head_size
|
||||||
|
= round_up(sizeof(get_block_collector_t), alignof(std::max_align_t));
|
||||||
|
|
||||||
|
/// \brief Select the incremental level based on the size.
|
||||||
|
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
|
||||||
|
return (s <= 128 ) ? 0 :
|
||||||
|
(s <= 1024 ) ? 1 :
|
||||||
|
(s <= 8192 ) ? 2 :
|
||||||
|
(s <= 65536) ? 3 : 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Calculates the appropriate memory block size based on the increment level and size.
|
||||||
|
constexpr inline std::size_t regular_sizeof_impl(std::size_t l, std::size_t s) noexcept {
|
||||||
|
return (l == 0) ? round_up<std::size_t>(s, regular_head_size) :
|
||||||
|
(l == 1) ? round_up<std::size_t>(s, 128 ) :
|
||||||
|
(l == 2) ? round_up<std::size_t>(s, 1024) :
|
||||||
|
(l == 3) ? round_up<std::size_t>(s, 8192) : (std::numeric_limits<std::size_t>::max)();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Calculates the appropriate memory block size based on the size.
|
||||||
|
constexpr inline std::size_t regular_sizeof_impl(std::size_t s) noexcept {
|
||||||
|
return regular_sizeof_impl(regular_level(s), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Calculates the appropriate memory block size based on the specific type.
|
||||||
|
template <typename T>
|
||||||
|
constexpr inline std::size_t regular_sizeof() noexcept {
|
||||||
|
return regular_sizeof_impl(regular_head_size + sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Use block pools to handle memory less than 64K.
|
||||||
|
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||||
|
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion> {
|
||||||
|
public:
|
||||||
|
void *allocate(std::size_t /*bytes*/, std::size_t /*alignment*/) noexcept {
|
||||||
|
return block_pool<BlockSize, BlockPoolExpansion>::allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(void *p, std::size_t /*bytes*/, std::size_t /*alignment*/) noexcept {
|
||||||
|
block_pool<BlockSize, BlockPoolExpansion>::deallocate(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Use `new`/`delete` to handle memory larger than 64K.
|
||||||
|
template <std::size_t BlockSize>
|
||||||
|
class block_resource_base<BlockSize, 0> : public new_delete_resource {
|
||||||
|
public:
|
||||||
|
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
|
||||||
|
return new_delete_resource::allocate(regular_head_size + bytes, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
|
||||||
|
new_delete_resource::deallocate(p, regular_head_size + bytes, alignment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Defines block pool memory resource based on block pool.
|
||||||
|
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
|
||||||
|
class block_pool_resource : public block_resource_base<BlockSize, BlockPoolExpansion>
|
||||||
|
, public block_collector {
|
||||||
|
|
||||||
|
using base_t = block_resource_base<BlockSize, BlockPoolExpansion>;
|
||||||
|
|
||||||
|
void recycle(void *p, std::size_t bytes, std::size_t alignment) noexcept override {
|
||||||
|
base_t::deallocate(p, bytes, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static block_collector *get() noexcept {
|
||||||
|
thread_local block_pool_resource instance;
|
||||||
|
return &instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept {
|
||||||
|
void *p = base_t::allocate(bytes, alignment);
|
||||||
|
*static_cast<get_block_collector_t *>(p) = get;
|
||||||
|
return static_cast<byte *>(p) + regular_head_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept {
|
||||||
|
p = static_cast<byte *>(p) - regular_head_size;
|
||||||
|
auto g = *static_cast<get_block_collector_t *>(p);
|
||||||
|
if (g == get) {
|
||||||
|
base_t::deallocate(p, bytes, alignment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g()->recycle(p, bytes, alignment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Different increment levels match different chunk sizes.
|
||||||
|
/// 512 means that 512 consecutive memory blocks are allocated at a time.
|
||||||
|
template <std::size_t L>
|
||||||
|
constexpr std::size_t block_pool_expansion = 0;
|
||||||
|
|
||||||
|
template <> constexpr std::size_t block_pool_expansion<0> = 512;
|
||||||
|
template <> constexpr std::size_t block_pool_expansion<1> = 256;
|
||||||
|
template <> constexpr std::size_t block_pool_expansion<2> = 128;
|
||||||
|
template <> constexpr std::size_t block_pool_expansion<3> = 64;
|
||||||
|
|
||||||
|
/// \brief Matches the appropriate memory block resource based on the specified type.
|
||||||
|
template <typename T, std::size_t N = regular_sizeof<T>(), std::size_t L = regular_level(N)>
|
||||||
|
auto *get_regular_resource() noexcept {
|
||||||
|
using block_poll_resource_t = block_pool_resource<N, block_pool_expansion<L>>;
|
||||||
|
return dynamic_cast<block_poll_resource_t *>(block_poll_resource_t::get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Creates an object based on the specified type and parameters with block pool resource.
|
||||||
|
/// \note This function is thread-safe.
|
||||||
|
template <typename T, typename... A>
|
||||||
|
T *$new(A &&... args) noexcept {
|
||||||
|
auto *res = get_regular_resource<T>();
|
||||||
|
if (res == nullptr) return nullptr;
|
||||||
|
return construct<T>(res->allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area.
|
||||||
|
/// \note This function is thread-safe. If the pointer type passed in is different from `$new`,
|
||||||
|
/// additional performance penalties may be incurred.
|
||||||
|
template <typename T>
|
||||||
|
void $delete(T *p) noexcept {
|
||||||
|
if (p == nullptr) return;
|
||||||
|
destroy(p);
|
||||||
|
auto *res = get_regular_resource<T>();
|
||||||
|
if (res == nullptr) return;
|
||||||
|
#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015)
|
||||||
|
res->deallocate(p, sizeof(T), alignof(T));
|
||||||
|
#else
|
||||||
|
// `alignof` of vs2015 requires that type must be able to be instantiated.
|
||||||
|
res->deallocate(p, sizeof(T));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \brief The destruction policy used by std::unique_ptr.
|
||||||
|
/// \see https://en.cppreference.com/w/cpp/memory/default_delete
|
||||||
|
struct deleter {
|
||||||
|
template <typename T>
|
||||||
|
void operator()(T *p) const noexcept {
|
||||||
|
$delete(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace mem
|
} // namespace mem
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
169
test/mem/test_mem_new.cpp
Normal file
169
test/mem/test_mem_new.cpp
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "test.h"
|
||||||
|
|
||||||
|
#include "libipc/mem/new.h"
|
||||||
|
|
||||||
|
TEST(new, regular_sizeof) {
|
||||||
|
ASSERT_EQ(ipc::mem::regular_sizeof<std::int8_t >(), ipc::mem::regular_head_size + alignof(std::max_align_t));
|
||||||
|
ASSERT_EQ(ipc::mem::regular_sizeof<std::int16_t>(), ipc::mem::regular_head_size + alignof(std::max_align_t));
|
||||||
|
ASSERT_EQ(ipc::mem::regular_sizeof<std::int32_t>(), ipc::mem::regular_head_size + alignof(std::max_align_t));
|
||||||
|
ASSERT_EQ(ipc::mem::regular_sizeof<std::int64_t>(), ipc::mem::regular_head_size + alignof(std::max_align_t));
|
||||||
|
|
||||||
|
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 10 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 10 , alignof(std::max_align_t)));
|
||||||
|
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 100 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 100 , alignof(std::max_align_t)));
|
||||||
|
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 1000 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 1000 , 128));
|
||||||
|
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 10000 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 10000, 8192));
|
||||||
|
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 100000>>()), (std::numeric_limits<std::size_t>::max)());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(new, new) {
|
||||||
|
auto p = ipc::mem::$new<int>();
|
||||||
|
ASSERT_NE(p, nullptr);
|
||||||
|
*p = -1;
|
||||||
|
ASSERT_EQ(*p, -1);
|
||||||
|
ipc::mem::$delete(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(new, new_value) {
|
||||||
|
auto p = ipc::mem::$new<int>((std::numeric_limits<int>::max)());
|
||||||
|
ASSERT_NE(p, nullptr);
|
||||||
|
ASSERT_EQ(*p, (std::numeric_limits<int>::max)());
|
||||||
|
ipc::mem::$delete(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <std::size_t Pts, std::size_t N>
|
||||||
|
void test_new$array() {
|
||||||
|
std::array<void *, Pts> pts;
|
||||||
|
using T = std::array<char, N>;
|
||||||
|
for (int i = 0; i < (int)pts.size(); ++i) {
|
||||||
|
auto p = ipc::mem::$new<T>();
|
||||||
|
pts[i] = p;
|
||||||
|
std::memset(p, i, sizeof(T));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < (int)pts.size(); ++i) {
|
||||||
|
T tmp;
|
||||||
|
std::memset(&tmp, i, sizeof(T));
|
||||||
|
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(T)), 0);
|
||||||
|
ipc::mem::$delete(static_cast<T *>(pts[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(new, new_array) {
|
||||||
|
test_new$array<1000, 10>();
|
||||||
|
test_new$array<1000, 100>();
|
||||||
|
test_new$array<1000, 1000>();
|
||||||
|
test_new$array<1000, 10000>();
|
||||||
|
test_new$array<1000, 100000>();
|
||||||
|
// test_new$array<1000, 1000000>();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int construct_count__ = 0;
|
||||||
|
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
virtual ~Base() = default;
|
||||||
|
virtual int get() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
Derived(int value) : value_(value) {
|
||||||
|
construct_count__ = value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Derived() override {
|
||||||
|
construct_count__ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get() const override { return value_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived64K : public Derived {
|
||||||
|
public:
|
||||||
|
using Derived::Derived;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<char, 65536> padding_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(new, delete_poly) {
|
||||||
|
Base *p = ipc::mem::$new<Derived>(-1);
|
||||||
|
ASSERT_NE(p, nullptr);
|
||||||
|
ASSERT_EQ(p->get(), -1);
|
||||||
|
ASSERT_EQ(construct_count__, -1);
|
||||||
|
ipc::mem::$delete(p);
|
||||||
|
ASSERT_EQ(construct_count__, 0);
|
||||||
|
|
||||||
|
ASSERT_EQ(p, ipc::mem::$new<Derived>((std::numeric_limits<int>::max)()));
|
||||||
|
ASSERT_EQ(p->get(), (std::numeric_limits<int>::max)());
|
||||||
|
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
|
||||||
|
ipc::mem::$delete(p);
|
||||||
|
ASSERT_EQ(construct_count__, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(new, delete_poly64k) {
|
||||||
|
Base *p = ipc::mem::$new<Derived64K>(-1);
|
||||||
|
ASSERT_NE(p, nullptr);
|
||||||
|
ASSERT_EQ(p->get(), -1);
|
||||||
|
ASSERT_EQ(construct_count__, -1);
|
||||||
|
ipc::mem::$delete(p);
|
||||||
|
ASSERT_EQ(construct_count__, 0);
|
||||||
|
|
||||||
|
Base *q = ipc::mem::$new<Derived64K>((std::numeric_limits<int>::max)());
|
||||||
|
ASSERT_EQ(q->get(), (std::numeric_limits<int>::max)());
|
||||||
|
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
|
||||||
|
ipc::mem::$delete(q);
|
||||||
|
ASSERT_EQ(construct_count__, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(new, delete_null) {
|
||||||
|
Base *p = nullptr;
|
||||||
|
ipc::mem::$delete(p);
|
||||||
|
SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(new, multi_thread) {
|
||||||
|
std::array<std::thread, 16> threads;
|
||||||
|
for (auto &t : threads) {
|
||||||
|
t = std::thread([] {
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
auto p = ipc::mem::$new<int>();
|
||||||
|
*p = i;
|
||||||
|
ipc::mem::$delete(p);
|
||||||
|
}
|
||||||
|
std::array<void *, 10000> pts;
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
auto p = ipc::mem::$new<std::array<char, 10>>();
|
||||||
|
pts[i] = p;
|
||||||
|
std::memset(p, i, sizeof(std::array<char, 10>));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
std::array<char, 10> tmp;
|
||||||
|
std::memset(&tmp, i, sizeof(std::array<char, 10>));
|
||||||
|
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(std::array<char, 10>)), 0);
|
||||||
|
ipc::mem::$delete(static_cast<std::array<char, 10> *>(pts[i]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (auto &t : threads) {
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
SUCCEED();
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user