allocator.hpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. * Copyright (c) 2017-2023 zhllxt
  3. *
  4. * author : zhllxt
  5. * email : 37792738@qq.com
  6. *
  7. * Distributed under the Boost Software License, Version 1.0. (See accompanying
  8. * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  9. */
  10. #ifndef __ASIO2_ALLOCATOR_HPP__
  11. #define __ASIO2_ALLOCATOR_HPP__
  12. #if defined(_MSC_VER) && (_MSC_VER >= 1200)
  13. #pragma once
  14. #endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
  15. #include <memory>
  16. #include <type_traits>
  17. #include <utility>
  18. #include <atomic>
  19. #include <mutex>
  20. #include <asio2/config.hpp>
  21. #include <asio2/base/log.hpp>
  22. namespace asio2::detail
  23. {
  24. #if defined(ASIO2_ENABLE_LOG) && defined(ASIO2_ENABLE_LOG_STORAGE_SIZE)
  25. template<typename = void>
  26. inline void log_allocator_storage_size(bool is_lookfree, bool is_stack, std::size_t size)
  27. {
  28. static std::mutex mtx;
  29. static std::map<std::size_t, std::size_t> unlock_stack_map;
  30. static std::map<std::size_t, std::size_t> unlock_heaps_map;
  31. static std::map<std::size_t, std::size_t> atomic_stack_map;
  32. static std::map<std::size_t, std::size_t> atomic_heaps_map;
  33. std::lock_guard guard(mtx);
  34. if (is_lookfree)
  35. {
  36. if (is_stack)
  37. unlock_stack_map[size]++;
  38. else
  39. unlock_heaps_map[size]++;
  40. }
  41. else
  42. {
  43. if (is_stack)
  44. atomic_stack_map[size]++;
  45. else
  46. atomic_heaps_map[size]++;
  47. }
  48. static auto t1 = std::chrono::steady_clock::now();
  49. auto t2 = std::chrono::steady_clock::now();
  50. if (std::chrono::duration_cast<std::chrono::seconds>(t2 - t1).count() > 60)
  51. {
  52. t1 = std::chrono::steady_clock::now();
  53. std::string str;
  54. str += "\n";
  55. str += "allocator storage size test: ";
  56. if /**/ constexpr (sizeof(void*) == sizeof(std::uint64_t))
  57. str += "x64";
  58. else if constexpr (sizeof(void*) == sizeof(std::uint32_t))
  59. str += "x86";
  60. else
  61. str += std::to_string(sizeof(void*)) + "bit";
  62. str += ", ";
  63. #if defined(_DEBUG) || defined(DEBUG)
  64. str += "Debug";
  65. #else
  66. str += "Release";
  67. #endif
  68. #if defined(ASIO2_ENABLE_SSL) || defined(ASIO2_USE_SSL)
  69. str += ", SSL";
  70. #endif
  71. #if ASIO2_OS_LINUX || ASIO2_OS_UNIX
  72. str += ", linux";
  73. #elif ASIO2_OS_WINDOWS
  74. str += ", windows";
  75. #elif ASIO2_OS_MACOS
  76. str += ", macos";
  77. #endif
  78. str += "\n";
  79. str += "------------------------------------------------------------\n";
  80. str += "unlock_stack_map\n";
  81. for (auto [len, num] : unlock_stack_map)
  82. {
  83. str += " ";
  84. str += std::to_string(len); str += " : ";
  85. str += std::to_string(num); str += "\n";
  86. }
  87. str += "unlock_heaps_map\n";
  88. for (auto [len, num] : unlock_heaps_map)
  89. {
  90. str += " ";
  91. str += std::to_string(len); str += " : ";
  92. str += std::to_string(num); str += "\n";
  93. }
  94. str += "atomic_stack_map\n";
  95. for (auto [len, num] : atomic_stack_map)
  96. {
  97. str += " ";
  98. str += std::to_string(len); str += " : ";
  99. str += std::to_string(num); str += "\n";
  100. }
  101. str += "atomic_heaps_map\n";
  102. for (auto [len, num] : atomic_heaps_map)
  103. {
  104. str += " ";
  105. str += std::to_string(len); str += " : ";
  106. str += std::to_string(num); str += "\n";
  107. }
  108. str += "------------------------------------------------------------\n";
  109. str += "\n";
  110. ASIO2_LOG_FATAL("{}", str);
  111. }
  112. }
  113. template<typename = void>
  114. inline void lockfree_allocator_threadsafe_test(std::size_t paddr, bool is_add)
  115. {
  116. static std::mutex mtx;
  117. static std::unordered_map<std::size_t, std::thread::id> addr_thread_map;
  118. std::lock_guard guard(mtx);
  119. if (is_add)
  120. {
  121. auto it = addr_thread_map.find(paddr);
  122. if (it == addr_thread_map.end())
  123. {
  124. addr_thread_map.emplace(paddr, std::this_thread::get_id());
  125. }
  126. else
  127. {
  128. if (it->second != std::this_thread::get_id())
  129. {
  130. ASIO2_ASSERT(false);
  131. throw 0;
  132. }
  133. }
  134. }
  135. else
  136. {
  137. addr_thread_map.erase(paddr);
  138. }
  139. }
  140. #endif
  141. /// see : boost\libs\asio\example\cpp11\allocation\server.cpp
  142. template<typename IsLockFree>
  143. inline constexpr std::size_t calc_allocator_storage_size() noexcept
  144. {
  145. #if defined(ASIO2_ALLOCATOR_STORAGE_SIZE)
  146. // if the user defined a custom allocator storage size, use it.
  147. return std::size_t(ASIO2_ALLOCATOR_STORAGE_SIZE);
  148. #elif defined(_DEBUG) || defined(DEBUG)
  149. // debug mode just provide a simple size
  150. if constexpr (IsLockFree::value)
  151. {
  152. return std::size_t(1024);
  153. }
  154. else
  155. {
  156. return std::size_t(2048);
  157. }
  158. #else
  159. #if defined(ASIO2_ENABLE_SSL) || defined(ASIO2_USE_SSL)
  160. if constexpr (IsLockFree::value)
  161. {
  162. if constexpr (sizeof(void*) == sizeof(std::uint32_t))
  163. return std::size_t(332);
  164. else
  165. return std::size_t(664);
  166. }
  167. else
  168. {
  169. if constexpr (sizeof(void*) == sizeof(std::uint32_t))
  170. return std::size_t(520);
  171. else
  172. return std::size_t(1024);
  173. }
  174. #else
  175. if constexpr (IsLockFree::value)
  176. {
  177. if constexpr (sizeof(void*) == sizeof(std::uint32_t))
  178. return std::size_t(332);
  179. else
  180. return std::size_t(664);
  181. }
  182. else
  183. {
  184. if constexpr (sizeof(void*) == sizeof(std::uint32_t))
  185. return std::size_t(512);
  186. else
  187. return std::size_t(1024);
  188. }
  189. #endif
  190. #endif
  191. }
  192. template<std::size_t N>
  193. struct allocator_size_op
  194. {
  195. static constexpr std::size_t size = N;
  196. };
  197. struct allocator_fixed_size_tag {};
  198. template<std::size_t N>
  199. struct allocator_fixed_size_op : public allocator_fixed_size_tag
  200. {
  201. static constexpr std::size_t size = N;
  202. };
  203. template<class args_t>
  204. struct allocator_size_traits
  205. {
  206. template<class, class = void>
  207. struct has_member_size : std::false_type {};
  208. template<class T>
  209. struct has_member_size<T, std::void_t<decltype(T::allocator_storage_size)>> : std::true_type {};
  210. static constexpr std::size_t calc()
  211. {
  212. if constexpr (has_member_size<args_t>::value)
  213. {
  214. return args_t::allocator_storage_size;
  215. }
  216. else
  217. {
  218. return 0;
  219. }
  220. }
  221. static constexpr std::size_t value = calc();
  222. };
  223. // allocator storage sizer
  224. template<class args_t>
  225. using assizer = allocator_size_op<allocator_size_traits<args_t>::value>;
  226. template<typename IsLockFree, typename SizeN>
  227. inline constexpr std::size_t get_allocator_storage_size() noexcept
  228. {
  229. if constexpr (std::is_base_of_v<allocator_fixed_size_tag, detail::remove_cvref_t<SizeN>>)
  230. {
  231. return SizeN::size;
  232. }
  233. else
  234. {
  235. #if defined(ASIO2_ALLOCATOR_STORAGE_SIZE)
  236. // if the user defined a custom allocator storage size, use it.
  237. return std::size_t(ASIO2_ALLOCATOR_STORAGE_SIZE);
  238. #else
  239. if constexpr (SizeN::size >= std::size_t(64) && SizeN::size <= std::size_t(1024 * 1024))
  240. {
  241. return SizeN::size;
  242. }
  243. else
  244. {
  245. return calc_allocator_storage_size<IsLockFree>();
  246. }
  247. #endif
  248. }
  249. }
  250. /**
  251. * @brief Class to manage the memory to be used for handler-based custom allocation.
  252. * It contains a single block of memory which may be returned for allocation
  253. * requests. If the memory is in use when an allocation request is made, the
  254. * allocator delegates allocation to the global heap.
  255. * @tparam IsLockFree - is lock free or not.
  256. * @tparam SizeN - the single block of memory size.
  257. */
  258. template<
  259. typename IsLockFree = std::true_type,
  260. typename SizeN = allocator_size_op<calc_allocator_storage_size<IsLockFree>()>>
  261. class handler_memory;
  262. /**
  263. * @brief Class to manage the memory to be used for handler-based custom allocation.
  264. * It contains a single block of memory which may be returned for allocation
  265. * requests. If the memory is in use when an allocation request is made, the
  266. * allocator delegates allocation to the global heap.
  267. * @tparam IsLockFree - is lock free or not.
  268. * @tparam SizeN - the single block of memory size.
  269. */
  270. template<typename SizeN>
  271. class handler_memory<std::true_type, SizeN>
  272. {
  273. public:
  274. static constexpr std::size_t storage_size = get_allocator_storage_size<std::true_type, SizeN>();
  275. explicit handler_memory() noexcept : in_use_(false) {}
  276. handler_memory(const handler_memory&) = delete;
  277. handler_memory& operator=(const handler_memory&) = delete;
  278. inline void* allocate(std::size_t size)
  279. {
  280. #if defined(ASIO2_ENABLE_LOG) && defined(ASIO2_ENABLE_LOG_STORAGE_SIZE)
  281. lockfree_allocator_threadsafe_test(std::size_t(this), true);
  282. #endif
  283. if (!in_use_ && size < sizeof(storage_))
  284. {
  285. #if defined(ASIO2_ENABLE_LOG) && defined(ASIO2_ENABLE_LOG_STORAGE_SIZE)
  286. log_allocator_storage_size(true, true, size);
  287. #endif
  288. in_use_ = true;
  289. return &storage_;
  290. }
  291. else
  292. {
  293. #if defined(ASIO2_ENABLE_LOG) && defined(ASIO2_ENABLE_LOG_STORAGE_SIZE)
  294. log_allocator_storage_size(true, false, size);
  295. #endif
  296. return ::operator new(size);
  297. }
  298. }
  299. inline void deallocate(void* pointer) noexcept
  300. {
  301. // must erase when deallocate, otherwise if call server.stop -> server.start
  302. // then the test map will incorrect.
  303. #if defined(ASIO2_ENABLE_LOG) && defined(ASIO2_ENABLE_LOG_STORAGE_SIZE)
  304. lockfree_allocator_threadsafe_test(std::size_t(this), false);
  305. #endif
  306. if (pointer == &storage_)
  307. {
  308. in_use_ = false;
  309. }
  310. else
  311. {
  312. ::operator delete(pointer);
  313. }
  314. }
  315. private:
  316. // Storage space used for handler-based custom memory allocation.
  317. typename std::aligned_storage<storage_size + sizeof(void*)>::type storage_{};
  318. // Whether the handler-based custom allocation storage has been used.
  319. bool in_use_{};
  320. };
  321. /**
  322. * @brief Class to manage the memory to be used for handler-based custom allocation.
  323. * It contains a single block of memory which may be returned for allocation
  324. * requests. If the memory is in use when an allocation request is made, the
  325. * allocator delegates allocation to the global heap.
  326. * @tparam IsLockFree - is lock free or not.
  327. * @tparam SizeN - the single block of memory size.
  328. */
  329. template<typename SizeN>
  330. class handler_memory<std::false_type, SizeN>
  331. {
  332. public:
  333. static constexpr std::size_t storage_size = get_allocator_storage_size<std::false_type, SizeN>();
  334. handler_memory() noexcept { in_use_.clear(); }
  335. handler_memory(const handler_memory&) = delete;
  336. handler_memory& operator=(const handler_memory&) = delete;
  337. inline void* allocate(std::size_t size)
  338. {
  339. if (size < sizeof(storage_) && (!in_use_.test_and_set()))
  340. {
  341. #if defined(ASIO2_ENABLE_LOG) && defined(ASIO2_ENABLE_LOG_STORAGE_SIZE)
  342. log_allocator_storage_size(false, true, size);
  343. #endif
  344. return &storage_;
  345. }
  346. else
  347. {
  348. #if defined(ASIO2_ENABLE_LOG) && defined(ASIO2_ENABLE_LOG_STORAGE_SIZE)
  349. log_allocator_storage_size(false, false, size);
  350. #endif
  351. return ::operator new(size);
  352. }
  353. }
  354. inline void deallocate(void* pointer) noexcept
  355. {
  356. if (pointer == &storage_)
  357. {
  358. in_use_.clear();
  359. }
  360. else
  361. {
  362. ::operator delete(pointer);
  363. }
  364. }
  365. private:
  366. // Storage space used for handler-based custom memory allocation.
  367. typename std::aligned_storage<storage_size + sizeof(void*)>::type storage_{};
  368. // Whether the handler-based custom allocation storage has been used.
  369. std::atomic_flag in_use_{};
  370. };
  371. #if defined(ASIO2_ENABLE_LOG)
  372. static_assert(handler_memory<std::true_type >::storage_size == calc_allocator_storage_size<std::true_type >());
  373. static_assert(handler_memory<std::false_type>::storage_size == calc_allocator_storage_size<std::false_type>());
  374. #endif
  375. // The allocator to be associated with the handler objects. This allocator only
  376. // needs to satisfy the C++11 minimal allocator requirements.
  377. template <typename T, typename N, typename B>
  378. class handler_allocator
  379. {
  380. public:
  381. using value_type = T;
  382. explicit handler_allocator(handler_memory<N, B>& mem) noexcept
  383. : memory_(mem)
  384. {
  385. }
  386. template <typename U, typename No, typename Bo>
  387. handler_allocator(const handler_allocator<U, No, Bo>& other) noexcept
  388. : memory_(other.memory_)
  389. {
  390. }
  391. inline bool operator==(const handler_allocator& other) const noexcept
  392. {
  393. return &memory_ == &other.memory_;
  394. }
  395. inline bool operator!=(const handler_allocator& other) const noexcept
  396. {
  397. return &memory_ != &other.memory_;
  398. }
  399. inline T* allocate(std::size_t n) const
  400. {
  401. return static_cast<T*>(memory_.allocate(sizeof(T) * n));
  402. }
  403. inline void deallocate(T* p, std::size_t /*n*/) const noexcept
  404. {
  405. return memory_.deallocate(p);
  406. }
  407. private:
  408. template <typename, typename, typename> friend class handler_allocator;
  409. // The underlying memory.
  410. handler_memory<N, B>& memory_{};
  411. };
  412. // Wrapper class template for handler objects to allow handler memory
  413. // allocation to be customised. The allocator_type type and get_allocator()
  414. // member function are used by the asynchronous operations to obtain the
  415. // allocator. Calls to operator() are forwarded to the encapsulated handler.
  416. template <typename Handler, typename N, typename B>
  417. class custom_alloc_handler
  418. {
  419. public:
  420. using allocator_type = handler_allocator<Handler, N, B>;
  421. custom_alloc_handler(handler_memory<N, B>& m, Handler&& h) noexcept
  422. : memory_(m)
  423. , handler_(std::forward<Handler>(h))
  424. {
  425. }
  426. inline allocator_type get_allocator() const noexcept
  427. {
  428. return allocator_type(memory_);
  429. }
  430. template <typename ...Args>
  431. inline void operator()(Args&&... args)
  432. {
  433. handler_(std::forward<Args>(args)...);
  434. }
  435. private:
  436. handler_memory<N, B>& memory_{};
  437. Handler handler_;
  438. };
  439. // Helper function to wrap a handler object to add custom allocation.
  440. // Must not be used in multithreading,Otherwise, it will cause program crash.
  441. template <typename Handler, typename N, typename B>
  442. inline custom_alloc_handler<Handler, N, B> make_allocator(handler_memory<N, B>& m, Handler&& h)
  443. {
  444. return custom_alloc_handler<Handler, N, B>(m, std::forward<Handler>(h));
  445. }
  446. }
  447. #endif // !__ASIO2_ALLOCATOR_HPP__