This commit is contained in:
blueloveTH 2023-08-11 00:10:54 +08:00
parent 162c597093
commit 9e9cbcdffd
11 changed files with 298 additions and 274 deletions

View File

@ -41,7 +41,7 @@ struct ManagedHeap{
PyObject* gcnew(Type type, Args&&... args){
using __T = Py_<std::decay_t<T>>;
// https://github.com/blueloveTH/pocketpy/issues/94#issuecomment-1594784476
PyObject* obj = new(pool64.alloc<__T>()) Py_<std::decay_t<T>>(type, std::forward<Args>(args)...);
PyObject* obj = new(pool64_alloc<__T>()) Py_<std::decay_t<T>>(type, std::forward<Args>(args)...);
gen.push_back(obj);
gc_counter++;
return obj;
@ -51,7 +51,7 @@ struct ManagedHeap{
PyObject* _new(Type type, Args&&... args){
using __T = Py_<std::decay_t<T>>;
// https://github.com/blueloveTH/pocketpy/issues/94#issuecomment-1594784476
PyObject* obj = new(pool64.alloc<__T>()) Py_<std::decay_t<T>>(type, std::forward<Args>(args)...);
PyObject* obj = new(pool64_alloc<__T>()) Py_<std::decay_t<T>>(type, std::forward<Args>(args)...);
obj->gc.enabled = false;
_no_gc.push_back(obj);
return obj;

View File

@ -4,249 +4,20 @@
namespace pkpy{
struct LinkedListNode{
LinkedListNode* prev;
LinkedListNode* next;
};
void* pool64_alloc(size_t);
void pool64_dealloc(void*);
void* pool128_alloc(size_t);
void pool128_dealloc(void*);
template<typename T>
struct DoubleLinkedList{
static_assert(std::is_base_of_v<LinkedListNode, T>);
int _size;
LinkedListNode head;
LinkedListNode tail;
void* pool64_alloc(){
return pool64_alloc(sizeof(T));
}
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 PK_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 PK_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 PK_DEBUG_MEMORY_POOL
if(empty()) throw std::runtime_error("DoubleLinkedList::back() called on empty list");
#endif
return static_cast<T*>(tail.prev);
}
T* front() const {
#if PK_DEBUG_MEMORY_POOL
if(empty()) throw std::runtime_error("DoubleLinkedList::front() called on empty list");
#endif
return static_cast<T*>(head.next);
}
void erase(T* node){
#if PK_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<T>& 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 PK_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; }
template<typename Func>
void apply(Func func){
LinkedListNode* p = head.next;
while(p != &tail){
LinkedListNode* next = p->next;
func(static_cast<T*>(p));
p = next;
}
}
};
template<int __BlockSize=128>
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;
bool dirty;
Arena(): _free_list_size(__MaxBlocks), dirty(false){
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; }
size_t allocated_size() const{
return __BlockSize * (__MaxBlocks - _free_list_size);
}
Block* alloc(){
#if PK_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 PK_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++;
}
};
MemoryPool() = default;
MemoryPool(const MemoryPool&) = delete;
MemoryPool& operator=(const MemoryPool&) = delete;
MemoryPool(MemoryPool&&) = delete;
MemoryPool& operator=(MemoryPool&&) = delete;
DoubleLinkedList<Arena> _arenas;
DoubleLinkedList<Arena> _empty_arenas;
template<typename __T>
void* alloc() { return alloc(sizeof(__T)); }
void* alloc(size_t size){
PK_GLOBAL_SCOPE_LOCK();
#if PK_DEBUG_NO_MEMORY_POOL
return malloc(size);
#endif
if(size > __BlockSize){
void* p = malloc(sizeof(void*) + size);
memset(p, 0, sizeof(void*));
return (char*)p + sizeof(void*);
}
if(_arenas.empty()){
// std::cout << _arenas.size() << ',' << _empty_arenas.size() << ',' << _full_arenas.size() << std::endl;
_arenas.push_back(new Arena());
}
Arena* arena = _arenas.back();
void* p = arena->alloc()->data;
if(arena->empty()){
_arenas.pop_back();
arena->dirty = true;
_empty_arenas.push_back(arena);
}
return p;
}
void dealloc(void* p){
PK_GLOBAL_SCOPE_LOCK();
#if PK_DEBUG_NO_MEMORY_POOL
free(p);
return;
#endif
#if PK_DEBUG_MEMORY_POOL
if(p == nullptr) throw std::runtime_error("MemoryPool::dealloc() called on nullptr");
#endif
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() && arena->dirty){
_arenas.erase(arena);
delete arena;
}
}
}
}
size_t allocated_size() {
size_t n = 0;
_arenas.apply([&n](Arena* arena){ n += arena->allocated_size(); });
_empty_arenas.apply([&n](Arena* arena){ n += arena->allocated_size(); });
return n;
}
~MemoryPool(){
_arenas.apply([](Arena* arena){ delete arena; });
_empty_arenas.apply([](Arena* arena){ delete arena; });
}
};
inline MemoryPool<64> pool64;
inline MemoryPool<128> pool128;
template<typename T>
void* pool128_alloc(){
return pool128_alloc(sizeof(T));
}
}; // namespace pkpy

