mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 16:56:45 +08:00
pool_alloc (TBD)
This commit is contained in:
parent
812d4b3be5
commit
8c75f8ad84
@ -13,7 +13,7 @@ A high-performance inter-process communication using shared memory on Linux/Wind
|
|||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
| | Environment |
|
| Environment | Value |
|
||||||
| ------ | ------ |
|
| ------ | ------ |
|
||||||
| Device | Lenovo ThinkPad T450 |
|
| Device | Lenovo ThinkPad T450 |
|
||||||
| CPU | Intel(R) Core(TM) i5-4300U @ 2.5 GHz |
|
| CPU | Intel(R) Core(TM) i5-4300U @ 2.5 GHz |
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <shared_mutex>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|||||||
33
src/ipc.cpp
33
src/ipc.cpp
@ -46,6 +46,29 @@ constexpr queue_t* queue_of(handle_t h) {
|
|||||||
return static_cast<queue_t*>(h);
|
return static_cast<queue_t*>(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using cache_t = mem::vector<byte_t>;
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
cache_t make_cache(byte_t const (& data)[N]) {
|
||||||
|
return {
|
||||||
|
static_cast<buff_t::value_type const *>(data),
|
||||||
|
static_cast<buff_t::value_type const *>(data) + N
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using remove_cv_ref_t = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||||
|
|
||||||
|
template <typename Cache>
|
||||||
|
constexpr auto to_buff(Cache&& cac) -> Requires<std::is_same<remove_cv_ref_t<Cache>, buff_t>::value, buff_t> {
|
||||||
|
return cac;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Cache>
|
||||||
|
auto to_buff(Cache&& cac) -> Requires<!std::is_same<remove_cv_ref_t<Cache>, buff_t>::value, buff_t> {
|
||||||
|
return make_buff(cac.data(), cac.size());
|
||||||
|
}
|
||||||
|
|
||||||
inline auto& recv_cache() {
|
inline auto& recv_cache() {
|
||||||
/*
|
/*
|
||||||
<Remarks> thread_local may have some bugs.
|
<Remarks> thread_local may have some bugs.
|
||||||
@ -56,12 +79,12 @@ inline auto& recv_cache() {
|
|||||||
https://developercommunity.visualstudio.com/content/problem/124121/thread-local-variables-fail-to-be-initialized-when.html
|
https://developercommunity.visualstudio.com/content/problem/124121/thread-local-variables-fail-to-be-initialized-when.html
|
||||||
https://software.intel.com/en-us/forums/intel-c-compiler/topic/684827
|
https://software.intel.com/en-us/forums/intel-c-compiler/topic/684827
|
||||||
*/
|
*/
|
||||||
static tls::pointer<memory::unordered_map<msg_id_t, buff_t>> rc;
|
static tls::pointer<mem::unordered_map<msg_id_t, cache_t>> rc;
|
||||||
return *rc.create();
|
return *rc.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline auto& queues_cache() {
|
inline auto& queues_cache() {
|
||||||
static tls::pointer<std::vector<queue_t*>> qc;
|
static tls::pointer<mem::vector<queue_t*>> qc;
|
||||||
return *qc.create();
|
return *qc.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +191,7 @@ buff_t multi_recv(F&& upd) {
|
|||||||
return make_buff(msg.data_, remain);
|
return make_buff(msg.data_, remain);
|
||||||
}
|
}
|
||||||
// cache the first message fragment
|
// cache the first message fragment
|
||||||
else rc.emplace(msg.id_, make_buff(msg.data_));
|
else rc.emplace(msg.id_, make_cache(msg.data_));
|
||||||
}
|
}
|
||||||
// has cached before this message
|
// has cached before this message
|
||||||
else {
|
else {
|
||||||
@ -177,9 +200,9 @@ buff_t multi_recv(F&& upd) {
|
|||||||
if (msg.remain_ <= 0) {
|
if (msg.remain_ <= 0) {
|
||||||
cache.insert(cache.end(), msg.data_, msg.data_ + remain);
|
cache.insert(cache.end(), msg.data_, msg.data_ + remain);
|
||||||
// finish this message, erase it from cache
|
// finish this message, erase it from cache
|
||||||
auto buf = std::move(cache);
|
auto cac = std::move(cache);
|
||||||
rc.erase(cache_it);
|
rc.erase(cache_it);
|
||||||
return buf;
|
return to_buff(std::move(cac));
|
||||||
}
|
}
|
||||||
// there are remain datas after this message
|
// there are remain datas after this message
|
||||||
cache.insert(cache.end(), msg.data_, msg.data_ + data_length);
|
cache.insert(cache.end(), msg.data_, msg.data_ + data_length);
|
||||||
|
|||||||
@ -2,19 +2,22 @@
|
|||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
#include "def.h"
|
#include "def.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace memory {
|
namespace mem {
|
||||||
|
|
||||||
struct static_alloc {
|
class static_alloc {
|
||||||
|
public:
|
||||||
static constexpr std::size_t remain() {
|
static constexpr std::size_t remain() {
|
||||||
return (std::numeric_limits<std::size_t>::max)();
|
return (std::numeric_limits<std::size_t>::max)();
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr void clear() {}
|
static constexpr void clear() {}
|
||||||
|
static constexpr void swap(static_alloc&) {}
|
||||||
|
|
||||||
static void* alloc(std::size_t size) {
|
static void* alloc(std::size_t size) {
|
||||||
return size ? std::malloc(size) : nullptr;
|
return size ? std::malloc(size) : nullptr;
|
||||||
@ -33,19 +36,40 @@ struct static_alloc {
|
|||||||
/// Scope allocation -- The destructor will release all allocated blocks.
|
/// Scope allocation -- The destructor will release all allocated blocks.
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
class scope_alloc_base {
|
||||||
|
protected:
|
||||||
|
struct block_t {
|
||||||
|
block_t* next_;
|
||||||
|
};
|
||||||
|
block_t* list_ = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::size_t remain() const {
|
||||||
|
std::size_t c = 0;
|
||||||
|
auto curr = list_;
|
||||||
|
while (curr != nullptr) {
|
||||||
|
++c;
|
||||||
|
curr = curr->next_;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free(void* /*p*/) {}
|
||||||
|
void free(void* /*p*/, std::size_t) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
template <typename AllocP = static_alloc>
|
template <typename AllocP = static_alloc>
|
||||||
class scope_alloc {
|
class scope_alloc : public detail::scope_alloc_base {
|
||||||
public:
|
public:
|
||||||
using alloc_policy = AllocP;
|
using alloc_policy = AllocP;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
alloc_policy alloc_;
|
alloc_policy alloc_;
|
||||||
|
|
||||||
struct block_t {
|
|
||||||
block_t* next_;
|
|
||||||
};
|
|
||||||
block_t* list_ = nullptr;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
scope_alloc() = default;
|
scope_alloc() = default;
|
||||||
|
|
||||||
@ -60,16 +84,6 @@ public:
|
|||||||
std::swap(this->list_ , rhs.list_);
|
std::swap(this->list_ , rhs.list_);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t remain() const {
|
|
||||||
std::size_t c = 0;
|
|
||||||
auto curr = list_;
|
|
||||||
while (curr != nullptr) {
|
|
||||||
++c;
|
|
||||||
curr = curr->next_;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
while (list_ != nullptr) {
|
while (list_ != nullptr) {
|
||||||
auto curr = list_;
|
auto curr = list_;
|
||||||
@ -85,16 +99,59 @@ public:
|
|||||||
curr->next_ = list_;
|
curr->next_ = list_;
|
||||||
return ((list_ = curr) + 1);
|
return ((list_ = curr) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void free(void* /*p*/) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
/// Fixed-size blocks allocation
|
/// Fixed-size blocks allocation
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
class fixed_pool_base {
|
||||||
|
protected:
|
||||||
|
std::size_t init_expand_;
|
||||||
|
std::size_t iter_;
|
||||||
|
void* cursor_;
|
||||||
|
|
||||||
|
void init(std::size_t init_expand) {
|
||||||
|
iter_ = init_expand_ = init_expand;
|
||||||
|
cursor_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void** node_p(void* node) {
|
||||||
|
return reinterpret_cast<void**>(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto& next(void* node) {
|
||||||
|
return *node_p(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::size_t remain() const {
|
||||||
|
std::size_t c = 0;
|
||||||
|
void* curr = cursor_;
|
||||||
|
while (curr != nullptr) {
|
||||||
|
++c;
|
||||||
|
curr = next(curr);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free(void* p) {
|
||||||
|
if (p == nullptr) return;
|
||||||
|
next(p) = cursor_;
|
||||||
|
cursor_ = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free(void* p, std::size_t) {
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
template <std::size_t BlockSize, typename AllocP = scope_alloc<>>
|
template <std::size_t BlockSize, typename AllocP = scope_alloc<>>
|
||||||
class fixed_pool {
|
class fixed_pool : public detail::fixed_pool_base {
|
||||||
public:
|
public:
|
||||||
using alloc_policy = AllocP;
|
using alloc_policy = AllocP;
|
||||||
|
|
||||||
@ -104,22 +161,15 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
alloc_policy alloc_;
|
alloc_policy alloc_;
|
||||||
std::size_t init_expand_, iter_;
|
|
||||||
void* cursor_;
|
|
||||||
|
|
||||||
void expand() {
|
void expand() {
|
||||||
void** p = reinterpret_cast<void**>(cursor_ = alloc_.alloc(block_size * iter_));
|
auto p = node_p(cursor_ = alloc_.alloc(block_size * iter_));
|
||||||
for (std::size_t i = 0; i < iter_ - 1; ++i)
|
for (std::size_t i = 0; i < iter_ - 1; ++i)
|
||||||
p = reinterpret_cast<void**>((*p) = reinterpret_cast<byte_t*>(p) + block_size);
|
p = node_p((*p) = reinterpret_cast<byte_t*>(p) + block_size);
|
||||||
(*p) = nullptr;
|
(*p) = nullptr;
|
||||||
iter_ *= 2;
|
iter_ *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(std::size_t init_expand) {
|
|
||||||
iter_ = init_expand_ = init_expand;
|
|
||||||
cursor_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit fixed_pool(std::size_t init_expand = 1) {
|
explicit fixed_pool(std::size_t init_expand = 1) {
|
||||||
init(init_expand);
|
init(init_expand);
|
||||||
@ -139,13 +189,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::size_t remain() const {
|
std::size_t remain() const {
|
||||||
std::size_t c = 0;
|
return detail::fixed_pool_base::remain() * block_size;
|
||||||
void* curr = cursor_;
|
|
||||||
while (curr != nullptr) {
|
|
||||||
++c;
|
|
||||||
curr = *reinterpret_cast<void**>(curr); // curr = next
|
|
||||||
}
|
|
||||||
return c * block_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
@ -156,16 +200,14 @@ public:
|
|||||||
void* alloc() {
|
void* alloc() {
|
||||||
if (cursor_ == nullptr) expand();
|
if (cursor_ == nullptr) expand();
|
||||||
void* p = cursor_;
|
void* p = cursor_;
|
||||||
cursor_ = *reinterpret_cast<void**>(p);
|
cursor_ = next(p);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
void free(void* p) {
|
void* alloc(std::size_t) {
|
||||||
if (p == nullptr) return;
|
return alloc();
|
||||||
*reinterpret_cast<void**>(p) = cursor_;
|
|
||||||
cursor_ = p;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace memory
|
} // namespace mem
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
@ -1,27 +1,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <initializer_list>
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
#include "tls_pointer.h"
|
|
||||||
|
|
||||||
#include "memory/alloc.hpp"
|
#include "memory/alloc.hpp"
|
||||||
#include "memory/wrapper.hpp"
|
#include "memory/wrapper.hpp"
|
||||||
|
|
||||||
|
#include "tls_pointer.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace memory {
|
namespace mem {
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
template <typename Comp, typename F, std::size_t...I>
|
template <typename F, typename D>
|
||||||
void switch_constexpr(std::size_t i, std::index_sequence<I...>, F&& f) {
|
constexpr decltype(auto) switch_constexpr(std::size_t /*i*/, std::index_sequence<>, F&& /*f*/, D&& def) {
|
||||||
[[maybe_unused]] std::initializer_list<int> expand {
|
return def();
|
||||||
(Comp{}(i, I) && (f(std::integral_constant<size_t, I>{}), 0))...
|
}
|
||||||
};
|
|
||||||
|
template <typename F, typename D, std::size_t N, std::size_t...I>
|
||||||
|
constexpr decltype(auto) switch_constexpr(std::size_t i, std::index_sequence<N, I...>, F&& f, D&& def) {
|
||||||
|
return (i == N) ? f(std::integral_constant<size_t, N>{}) :
|
||||||
|
switch_constexpr(i, std::index_sequence<I...>{}, f, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
@ -35,18 +39,21 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename F>
|
template <typename F>
|
||||||
static void choose(std::size_t size, F&& f) {
|
static decltype(auto) choose(std::size_t size, F&& f) {
|
||||||
enum : std::size_t { base_size = sizeof(void*) };
|
enum : std::size_t { base_size = sizeof(void*) };
|
||||||
detail::switch_constexpr<std::less_equal<std::size_t>>(size, std::index_sequence<
|
size = ((size - 1) & (~(base_size - 1))) + base_size;
|
||||||
|
return detail::switch_constexpr(size, std::index_sequence<
|
||||||
base_size , base_size * 2 ,
|
base_size , base_size * 2 ,
|
||||||
base_size * 4 , base_size * 6 ,
|
base_size * 3 , base_size * 4 ,
|
||||||
base_size * 8 , base_size * 12,
|
base_size * 5 , base_size * 6 ,
|
||||||
base_size * 16, base_size * 20,
|
base_size * 7 , base_size * 8 ,
|
||||||
base_size * 24, base_size * 28,
|
base_size * 9 , base_size * 10,
|
||||||
base_size * 32, base_size * 40,
|
base_size * 11, base_size * 12,
|
||||||
base_size * 48, base_size * 56,
|
base_size * 13, base_size * 14,
|
||||||
base_size * 64
|
base_size * 15, base_size * 16
|
||||||
>{}, [&f](auto index) { f(fixed<decltype(index)::value>()); });
|
>{}, [&f](auto index) {
|
||||||
|
return f(fixed<decltype(index)::value>());
|
||||||
|
}, [&f] { return f(static_alloc{}); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -63,13 +70,11 @@ public:
|
|||||||
static constexpr void clear() {}
|
static constexpr void clear() {}
|
||||||
|
|
||||||
static void* alloc(std::size_t size) {
|
static void* alloc(std::size_t size) {
|
||||||
void* p;
|
return choose(size, [size](auto&& fp) { return fp.alloc(size); });
|
||||||
choose(size, [&p](auto& fp) { p = fp.alloc(); });
|
|
||||||
return p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void free(void* p, std::size_t size) {
|
static void free(void* p, std::size_t size) {
|
||||||
choose(size, [p](auto& fp) { fp.free(p); });
|
choose(size, [p](auto&& fp) { fp.free(p); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,5 +86,8 @@ using unordered_map = std::unordered_map<
|
|||||||
Key, T//, std::hash<Key>, std::equal_to<Key>, allocator<std::pair<const Key, T>>
|
Key, T//, std::hash<Key>, std::equal_to<Key>, allocator<std::pair<const Key, T>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
} // namespace memory
|
template <typename T>
|
||||||
|
using vector = std::vector<T/*, allocator<T>*/>;
|
||||||
|
|
||||||
|
} // namespace mem
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
@ -1,14 +1,61 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "rw_lock.h"
|
||||||
|
#include "tls_pointer.h"
|
||||||
|
|
||||||
namespace ipc {
|
namespace ipc {
|
||||||
namespace memory {
|
namespace mem {
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
/// The allocator wrapper class
|
/// Thread-safe allocation wrapper
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
template <typename AllocP>
|
||||||
|
class synchronized_pool {
|
||||||
|
public:
|
||||||
|
using alloc_policy = AllocP;
|
||||||
|
|
||||||
|
private:
|
||||||
|
rw_lock lc_;
|
||||||
|
std::vector<alloc_policy*> allocs_;
|
||||||
|
|
||||||
|
struct alloc_t {
|
||||||
|
synchronized_pool* t_;
|
||||||
|
alloc_policy* alc_;
|
||||||
|
|
||||||
|
alloc_t(synchronized_pool* t)
|
||||||
|
: t_ { t }
|
||||||
|
{}
|
||||||
|
|
||||||
|
~alloc_t() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr std::size_t remain() {
|
||||||
|
return (std::numeric_limits<std::size_t>::max)();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* alloc() {
|
||||||
|
static tls::pointer<alloc_policy> alc;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* alloc(std::size_t) {
|
||||||
|
return alloc();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
/// The allocator wrapper class for STL
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
template <typename T, typename AllocP>
|
template <typename T, typename AllocP>
|
||||||
@ -106,5 +153,5 @@ constexpr bool operator!=(const allocator_wrapper<T, AllocP>&, const allocator_w
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace memory
|
} // namespace mem
|
||||||
} // namespace ipc
|
} // namespace ipc
|
||||||
|
|||||||
@ -25,7 +25,7 @@ constexpr void* mem_of(void* mem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline auto& m2h() {
|
inline auto& m2h() {
|
||||||
static ipc::tls::pointer<ipc::memory::unordered_map<void*, std::string>> cache;
|
static ipc::tls::pointer<ipc::mem::unordered_map<void*, std::string>> cache;
|
||||||
return *cache.create();
|
return *cache.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ constexpr auto to_tchar(std::string && str) -> IsSame<T, std::wstring> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline auto& m2h() {
|
inline auto& m2h() {
|
||||||
static ipc::tls::pointer<ipc::memory::unordered_map<void*, HANDLE>> cache;
|
static ipc::tls::pointer<ipc::mem::unordered_map<void*, HANDLE>> cache;
|
||||||
return *cache.create();
|
return *cache.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user