diff --git a/.gitignore b/.gitignore index 33c370e4..67e402d6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ plugins/godot/godot-cpp/ src/_generated.h profile.sh test +tmp.rar diff --git a/src/ceval.h b/src/ceval.h index f253a7fc..de39b215 100644 --- a/src/ceval.h +++ b/src/ceval.h @@ -16,7 +16,7 @@ __NEXT_STEP:; * `Args` containing strong references is safe if it is passed to `call` or `fast_call` */ #if !DEBUG_NO_AUTO_GC - heap._auto_collect(this); + heap._auto_collect(); #endif const Bytecode& byte = frame->next_bytecode(); diff --git a/src/common.h b/src/common.h index ad90a7b0..2303030f 100644 --- a/src/common.h +++ b/src/common.h @@ -36,8 +36,9 @@ #define DEBUG_DIS_EXEC 0 #define DEBUG_DIS_EXEC_MIN 1 #define DEBUG_CEVAL_STEP 0 -#define DEBUG_FULL_EXCEPTION 0 -#define DEBUG_NO_AUTO_GC 1 +#define DEBUG_FULL_EXCEPTION 1 +#define DEBUG_MEMORY_POOL 0 +#define DEBUG_NO_AUTO_GC 0 #define DEBUG_GC_STATS 0 #if (defined(__ANDROID__) && __ANDROID_API__ <= 22) || defined(__EMSCRIPTEN__) diff --git a/src/gc.h b/src/gc.h index dbb22d23..4cb16eab 100644 --- a/src/gc.h +++ b/src/gc.h @@ -1,6 +1,7 @@ #pragma once #include "common.h" +#include "memory.h" #include "obj.h" #include "codeobject.h" #include "namedict.h" @@ -10,6 +11,7 @@ struct ManagedHeap{ std::vector _no_gc; std::vector gen; VM* vm; + MemoryPool<> pool; ManagedHeap(VM* vm): vm(vm) {} @@ -36,7 +38,8 @@ struct ManagedHeap{ template PyObject* gcnew(Type type, T&& val){ - PyObject* obj = new Py_>(type, std::forward(val)); + using __T = Py_>; + PyObject* obj = new(pool.alloc<__T>()) __T(type, std::forward(val)); gen.push_back(obj); gc_counter++; return obj; @@ -44,16 +47,19 @@ struct ManagedHeap{ template PyObject* _new(Type type, T&& val){ - PyObject* obj = new Py_>(type, std::forward(val)); + using __T = Py_>; + PyObject* obj = new(pool.alloc<__T>()) __T(type, std::forward(val)); obj->gc.enabled = false; _no_gc.push_back(obj); return obj; } +#if DEBUG_GC_STATS inline static std::map deleted; +#endif ~ManagedHeap(){ - for(PyObject* obj: _no_gc) delete obj; + for(PyObject* obj: _no_gc) obj->~PyObject(), pool.dealloc(obj); #if DEBUG_GC_STATS for(auto& [type, count]: deleted){ std::cout << "GC: " << obj_type_name(vm, type) << "=" << count << std::endl; @@ -68,8 +74,10 @@ struct ManagedHeap{ obj->gc.marked = false; alive.push_back(obj); }else{ +#if DEBUG_GC_STATS deleted[obj->type] += 1; - delete obj; +#endif + obj->~PyObject(), pool.dealloc(obj); } } diff --git a/src/memory.h b/src/memory.h index e7559865..2fc53ba8 100644 --- a/src/memory.h +++ b/src/memory.h @@ -105,4 +105,227 @@ struct FreeListA { } }; + +struct LinkedListNode{ + LinkedListNode* prev; + LinkedListNode* next; +}; + +template +struct DoubleLinkedList{ + static_assert(std::is_base_of_v); + int _size; + LinkedListNode head; + LinkedListNode tail; + + DoubleLinkedList(): _size(0){ + head.prev = nullptr; + head.next = &tail; + tail.prev = &head; + tail.next = nullptr; + } + + void push_back(T* node){ + node->prev = tail.prev; + node->next = &tail; + tail.prev->next = node; + tail.prev = node; + _size++; + } + + void push_front(T* node){ + node->prev = &head; + node->next = head.next; + head.next->prev = node; + head.next = node; + _size++; + } + + void pop_back(){ +#if DEBUG_MEMORY_POOL + if(empty()) throw std::runtime_error("DoubleLinkedList::pop_back() called on empty list"); +#endif + tail.prev->prev->next = &tail; + tail.prev = tail.prev->prev; + _size--; + } + + void pop_front(){ +#if DEBUG_MEMORY_POOL + if(empty()) throw std::runtime_error("DoubleLinkedList::pop_front() called on empty list"); +#endif + head.next->next->prev = &head; + head.next = head.next->next; + _size--; + } + + T* back() const { +#if DEBUG_MEMORY_POOL + if(empty()) throw std::runtime_error("DoubleLinkedList::back() called on empty list"); +#endif + return static_cast(tail.prev); + } + + T* front() const { +#if DEBUG_MEMORY_POOL + if(empty()) throw std::runtime_error("DoubleLinkedList::front() called on empty list"); +#endif + return static_cast(head.next); + } + + void erase(T* node){ +#if DEBUG_MEMORY_POOL + if(empty()) throw std::runtime_error("DoubleLinkedList::erase() called on empty list"); + LinkedListNode* n = head.next; + while(n != &tail){ + if(n == node) break; + n = n->next; + } + if(n != node) throw std::runtime_error("DoubleLinkedList::erase() called on node not in the list"); +#endif + node->prev->next = node->next; + node->next->prev = node->prev; + _size--; + } + + void move_all_back(DoubleLinkedList& other){ + if(other.empty()) return; + other.tail.prev->next = &tail; + tail.prev->next = other.head.next; + other.head.next->prev = tail.prev; + tail.prev = other.tail.prev; + _size += other._size; + other.head.next = &other.tail; + other.tail.prev = &other.head; + other._size = 0; + } + + bool empty() const { +#if DEBUG_MEMORY_POOL + if(size() == 0){ + if(head.next != &tail || tail.prev != &head){ + throw std::runtime_error("DoubleLinkedList::size() returned 0 but the list is not empty"); + } + return true; + } +#endif + return _size == 0; + } + + int size() const { return _size; } + + void apply(std::function func){ + LinkedListNode* p = head.next; + while(p != &tail){ + LinkedListNode* next = p->next; + func(static_cast(p)); + p = next; + } + } +}; + +template +struct MemoryPool{ + static const size_t __MaxBlocks = 256*1024 / __BlockSize; + struct Block{ + void* arena; + char data[__BlockSize]; + }; + + struct Arena: LinkedListNode{ + Block _blocks[__MaxBlocks]; + Block* _free_list[__MaxBlocks]; + int _free_list_size; + + Arena(): _free_list_size(__MaxBlocks) { + for(int i=0; i<__MaxBlocks; i++){ + _blocks[i].arena = this; + _free_list[i] = &_blocks[i]; + } + } + + bool empty() const { return _free_list_size == 0; } + bool full() const { return _free_list_size == __MaxBlocks; } + + Block* alloc(){ +#if DEBUG_MEMORY_POOL + if(empty()) throw std::runtime_error("Arena::alloc() called on empty arena"); +#endif + _free_list_size--; + return _free_list[_free_list_size]; + } + + void dealloc(Block* block){ +#if DEBUG_MEMORY_POOL + if(full()) throw std::runtime_error("Arena::dealloc() called on full arena"); +#endif + _free_list[_free_list_size] = block; + _free_list_size++; + } + }; + + DoubleLinkedList _arenas; + DoubleLinkedList _empty_arenas; + DoubleLinkedList _full_arenas; + + template + void* alloc() { return alloc(sizeof(__T)); } + + void* alloc(size_t size){ + if(size > __BlockSize){ + void* p = malloc(sizeof(void*) + size); + memset(p, 0, sizeof(void*)); + return (char*)p + sizeof(void*); + } + + if(_arenas.empty()){ + if(_full_arenas.empty()){ + _arenas.push_back(new Arena()); + }else{ + _arenas.move_all_back(_full_arenas); + } + } + Arena* arena = _arenas.back(); + void* p = arena->alloc()->data; + if(arena->empty()){ + _arenas.pop_back(); + _empty_arenas.push_back(arena); + } + return p; + } + + void dealloc(void* p){ + Block* block = (Block*)((char*)p - sizeof(void*)); + if(block->arena == nullptr){ + free(block); + }else{ + Arena* arena = (Arena*)block->arena; + if(arena->empty()){ + _empty_arenas.erase(arena); + _arenas.push_front(arena); + arena->dealloc(block); + }else{ + arena->dealloc(block); + if(arena->full()){ // && _arenas.size() > 2 + _arenas.erase(arena); + if(_full_arenas.size() < 8){ + _full_arenas.push_back(arena); + }else{ + delete arena; + } + } + } + } + } + + ~MemoryPool(){ + // std::cout << _arenas.size() << std::endl; + // std::cout << _empty_arenas.size() << std::endl; + // std::cout << _full_arenas.size() << std::endl; + _arenas.apply([](Arena* arena){ delete arena; }); + _empty_arenas.apply([](Arena* arena){ delete arena; }); + _full_arenas.apply([](Arena* arena){ delete arena; }); + } +}; + }; // namespace pkpy