View File

@ -38,7 +38,7 @@ while(!_items[i].first.empty()) { \
}
#define NAMEDICT_ALLOC() \
_items = (Item*)pool128.alloc(_capacity * sizeof(Item)); \
_items = (Item*)pool128_alloc(_capacity * sizeof(Item)); \
memset(_items, 0, _capacity * sizeof(Item)); \
NameDictImpl(float load_factor=0.67f):
@ -54,14 +54,14 @@ while(!_items[i].first.empty()) { \
}
NameDictImpl& operator=(const NameDictImpl& other) {
pool128.dealloc(_items);
pool128_dealloc(_items);
memcpy(this, &other, sizeof(NameDictImpl));
NAMEDICT_ALLOC()
for(int i=0; i<_capacity; i++) _items[i] = other._items[i];
return *this;
}
~NameDictImpl(){ pool128.dealloc(_items); }
~NameDictImpl(){ pool128_dealloc(_items); }
NameDictImpl(NameDictImpl&&) = delete;
NameDictImpl& operator=(NameDictImpl&&) = delete;
@ -103,7 +103,7 @@ while(!_items[i].first.empty()) { \
if(ok) FATAL_ERROR();
_items[j] = old_items[i];
}
pool128.dealloc(old_items);
pool128_dealloc(old_items);
}
void _try_perfect_rehash(){

View File

@ -119,7 +119,7 @@ struct PyObject{
virtual ~PyObject();
void enable_instance_dict(float lf=kInstAttrLoadFactor) {
_attr = new(pool64.alloc<NameDict>()) NameDict(lf);
_attr = new(pool64_alloc<NameDict>()) NameDict(lf);
}
};

View File

@ -16,15 +16,15 @@ struct pod_vector{
T* _data;
pod_vector(): _size(0), _capacity(N) {
_data = (T*)pool64.alloc(_capacity * sizeof(T));
_data = (T*)pool64_alloc(_capacity * sizeof(T));
}
pod_vector(int size): _size(size), _capacity(std::max(N, size)) {
_data = (T*)pool64.alloc(_capacity * sizeof(T));
_data = (T*)pool64_alloc(_capacity * sizeof(T));
}
pod_vector(const pod_vector& other): _size(other._size), _capacity(other._capacity) {
_data = (T*)pool64.alloc(_capacity * sizeof(T));
_data = (T*)pool64_alloc(_capacity * sizeof(T));
memcpy(_data, other._data, sizeof(T) * _size);
}
@ -36,7 +36,7 @@ struct pod_vector{
}
pod_vector& operator=(pod_vector&& other) noexcept {
if(_data!=nullptr) pool64.dealloc(_data);
if(_data!=nullptr) pool64_dealloc(_data);
_size = other._size;
_capacity = other._capacity;
_data = other._data;
@ -63,10 +63,10 @@ struct pod_vector{
if(cap <= _capacity) return;
_capacity = cap;
T* old_data = _data;
_data = (T*)pool64.alloc(_capacity * sizeof(T));
_data = (T*)pool64_alloc(_capacity * sizeof(T));
if(old_data!=nullptr){
memcpy(_data, old_data, sizeof(T) * _size);
pool64.dealloc(old_data);
pool64_dealloc(old_data);
}
}
@ -115,7 +115,7 @@ struct pod_vector{
}
~pod_vector() {
if(_data!=nullptr) pool64.dealloc(_data);
if(_data!=nullptr) pool64_dealloc(_data);
}
};

View File

@ -5,9 +5,9 @@ namespace pkpy{
Dict::Dict(VM* vm): vm(vm), _capacity(__Capacity),
_mask(__Capacity-1),
_size(0), _critical_size(__Capacity*__LoadFactor+0.5f), _head_idx(-1), _tail_idx(-1){
_items = (Item*)pool128.alloc(_capacity * sizeof(Item));
_items = (Item*)pool128_alloc(_capacity * sizeof(Item));
memset(_items, 0, _capacity * sizeof(Item));
_nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
_nodes = (ItemNode*)pool64_alloc(_capacity * sizeof(ItemNode));
memset(_nodes, -1, _capacity * sizeof(ItemNode));
}
@ -33,9 +33,9 @@ namespace pkpy{
_critical_size = other._critical_size;
_head_idx = other._head_idx;
_tail_idx = other._tail_idx;
_items = (Item*)pool128.alloc(_capacity * sizeof(Item));
_items = (Item*)pool128_alloc(_capacity * sizeof(Item));
memcpy(_items, other._items, _capacity * sizeof(Item));
_nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
_nodes = (ItemNode*)pool64_alloc(_capacity * sizeof(ItemNode));
memcpy(_nodes, other._nodes, _capacity * sizeof(ItemNode));
}
@ -73,9 +73,9 @@ namespace pkpy{
_head_idx = -1;
_tail_idx = -1;
_items = (Item*)pool128.alloc(_capacity * sizeof(Item));
_items = (Item*)pool128_alloc(_capacity * sizeof(Item));
memset(_items, 0, _capacity * sizeof(Item));
_nodes = (ItemNode*)pool64.alloc(_capacity * sizeof(ItemNode));
_nodes = (ItemNode*)pool64_alloc(_capacity * sizeof(ItemNode));
memset(_nodes, -1, _capacity * sizeof(ItemNode));
// copy old items to new dict
@ -84,8 +84,8 @@ namespace pkpy{
set(old_items[i].first, old_items[i].second);
i = old_nodes[i].next;
}
pool128.dealloc(old_items);
pool64.dealloc(old_nodes);
pool128_dealloc(old_items);
pool64_dealloc(old_nodes);
}
@ -167,8 +167,8 @@ namespace pkpy{
Dict::~Dict(){
if(_items==nullptr) return;
pool128.dealloc(_items);
pool64.dealloc(_nodes);
pool128_dealloc(_items);
pool64_dealloc(_nodes);
}
void Dict::_gc_mark() const{

View File

@ -14,7 +14,7 @@ namespace pkpy{
#endif
if(_gc_on_delete) _gc_on_delete(vm, obj);
obj->~PyObject();
pool64.dealloc(obj);
pool64_dealloc(obj);
}
}
@ -47,8 +47,8 @@ namespace pkpy{
}
ManagedHeap::~ManagedHeap(){
for(PyObject* obj: _no_gc) { obj->~PyObject(); pool64.dealloc(obj); }
for(PyObject* obj: gen) { obj->~PyObject(); pool64.dealloc(obj); }
for(PyObject* obj: _no_gc) { obj->~PyObject(); pool64_dealloc(obj); }
for(PyObject* obj: gen) { obj->~PyObject(); pool64_dealloc(obj); }
#if PK_DEBUG_GC_STATS
for(auto& [type, count]: deleted){
std::cout << "GC: " << obj_type_name(vm, type) << "=" << count << std::endl;

253
src/memory.cpp Normal file
View File

@ -0,0 +1,253 @@
#include "pocketpy/memory.h"
namespace pkpy{
struct LinkedListNode{
LinkedListNode* prev;
LinkedListNode* next;
};
template<typename T>
struct DoubleLinkedList{
static_assert(std::is_base_of_v<LinkedListNode, T>);
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 PK_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 PK_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 PK_DEBUG_MEMORY_POOL
if(empty()) throw std::runtime_error("DoubleLinkedList::back() called on empty list");
#endif
return static_cast<T*>(tail.prev);
}
T* front() const {
#if PK_DEBUG_MEMORY_POOL
if(empty()) throw std::runtime_error("DoubleLinkedList::front() called on empty list");
#endif
return static_cast<T*>(head.next);
}
void erase(T* node){
#if PK_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<T>& 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 PK_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; }
template<typename Func>
void apply(Func func){
LinkedListNode* p = head.next;
while(p != &tail){
LinkedListNode* next = p->next;
func(static_cast<T*>(p));
p = next;
}
}
};
template<int __BlockSize=128>
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;
bool dirty;
Arena(): _free_list_size(__MaxBlocks), dirty(false){
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; }
size_t allocated_size() const{
return __BlockSize * (__MaxBlocks - _free_list_size);
}
Block* alloc(){
#if PK_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 PK_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++;
}
};
MemoryPool() = default;
MemoryPool(const MemoryPool&) = delete;
MemoryPool& operator=(const MemoryPool&) = delete;
MemoryPool(MemoryPool&&) = delete;
MemoryPool& operator=(MemoryPool&&) = delete;
DoubleLinkedList<Arena> _arenas;
DoubleLinkedList<Arena> _empty_arenas;
void* alloc(size_t size){
PK_GLOBAL_SCOPE_LOCK();
#if PK_DEBUG_NO_MEMORY_POOL
return malloc(size);
#endif
if(size > __BlockSize){
void* p = malloc(sizeof(void*) + size);
memset(p, 0, sizeof(void*));
return (char*)p + sizeof(void*);
}
if(_arenas.empty()){
// std::cout << _arenas.size() << ',' << _empty_arenas.size() << ',' << _full_arenas.size() << std::endl;
_arenas.push_back(new Arena());
}
Arena* arena = _arenas.back();
void* p = arena->alloc()->data;
if(arena->empty()){
_arenas.pop_back();
arena->dirty = true;
_empty_arenas.push_back(arena);
}
return p;
}
void dealloc(void* p){
PK_GLOBAL_SCOPE_LOCK();
#if PK_DEBUG_NO_MEMORY_POOL
free(p);
return;
#endif
#if PK_DEBUG_MEMORY_POOL
if(p == nullptr) throw std::runtime_error("MemoryPool::dealloc() called on nullptr");
#endif
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() && arena->dirty){
_arenas.erase(arena);
delete arena;
}
}
}
}
// size_t allocated_size() {
// size_t n = 0;
// _arenas.apply([&n](Arena* arena){ n += arena->allocated_size(); });
// _empty_arenas.apply([&n](Arena* arena){ n += arena->allocated_size(); });
// return n;
// }
~MemoryPool(){
_arenas.apply([](Arena* arena){ delete arena; });
_empty_arenas.apply([](Arena* arena){ delete arena; });
}
};
static MemoryPool<64> pool64;
static MemoryPool<128> pool128;
void* pool64_alloc(size_t size){ return pool64.alloc(size); }
void pool64_dealloc(void* p){ pool64.dealloc(p); }
void* pool128_alloc(size_t size){ return pool128.alloc(size); }
void pool128_dealloc(void* p){ pool128.dealloc(p); }
}

View File

@ -4,6 +4,6 @@ namespace pkpy{
PyObject::~PyObject() {
if(_attr == nullptr) return;
_attr->~NameDict();
pool64.dealloc(_attr);
pool64_dealloc(_attr);
}
} // namespace pkpy

View File

@ -75,12 +75,12 @@ int utf8len(unsigned char c, bool suppress){
if(size <= 16){
this->data = _inlined;
}else{
this->data = (char*)pool64.alloc(size);
this->data = (char*)pool64_alloc(size);
}
}
Str& Str::operator=(const Str& other){
if(!is_inlined()) pool64.dealloc(data);
if(!is_inlined()) pool64_dealloc(data);
size = other.size;
is_ascii = other.is_ascii;
_cached_c_str = nullptr;
@ -150,7 +150,7 @@ int utf8len(unsigned char c, bool suppress){
}
Str::~Str(){
if(!is_inlined()) pool64.dealloc(data);
if(!is_inlined()) pool64_dealloc(data);
if(_cached_c_str != nullptr) free((void*)_cached_c_str);
}

View File

@ -6,7 +6,7 @@ Tuple::Tuple(int n){
if(n <= 3){
this->_args = _inlined;
}else{
this->_args = (PyObject**)pool64.alloc(n * sizeof(void*));
this->_args = (PyObject**)pool64_alloc(n * sizeof(void*));
}
this->_size = n;
}
@ -38,7 +38,7 @@ Tuple::Tuple(std::initializer_list<PyObject*> list): Tuple(list.size()){
for(PyObject* obj: list) _args[i++] = obj;
}
Tuple::~Tuple(){ if(!is_inlined()) pool64.dealloc(_args); }
Tuple::~Tuple(){ if(!is_inlined()) pool64_dealloc(_args); }
List ArgsView::to_list() const{
List ret(size());