1
0

memory.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #include "pocketpy/memory.h"
  2. namespace pkpy{
  3. struct LinkedListNode{
  4. LinkedListNode* prev;
  5. LinkedListNode* next;
  6. };
  7. template<typename T>
  8. struct DoubleLinkedList{
  9. static_assert(std::is_base_of_v<LinkedListNode, T>);
  10. int _size;
  11. LinkedListNode head;
  12. LinkedListNode tail;
  13. DoubleLinkedList(): _size(0){
  14. head.prev = nullptr;
  15. head.next = &tail;
  16. tail.prev = &head;
  17. tail.next = nullptr;
  18. }
  19. void push_back(T* node){
  20. node->prev = tail.prev;
  21. node->next = &tail;
  22. tail.prev->next = node;
  23. tail.prev = node;
  24. _size++;
  25. }
  26. void push_front(T* node){
  27. node->prev = &head;
  28. node->next = head.next;
  29. head.next->prev = node;
  30. head.next = node;
  31. _size++;
  32. }
  33. void pop_back(){
  34. #if PK_DEBUG_MEMORY_POOL
  35. if(empty()) throw std::runtime_error("DoubleLinkedList::pop_back() called on empty list");
  36. #endif
  37. tail.prev->prev->next = &tail;
  38. tail.prev = tail.prev->prev;
  39. _size--;
  40. }
  41. void pop_front(){
  42. #if PK_DEBUG_MEMORY_POOL
  43. if(empty()) throw std::runtime_error("DoubleLinkedList::pop_front() called on empty list");
  44. #endif
  45. head.next->next->prev = &head;
  46. head.next = head.next->next;
  47. _size--;
  48. }
  49. T* back() const {
  50. #if PK_DEBUG_MEMORY_POOL
  51. if(empty()) throw std::runtime_error("DoubleLinkedList::back() called on empty list");
  52. #endif
  53. return static_cast<T*>(tail.prev);
  54. }
  55. T* front() const {
  56. #if PK_DEBUG_MEMORY_POOL
  57. if(empty()) throw std::runtime_error("DoubleLinkedList::front() called on empty list");
  58. #endif
  59. return static_cast<T*>(head.next);
  60. }
  61. void erase(T* node){
  62. #if PK_DEBUG_MEMORY_POOL
  63. if(empty()) throw std::runtime_error("DoubleLinkedList::erase() called on empty list");
  64. LinkedListNode* n = head.next;
  65. while(n != &tail){
  66. if(n == node) break;
  67. n = n->next;
  68. }
  69. if(n != node) throw std::runtime_error("DoubleLinkedList::erase() called on node not in the list");
  70. #endif
  71. node->prev->next = node->next;
  72. node->next->prev = node->prev;
  73. _size--;
  74. }
  75. // void move_all_back(DoubleLinkedList<T>& other){
  76. // if(other.empty()) return;
  77. // other.tail.prev->next = &tail;
  78. // tail.prev->next = other.head.next;
  79. // other.head.next->prev = tail.prev;
  80. // tail.prev = other.tail.prev;
  81. // _size += other._size;
  82. // other.head.next = &other.tail;
  83. // other.tail.prev = &other.head;
  84. // other._size = 0;
  85. // }
  86. bool empty() const {
  87. #if PK_DEBUG_MEMORY_POOL
  88. if(size() == 0){
  89. if(head.next != &tail || tail.prev != &head){
  90. throw std::runtime_error("DoubleLinkedList::size() returned 0 but the list is not empty");
  91. }
  92. return true;
  93. }
  94. #endif
  95. return _size == 0;
  96. }
  97. int size() const { return _size; }
  98. template<typename Func>
  99. void apply(Func func){
  100. LinkedListNode* p = head.next;
  101. while(p != &tail){
  102. LinkedListNode* next = p->next;
  103. func(static_cast<T*>(p));
  104. p = next;
  105. }
  106. }
  107. };
  108. template<int __BlockSize>
  109. struct MemoryPool{
  110. static const int __MaxBlocks = 256*1024 / __BlockSize;
  111. static const int __MinArenaCount = PK_GC_MIN_THRESHOLD*100 / (256*1024);
  112. struct Block{
  113. void* arena;
  114. char data[__BlockSize];
  115. };
  116. struct Arena: LinkedListNode{
  117. Block _blocks[__MaxBlocks];
  118. Block* _free_list[__MaxBlocks];
  119. int _free_list_size;
  120. Arena(): _free_list_size(__MaxBlocks) {
  121. for(int i=0; i<__MaxBlocks; i++){
  122. _blocks[i].arena = this;
  123. _free_list[i] = &_blocks[i];
  124. }
  125. }
  126. bool empty() const { return _free_list_size == 0; }
  127. bool full() const { return _free_list_size == __MaxBlocks; }
  128. size_t allocated_size() const{
  129. return __BlockSize * (__MaxBlocks - _free_list_size);
  130. }
  131. Block* alloc(){
  132. #if PK_DEBUG_MEMORY_POOL
  133. if(empty()) throw std::runtime_error("Arena::alloc() called on empty arena");
  134. #endif
  135. _free_list_size--;
  136. return _free_list[_free_list_size];
  137. }
  138. void dealloc(Block* block){
  139. #if PK_DEBUG_MEMORY_POOL
  140. if(full()) throw std::runtime_error("Arena::dealloc() called on full arena");
  141. #endif
  142. _free_list[_free_list_size] = block;
  143. _free_list_size++;
  144. }
  145. };
  146. MemoryPool() = default;
  147. MemoryPool(const MemoryPool&) = delete;
  148. MemoryPool& operator=(const MemoryPool&) = delete;
  149. MemoryPool(MemoryPool&&) = delete;
  150. MemoryPool& operator=(MemoryPool&&) = delete;
  151. DoubleLinkedList<Arena> _arenas;
  152. DoubleLinkedList<Arena> _empty_arenas;
  153. void* alloc(size_t size){
  154. PK_GLOBAL_SCOPE_LOCK();
  155. #if PK_DEBUG_NO_MEMORY_POOL
  156. return malloc(size);
  157. #endif
  158. if(size > __BlockSize){
  159. void* p = malloc(sizeof(void*) + size);
  160. memset(p, 0, sizeof(void*));
  161. return (char*)p + sizeof(void*);
  162. }
  163. if(_arenas.empty()){
  164. // std::cout << _arenas.size() << ',' << _empty_arenas.size() << ',' << _full_arenas.size() << std::endl;
  165. _arenas.push_back(new Arena());
  166. }
  167. Arena* arena = _arenas.back();
  168. void* p = arena->alloc()->data;
  169. if(arena->empty()){
  170. _arenas.pop_back();
  171. _empty_arenas.push_back(arena);
  172. }
  173. return p;
  174. }
  175. void dealloc(void* p){
  176. PK_GLOBAL_SCOPE_LOCK();
  177. #if PK_DEBUG_NO_MEMORY_POOL
  178. free(p);
  179. return;
  180. #endif
  181. #if PK_DEBUG_MEMORY_POOL
  182. if(p == nullptr) throw std::runtime_error("MemoryPool::dealloc() called on nullptr");
  183. #endif
  184. Block* block = (Block*)((char*)p - sizeof(void*));
  185. if(block->arena == nullptr){
  186. free(block);
  187. }else{
  188. Arena* arena = (Arena*)block->arena;
  189. if(arena->empty()){
  190. _empty_arenas.erase(arena);
  191. _arenas.push_front(arena);
  192. arena->dealloc(block);
  193. }else{
  194. arena->dealloc(block);
  195. }
  196. }
  197. }
  198. void shrink_to_fit(){
  199. PK_GLOBAL_SCOPE_LOCK();
  200. if(_arenas.size() < __MinArenaCount) return;
  201. _arenas.apply([this](Arena* arena){
  202. if(arena->full()){
  203. _arenas.erase(arena);
  204. delete arena;
  205. }
  206. });
  207. }
  208. std::string info(){
  209. int n_used_arenas = _arenas.size();
  210. int n_total_arenas = n_used_arenas + _empty_arenas.size();
  211. size_t allocated_size = 0;
  212. size_t total_size = 0;
  213. _arenas.apply([&](Arena* arena){
  214. allocated_size += arena->allocated_size();
  215. total_size += __BlockSize * __MaxBlocks;
  216. });
  217. _empty_arenas.apply([&](Arena* arena){
  218. total_size += __BlockSize * __MaxBlocks;
  219. });
  220. char buffer[512];
  221. snprintf(
  222. buffer,
  223. sizeof(buffer),
  224. "pool%d: %.2f/%.2f MB (%d/%d arenas)",
  225. __BlockSize,
  226. (float)allocated_size / (1024*1024),
  227. (float)total_size / (1024*1024),
  228. n_used_arenas,
  229. n_total_arenas
  230. );
  231. return buffer;
  232. }
  233. ~MemoryPool(){
  234. _arenas.apply([](Arena* arena){ delete arena; });
  235. _empty_arenas.apply([](Arena* arena){ delete arena; });
  236. }
  237. };
  238. static MemoryPool<128> pool128;
  239. void* pool128_alloc(size_t size) noexcept { return pool128.alloc(size); }
  240. void pool128_dealloc(void* p) noexcept { pool128.dealloc(p); }
  241. void pools_shrink_to_fit() noexcept {
  242. pool128.shrink_to_fit();
  243. }
  244. std::string pool64_info() noexcept { return "unavailable"; }
  245. std::string pool128_info() noexcept { return pool128.info(); }
  246. }