mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 16:56:45 +08:00
modify test cases of mem;
sync_pool_alloc => async_pool_alloc, synchronized => async_wrapper; statical => static_wrapper; optimize ipc::mem::allocator; use std::hardware_destructive_interference_size for cache_line_size (TBD); simplified codes.
This commit is contained in:
parent
0beb0b4a13
commit
36d85e4b41
BIN
performance.xlsx
BIN
performance.xlsx
Binary file not shown.
@ -12,10 +12,6 @@
|
||||
namespace ipc {
|
||||
namespace circ {
|
||||
|
||||
enum {
|
||||
cache_line_size = 64
|
||||
};
|
||||
|
||||
using u1_t = ipc::uint_t<8>;
|
||||
using u2_t = ipc::uint_t<32>;
|
||||
|
||||
|
||||
0
src/ipc.cpp
Executable file → Normal file
0
src/ipc.cpp
Executable file → Normal file
@ -2,9 +2,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "def.h"
|
||||
@ -31,10 +28,6 @@ public:
|
||||
static void free(void* p, std::size_t /*size*/) {
|
||||
free(p);
|
||||
}
|
||||
|
||||
static std::size_t size_of(void* /*p*/) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
@ -54,8 +47,8 @@ public:
|
||||
void free(void* /*p*/) {}
|
||||
void free(void* /*p*/, std::size_t) {}
|
||||
|
||||
constexpr std::size_t size_of(void* /*p*/) const {
|
||||
return 0;
|
||||
void swap(scope_alloc_base & rhs) {
|
||||
std::swap(this->list_, rhs.list_);
|
||||
}
|
||||
};
|
||||
|
||||
@ -64,6 +57,7 @@ public:
|
||||
template <typename AllocP = static_alloc>
|
||||
class scope_alloc : public detail::scope_alloc_base {
|
||||
public:
|
||||
using base_t = detail::scope_alloc_base;
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
@ -80,7 +74,7 @@ public:
|
||||
public:
|
||||
void swap(scope_alloc& rhs) {
|
||||
std::swap(this->alloc_, rhs.alloc_);
|
||||
std::swap(this->list_ , rhs.list_);
|
||||
base_t::swap(rhs);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
@ -106,6 +100,14 @@ public:
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <std::size_t BlockSize>
|
||||
struct fixed_expand_policy {
|
||||
static std::size_t next(std::size_t & e) {
|
||||
static constexpr std::size_t basic_size = (ipc::detail::max)(BlockSize, static_cast<std::size_t>(2048));
|
||||
return basic_size * (e *= 2);
|
||||
}
|
||||
};
|
||||
|
||||
class fixed_alloc_base {
|
||||
protected:
|
||||
std::size_t init_expand_;
|
||||
@ -143,7 +145,7 @@ public:
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <std::size_t BlockSize, typename AllocP = scope_alloc<>>
|
||||
template <std::size_t BlockSize, template <std::size_t> class ExpandP = detail::fixed_expand_policy, typename AllocP = scope_alloc<>>
|
||||
class fixed_alloc : public detail::fixed_alloc_base {
|
||||
public:
|
||||
using base_t = detail::fixed_alloc_base;
|
||||
@ -160,9 +162,9 @@ private:
|
||||
if (this->cursor_ != nullptr) {
|
||||
return this->cursor_;
|
||||
}
|
||||
auto p = this->node_p(this->cursor_ = alloc_.alloc(block_size));
|
||||
auto size = alloc_.size_of(p);
|
||||
if (size > 0) for (std::size_t i = 0; i < (size / block_size) - 1; ++i)
|
||||
auto size = ExpandP<block_size>::next(this->init_expand_);
|
||||
auto p = this->node_p(this->cursor_ = alloc_.alloc(size));
|
||||
for (std::size_t i = 0; i < (size / block_size) - 1; ++i)
|
||||
p = this->node_p((*p) = reinterpret_cast<byte_t*>(p) + block_size);
|
||||
(*p) = nullptr;
|
||||
return this->cursor_;
|
||||
@ -187,10 +189,6 @@ public:
|
||||
this->init(this->init_expand_);
|
||||
}
|
||||
|
||||
constexpr std::size_t size_of(void* /*p*/) const {
|
||||
return block_size;
|
||||
}
|
||||
|
||||
void* alloc() {
|
||||
void* p = try_expand();
|
||||
this->cursor_ = this->next(p);
|
||||
@ -202,14 +200,5 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// page memory allocation
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
using page_alloc = fixed_alloc<4096>;
|
||||
|
||||
template <std::size_t BlockSize>
|
||||
using page_fixed_alloc = fixed_alloc<BlockSize, page_alloc>;
|
||||
|
||||
} // namespace mem
|
||||
} // namespace ipc
|
||||
|
||||
@ -13,14 +13,6 @@ enum : std::size_t {
|
||||
base_size = sizeof(void*)
|
||||
};
|
||||
|
||||
template <std::size_t Size, template <std::size_t> class FixedAlloc>
|
||||
struct fixed {
|
||||
static auto& pool() {
|
||||
static FixedAlloc<Size> pool;
|
||||
return pool;
|
||||
}
|
||||
};
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
constexpr std::size_t classify(std::size_t size) {
|
||||
constexpr std::size_t mapping[] = {
|
||||
@ -51,18 +43,18 @@ inline std::size_t classify(std::size_t size) {
|
||||
template <template <std::size_t> class Fixed, typename F>
|
||||
decltype(auto) choose(std::size_t size, F&& f) {
|
||||
return ipc::detail::static_switch<32>(classify(size), [&f](auto index) {
|
||||
return f(Fixed<(decltype(index)::value + 1) * base_size>::pool());
|
||||
return f(Fixed<(decltype(index)::value + 1) * base_size>::instance());
|
||||
}, [&f] {
|
||||
return f(static_alloc{});
|
||||
});
|
||||
}
|
||||
|
||||
template <template <std::size_t> class Fixed>
|
||||
class pool_alloc {
|
||||
class fixed_alloc_policy {
|
||||
public:
|
||||
static void clear() {
|
||||
ipc::detail::static_for<32>([](auto index) {
|
||||
Fixed<(decltype(index)::value + 1) * base_size>::pool().clear();
|
||||
Fixed<(decltype(index)::value + 1) * base_size>::instance().clear();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
10
src/memory/resource.h
Executable file → Normal file
10
src/memory/resource.h
Executable file → Normal file
@ -19,15 +19,11 @@ namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
template <std::size_t Size>
|
||||
using sync_fixed_alloc = mem::synchronized<page_fixed_alloc<Size>>;
|
||||
|
||||
template <std::size_t Size>
|
||||
using sync_fixed = mem::detail::fixed<Size, sync_fixed_alloc>;
|
||||
|
||||
using sync_pool_alloc = mem::detail::pool_alloc<sync_fixed>;
|
||||
using static_async_fixed = mem::static_wrapper<mem::async_wrapper<mem::fixed_alloc<Size>>>;
|
||||
using async_pool_alloc = mem::detail::fixed_alloc_policy<static_async_fixed>;
|
||||
|
||||
template <typename T>
|
||||
using allocator = allocator_wrapper<T, sync_pool_alloc>;
|
||||
using allocator = allocator_wrapper<T, async_pool_alloc>;
|
||||
|
||||
} // namespace mem
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
@ -128,76 +128,62 @@ constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_w
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP>
|
||||
class synchronized {
|
||||
class async_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
private:
|
||||
spin_lock lc_;
|
||||
std::multimap<std::size_t, alloc_policy*> allocs_;
|
||||
class alloc_proxy : public alloc_policy {
|
||||
async_wrapper * w_ = nullptr;
|
||||
|
||||
struct alloc_t {
|
||||
synchronized* t_;
|
||||
std::size_t s_ = 0;
|
||||
alloc_policy* a_ = nullptr;
|
||||
public:
|
||||
alloc_proxy(alloc_proxy&& rhs)
|
||||
: alloc_policy(std::move(rhs))
|
||||
{}
|
||||
|
||||
alloc_t(synchronized* t)
|
||||
: t_ { t } {
|
||||
{
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(t_->lc_);
|
||||
auto it = t_->allocs_.begin();
|
||||
if (it != t_->allocs_.end()) {
|
||||
std::tie(s_, a_) = *it;
|
||||
t_->allocs_.erase(it);
|
||||
}
|
||||
}
|
||||
if (a_ == nullptr) {
|
||||
a_ = new alloc_policy;
|
||||
alloc_proxy(async_wrapper* w)
|
||||
: alloc_policy(), w_(w) {
|
||||
if (w_ == nullptr) return;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(w_->master_lock_);
|
||||
if (!w_->master_allocs_.empty()) {
|
||||
alloc_policy::swap(w_->master_allocs_.back());
|
||||
w_->master_allocs_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
~alloc_t() {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(t_->lc_);
|
||||
t_->allocs_.emplace(s_, a_);
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
void* p = a_->alloc(size);
|
||||
if ((p != nullptr) && (s_ > 0)) {
|
||||
--s_;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void free(void* p) {
|
||||
a_->free(p);
|
||||
++s_;
|
||||
~alloc_proxy() {
|
||||
if (w_ == nullptr) return;
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(w_->master_lock_);
|
||||
w_->master_allocs_.emplace_back(std::move(*this));
|
||||
}
|
||||
};
|
||||
|
||||
auto& alc_info() {
|
||||
static tls::pointer<alloc_t> alc;
|
||||
return *alc.create(this);
|
||||
friend class alloc_proxy;
|
||||
|
||||
spin_lock master_lock_;
|
||||
std::vector<alloc_proxy> master_allocs_;
|
||||
|
||||
auto& get_alloc() {
|
||||
static tls::pointer<alloc_proxy> tls_alc;
|
||||
return *tls_alc.create(this);
|
||||
}
|
||||
|
||||
public:
|
||||
~synchronized() {
|
||||
~async_wrapper() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(lc_);
|
||||
for (auto& pair : allocs_) {
|
||||
delete pair.second;
|
||||
}
|
||||
IPC_UNUSED_ auto guard = ipc::detail::unique_lock(master_lock_);
|
||||
master_allocs_.clear();
|
||||
}
|
||||
|
||||
void* alloc(std::size_t size) {
|
||||
return alc_info().alloc(size);
|
||||
return get_alloc().alloc(size);
|
||||
}
|
||||
|
||||
void free(void* p) {
|
||||
alc_info().free(p);
|
||||
get_alloc().free(p);
|
||||
}
|
||||
|
||||
void free(void* p, std::size_t /*size*/) {
|
||||
@ -210,7 +196,7 @@ public:
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename AllocP>
|
||||
class statical {
|
||||
class static_wrapper {
|
||||
public:
|
||||
using alloc_policy = AllocP;
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <new>
|
||||
|
||||
#include "def.h"
|
||||
#include "export.h"
|
||||
@ -137,5 +138,14 @@ inline void static_for(F&& f) {
|
||||
static_for(std::make_index_sequence<N>{}, std::forward<F>(f));
|
||||
}
|
||||
|
||||
// Minimum offset between two objects to avoid false sharing.
|
||||
enum {
|
||||
// #if __cplusplus >= 201703L
|
||||
// cache_line_size = std::hardware_destructive_interference_size
|
||||
// #else /*__cplusplus < 201703L*/
|
||||
cache_line_size = 64
|
||||
// #endif/*__cplusplus < 201703L*/
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ipc
|
||||
|
||||
@ -6,15 +6,15 @@ namespace ipc {
|
||||
namespace mem {
|
||||
|
||||
void pool_alloc::clear() {
|
||||
sync_pool_alloc::clear();
|
||||
async_pool_alloc::clear();
|
||||
}
|
||||
|
||||
void* pool_alloc::alloc(std::size_t size) {
|
||||
return sync_pool_alloc::alloc(size);
|
||||
return async_pool_alloc::alloc(size);
|
||||
}
|
||||
|
||||
void pool_alloc::free(void* p, std::size_t size) {
|
||||
sync_pool_alloc::free(p, size);
|
||||
async_pool_alloc::free(p, size);
|
||||
}
|
||||
|
||||
} // namespace mem
|
||||
|
||||
@ -26,8 +26,8 @@ struct prod_cons_impl<wr<relat::single, relat::single, trans::unicast>> {
|
||||
std::aligned_storage_t<DataSize, AlignSize> data_ {};
|
||||
};
|
||||
|
||||
alignas(circ::cache_line_size) std::atomic<circ::u2_t> rd_; // read index
|
||||
alignas(circ::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> rd_; // read index
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
|
||||
constexpr circ::u2_t cursor() const noexcept {
|
||||
return 0;
|
||||
@ -96,7 +96,7 @@ struct prod_cons_impl<wr<relat::multi , relat::multi, trans::unicast>>
|
||||
std::atomic<flag_t> f_ct_ { 0 }; // commit flag
|
||||
};
|
||||
|
||||
alignas(circ::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
|
||||
template <typename W, typename F, typename E>
|
||||
bool push(W* /*wrapper*/, F&& f, E* elems) {
|
||||
@ -182,7 +182,7 @@ struct prod_cons_impl<wr<relat::single, relat::multi, trans::broadcast>> {
|
||||
std::atomic<rc_t> rc_ { 0 }; // read-counter
|
||||
};
|
||||
|
||||
alignas(circ::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> wt_; // write index
|
||||
|
||||
circ::u2_t cursor() const noexcept {
|
||||
return wt_.load(std::memory_order_acquire);
|
||||
@ -276,7 +276,7 @@ struct prod_cons_impl<wr<relat::multi , relat::multi, trans::broadcast>> {
|
||||
std::atomic<flag_t> f_ct_ { 0 }; // commit flag
|
||||
};
|
||||
|
||||
alignas(circ::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
alignas(detail::cache_line_size) std::atomic<circ::u2_t> ct_; // commit index
|
||||
|
||||
circ::u2_t cursor() const noexcept {
|
||||
return ct_.load(std::memory_order_acquire);
|
||||
|
||||
@ -238,7 +238,7 @@ private slots:
|
||||
void test_prod_cons_1v3();
|
||||
void test_prod_cons_performance();
|
||||
void test_queue();
|
||||
} /*unit__*/;
|
||||
} unit__;
|
||||
|
||||
#include "test_circ.moc"
|
||||
|
||||
|
||||
@ -23,14 +23,15 @@ private slots:
|
||||
void initTestCase();
|
||||
|
||||
void test_alloc_free();
|
||||
void test_linear();
|
||||
} /*unit__*/;
|
||||
void test_static();
|
||||
void test_pool();
|
||||
} unit__;
|
||||
|
||||
#include "test_mem.moc"
|
||||
|
||||
constexpr int DataMin = sizeof(void*);
|
||||
constexpr int DataMax = sizeof(void*) * 16;
|
||||
constexpr int LoopCount = 1000000;
|
||||
constexpr int LoopCount = 10000000;
|
||||
|
||||
std::vector<std::size_t> sizes__;
|
||||
|
||||
@ -106,7 +107,7 @@ void benchmark_alloc() {
|
||||
|
||||
void Unit::test_alloc_free() {
|
||||
benchmark_alloc<ipc::mem::static_alloc>();
|
||||
benchmark_alloc<ipc::mem::sync_pool_alloc>();
|
||||
benchmark_alloc<ipc::mem::async_pool_alloc>();
|
||||
}
|
||||
|
||||
template <typename AllocT, typename ModeT, int ThreadsN>
|
||||
@ -145,7 +146,7 @@ void benchmark_alloc() {
|
||||
}
|
||||
}
|
||||
if ((fini.fetch_add(1, std::memory_order_relaxed) + 1) == ThreadsN) {
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount);
|
||||
sw.print_elapsed<1>(DataMin, DataMax, LoopCount * ThreadsN);
|
||||
}
|
||||
}};
|
||||
++pid;
|
||||
@ -170,15 +171,16 @@ struct test_performance<AllocT, ModeT, 1> {
|
||||
}
|
||||
};
|
||||
|
||||
void Unit::test_linear() {
|
||||
test_performance<ipc::mem::static_alloc, alloc_random, 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc , alloc_random, 8>::start();
|
||||
|
||||
test_performance<ipc::mem::static_alloc, alloc_LIFO , 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc , alloc_LIFO , 8>::start();
|
||||
|
||||
void Unit::test_static() {
|
||||
test_performance<ipc::mem::static_alloc, alloc_FIFO , 8>::start();
|
||||
test_performance<ipc::mem::static_alloc, alloc_LIFO , 8>::start();
|
||||
test_performance<ipc::mem::static_alloc, alloc_random, 8>::start();
|
||||
}
|
||||
|
||||
void Unit::test_pool() {
|
||||
test_performance<ipc::mem::pool_alloc, alloc_FIFO , 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc, alloc_LIFO , 8>::start();
|
||||
test_performance<ipc::mem::pool_alloc, alloc_random, 8>::start();
|
||||
}
|
||||
|
||||
} // internal-linkage
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user