Сообщение Re: Порядок создания и удаления в графе (StackAllocator) от 31.12.2019 12:24
Изменено 31.12.2019 14:53 Erop
Re: Порядок создания и удаления в графе
Здравствуйте, SuhanovSergey, Вы писали:
SS>Подсчёт ссылок (shared_ptr) решил бы проблему. Но он привносит другие проблемы, и по ряду причин переписывание на shared_ptr не является опцией.
SS>Нужно чтобы зависимости приходили классам снаружи, ибо DI.
А может можно отложить немного разрушение объектов?
Ну, типа, у нас есть какая-то операция, в течении которой у нас создаётся куча объектов с зависимостями. Вместо того, что бы тратить время на разборки с тем, кого за кем удалять, я просто всё, что хочу удалить, помещаю в множество владеющих указателей (hash_map указателей). А в какой-то момент операции, когда я уверен, что множество конситентное, грохать всё множество скопом?
Например, у меня есть какой-то граф объектов. Скажем дерево перебора игры из позиций и всяких гипотез и стратегий, и наборов ходов, ассоциированных с ними.
Когда я или противник делаем ход, надо профильтровать это дерево, оставив в нём только то, что ещё актуально.
Ну я фильтрую это всё простым обходом, помещая объекты в очередь на удаление. А потом удаляю всю очередь.
Можно, кстати, ещё обойти перед этим дерево и вымарать из множества все те объекты, которые ещё нужны.
При этом, конкретно в вопросе фильтрации, можно удалять то, что ещё нужно, во время возвратов в рекурсивном обходе, то есть с O(1) оверхедом...
Это будет работать примерно так же, как GC, только без тормозов.
Или, раз уж речь идёт о DI и каком-то внешнем создании объектов и имущества. Можно в том же менеджере объектов, который их создаёт, регистрировать связи между объектами. И им же и решать, что и когда удалять.
Но у меня сложилось впечатление, что речь идёт о таком паттерне, что у нас есть какой-то стек. То есть идёт, например, рекурсивный перебор гипотез, мы по ходу пьесы, создаём какие-то объекты, причём внешним по отношению к объектам и перебору менеджером, и что-то там такое делаем, а потом откатываемся по стеку, и всё разрушаем, что насоздавали.
Кроме, быть может, каких-то избранных, которые мы возвращаем из перебора наверх или куда-то во внешний объект сливаем.
Тогда их можно складывать в список-стек, и при откате стек чистить. А если хотим разрушение объекта отложить, вынимать объект из этого стека или перекладывать подальше (например сразу после объекта, которому мы передали имущество.
Кстати, если объекты такие, что ничем не владеют, и наверх отдаются редко, то можно аллокировать их на особом отдельном стековом аллокаторе, а если объект надо вернуть, то копировать объекты на другой аллокатор. И при рекурсивном откате, можно просто отматывать аллокатор к соответствующему состоянию.
Будет работать очень быстро и просто.
SS>Подсчёт ссылок (shared_ptr) решил бы проблему. Но он привносит другие проблемы, и по ряду причин переписывание на shared_ptr не является опцией.
SS>Нужно чтобы зависимости приходили классам снаружи, ибо DI.
А может можно отложить немного разрушение объектов?
Ну, типа, у нас есть какая-то операция, в течении которой у нас создаётся куча объектов с зависимостями. Вместо того, что бы тратить время на разборки с тем, кого за кем удалять, я просто всё, что хочу удалить, помещаю в множество владеющих указателей (hash_map указателей). А в какой-то момент операции, когда я уверен, что множество конситентное, грохать всё множество скопом?
Например, у меня есть какой-то граф объектов. Скажем дерево перебора игры из позиций и всяких гипотез и стратегий, и наборов ходов, ассоциированных с ними.
Когда я или противник делаем ход, надо профильтровать это дерево, оставив в нём только то, что ещё актуально.
Ну я фильтрую это всё простым обходом, помещая объекты в очередь на удаление. А потом удаляю всю очередь.
Можно, кстати, ещё обойти перед этим дерево и вымарать из множества все те объекты, которые ещё нужны.
При этом, конкретно в вопросе фильтрации, можно удалять то, что ещё нужно, во время возвратов в рекурсивном обходе, то есть с O(1) оверхедом...
Это будет работать примерно так же, как GC, только без тормозов.
Или, раз уж речь идёт о DI и каком-то внешнем создании объектов и имущества. Можно в том же менеджере объектов, который их создаёт, регистрировать связи между объектами. И им же и решать, что и когда удалять.
Но у меня сложилось впечатление, что речь идёт о таком паттерне, что у нас есть какой-то стек. То есть идёт, например, рекурсивный перебор гипотез, мы по ходу пьесы, создаём какие-то объекты, причём внешним по отношению к объектам и перебору менеджером, и что-то там такое делаем, а потом откатываемся по стеку, и всё разрушаем, что насоздавали.
Кроме, быть может, каких-то избранных, которые мы возвращаем из перебора наверх или куда-то во внешний объект сливаем.
Тогда их можно складывать в список-стек, и при откате стек чистить. А если хотим разрушение объекта отложить, вынимать объект из этого стека или перекладывать подальше (например сразу после объекта, которому мы передали имущество.
Кстати, если объекты такие, что ничем не владеют, и наверх отдаются редко, то можно аллокировать их на особом отдельном стековом аллокаторе, а если объект надо вернуть, то копировать объекты на другой аллокатор. И при рекурсивном откате, можно просто отматывать аллокатор к соответствующему состоянию.
Будет работать очень быстро и просто.
Re: Порядок создания и удаления в графе (StackAllocator)
Здравствуйте, SuhanovSergey, Вы писали:
SS>Подсчёт ссылок (shared_ptr) решил бы проблему. Но он привносит другие проблемы, и по ряду причин переписывание на shared_ptr не является опцией.
SS>Нужно чтобы зависимости приходили классам снаружи, ибо DI.
А может можно отложить немного разрушение объектов?
Ну, типа, у нас есть какая-то операция, в течении которой у нас создаётся куча объектов с зависимостями. Вместо того, что бы тратить время на разборки с тем, кого за кем удалять, я просто всё, что хочу удалить, помещаю в множество владеющих указателей (hash_map указателей). А в какой-то момент операции, когда я уверен, что множество конситентное, грохать всё множество скопом?
Например, у меня есть какой-то граф объектов. Скажем дерево перебора игры из позиций и всяких гипотез и стратегий, и наборов ходов, ассоциированных с ними.
Когда я или противник делаем ход, надо профильтровать это дерево, оставив в нём только то, что ещё актуально.
Ну я фильтрую это всё простым обходом, помещая объекты в очередь на удаление. А потом удаляю всю очередь.
Можно, кстати, ещё обойти перед этим дерево и вымарать из множества все те объекты, которые ещё нужны.
При этом, конкретно в вопросе фильтрации, можно удалять то, что ещё нужно, во время возвратов в рекурсивном обходе, то есть с O(1) оверхедом...
Это будет работать примерно так же, как GC, только без тормозов.
Или, раз уж речь идёт о DI и каком-то внешнем создании объектов и имущества. Можно в том же менеджере объектов, который их создаёт, регистрировать связи между объектами. И им же и решать, что и когда удалять.
Но у меня сложилось впечатление, что речь идёт о таком паттерне, что у нас есть какой-то стек. То есть идёт, например, рекурсивный перебор гипотез, мы по ходу пьесы, создаём какие-то объекты, причём внешним по отношению к объектам и перебору менеджером, и что-то там такое делаем, а потом откатываемся по стеку, и всё разрушаем, что насоздавали.
Кроме, быть может, каких-то избранных, которые мы возвращаем из перебора наверх или куда-то во внешний объект сливаем.
Тогда их можно складывать в список-стек, и при откате стек чистить. А если хотим разрушение объекта отложить, вынимать объект из этого стека или перекладывать подальше (например сразу после объекта, которому мы передали имущество.
Кстати, если объекты такие, что ничем не владеют, и наверх отдаются редко, то можно аллокировать их на особом отдельном стековом аллокаторе, а если объект надо вернуть, то копировать объекты на другой аллокатор. И при рекурсивном откате, можно просто отматывать аллокатор к соответствующему состоянию.
Будет работать очень быстро и просто.
---------------------------
p. s.
Если опустить всякие нудные подробности, вроде выравнивания, ограничения общего объёма аллокаций, подсчёта уже аллокированной памяти и степени фрагментации, то я имею в виду что-то вроде:
SS>Подсчёт ссылок (shared_ptr) решил бы проблему. Но он привносит другие проблемы, и по ряду причин переписывание на shared_ptr не является опцией.
SS>Нужно чтобы зависимости приходили классам снаружи, ибо DI.
А может можно отложить немного разрушение объектов?
Ну, типа, у нас есть какая-то операция, в течении которой у нас создаётся куча объектов с зависимостями. Вместо того, что бы тратить время на разборки с тем, кого за кем удалять, я просто всё, что хочу удалить, помещаю в множество владеющих указателей (hash_map указателей). А в какой-то момент операции, когда я уверен, что множество конситентное, грохать всё множество скопом?
Например, у меня есть какой-то граф объектов. Скажем дерево перебора игры из позиций и всяких гипотез и стратегий, и наборов ходов, ассоциированных с ними.
Когда я или противник делаем ход, надо профильтровать это дерево, оставив в нём только то, что ещё актуально.
Ну я фильтрую это всё простым обходом, помещая объекты в очередь на удаление. А потом удаляю всю очередь.
Можно, кстати, ещё обойти перед этим дерево и вымарать из множества все те объекты, которые ещё нужны.
При этом, конкретно в вопросе фильтрации, можно удалять то, что ещё нужно, во время возвратов в рекурсивном обходе, то есть с O(1) оверхедом...
Это будет работать примерно так же, как GC, только без тормозов.
Или, раз уж речь идёт о DI и каком-то внешнем создании объектов и имущества. Можно в том же менеджере объектов, который их создаёт, регистрировать связи между объектами. И им же и решать, что и когда удалять.
Но у меня сложилось впечатление, что речь идёт о таком паттерне, что у нас есть какой-то стек. То есть идёт, например, рекурсивный перебор гипотез, мы по ходу пьесы, создаём какие-то объекты, причём внешним по отношению к объектам и перебору менеджером, и что-то там такое делаем, а потом откатываемся по стеку, и всё разрушаем, что насоздавали.
Кроме, быть может, каких-то избранных, которые мы возвращаем из перебора наверх или куда-то во внешний объект сливаем.
Тогда их можно складывать в список-стек, и при откате стек чистить. А если хотим разрушение объекта отложить, вынимать объект из этого стека или перекладывать подальше (например сразу после объекта, которому мы передали имущество.
Кстати, если объекты такие, что ничем не владеют, и наверх отдаются редко, то можно аллокировать их на особом отдельном стековом аллокаторе, а если объект надо вернуть, то копировать объекты на другой аллокатор. И при рекурсивном откате, можно просто отматывать аллокатор к соответствующему состоянию.
Будет работать очень быстро и просто.
---------------------------
p. s.
Если опустить всякие нудные подробности, вроде выравнивания, ограничения общего объёма аллокаций, подсчёта уже аллокированной памяти и степени фрагментации, то я имею в виду что-то вроде:
#include <iostream>
#include <list>
#include <assert.h>
using namespace std;
template<size_t TChunkSize=1024*1024, typename TItem=void> class StackAllocator;
template<size_t TChunkSize, typename TItem> class StackAllocator {
class Chunk {
TItem data[TChunkSize];
TItem* cur;
const TItem* end() const { return this->data+TChunkSize; }
public:
size_t UsedSize() const { return this->cur - this->data; }
size_t FreeSize() const { return this->end() - this->cur; }
TItem* Alloc( size_t sz ) {
if( sz > this->FreeSize() ) {
return 0;
}
TItem* res = this->cur;
this->cur += sz;
assert( this->cur <= this->end() );
return res;
}
void setAllocatedSize( size_t new_allocated_size ) {
assert( new_allocated_size <= TChunkSize );
this->cur = this->data + new_allocated_size;
}
};
typedef std::list<Chunk> TChunks;
typedef typename TChunks::iterator TChunksItr;
TChunks chunks;
TChunksItr cur_chunk;
void get_free_chunk() {
TChunksItr next = this->cur_chunk;
if( this->cur_chunk == this->chunks.end() || ++next == this->chunks.end() ) {
this->chunks.emplace_back();
next == this->chunks.end();
--next;
}
assert( this->chunks.end() != next );
this->cur_chunk = next;
next->setAllocatedSize( 0 );
}
TChunksItr find_chunk(const Chunk* chunkPtr ) {
for( typename std::list<Chunk>::reverse_iterator cur = this->chunks.rbegin();
cur != this->chunks.rend(); ++cur ){
if( &*cur == chunkPtr ) {
TChunksItr res = cur.base();
return --res;
}
}
return this->chunks.end();
}
public :
struct State {
const Chunk* chunkPtr;
size_t usedSize;
// Число, полезное для отладки. Обычно в рамках одной сессии программы оно
// однозначно соответствует состоянию аллокатора.
intptr_t debug_int() const { return this->usedSize + (intptr_t)this->chunkPtr; }
};
StackAllocator() {
this->cur_chunk = this->chunks.end();
this->get_free_chunk();
}
TItem* Alloc( size_t sz ) {
assert( sz <= TChunkSize );
TItem* res = this->cur_chunk->Alloc( sz );
if( res == 0 ) {
this->get_free_chunk();
res = this->cur_chunk->Alloc( sz );
}
assert( res != 0 );
return res;
}
bool IsValidState( const State& st ) const {
TChunksItr pos = const_cast<StackAllocator*>(this)->find_chunk( st.chunkPtr );
return pos != this->chunks.end() && st.usedSize <= pos->UsedSize();
}
State GetState() const {
State res = { &*this->cur_chunk, this->cur_chunk->UsedSize() };
return res;
}
void Rewind( const State& st ) {
TChunksItr pos = this->find_chunk( st.chunkPtr );
assert( pos != this->chunks.end() && st.usedSize <= pos->UsedSize() );
pos->setAllocatedSize(st.usedSize);
this->cur_chunk = pos;
}
//private:
template<typename TCompatibleItem> class compatibleAllocator {
typedef StackAllocator<TChunkSize, TItem> TAllocator;
TAllocator allocator;
public:
typedef TAllocator::State State;
TCompatibleItem* Alloc( size_t sz )
{ return static_cast<TCompatibleItem*>( this->allocator.Alloc( sz )); }
State GetState() const { return this->allocator.GetState(); }
void Rewind( State st ) { this->allocator.Rewind( st ); }
};
};
template<size_t TChunkSize, typename TItem>
class StackAllocator<TChunkSize, const TItem> :
public StackAllocator<TChunkSize, TItem>::template compatibleAllocator<const TItem> {};
template<size_t TChunkSize, typename TItem>
class StackAllocator<TChunkSize, volatile TItem> :
public StackAllocator<TChunkSize, TItem>::template compatibleAllocator<volatile TItem> {};
template<size_t TChunkSize, typename TItem>
class StackAllocator<TChunkSize, const volatile TItem> :
public StackAllocator<TChunkSize, TItem>::template compatibleAllocator<const volatile TItem> {};
template<size_t TChunkSize>
class StackAllocator<TChunkSize, void> :
public StackAllocator<TChunkSize, char>::template compatibleAllocator<void> {};
/////////////////////////////////////////////////////////////
// TEST IT
////////////////////////////////////////////////////////////
StackAllocator<500, char> a;
void test_it( int n, int max_n=5 ) {
if( n <= 0 ) {
return;
}
std::string title( std::max(max_n-n, 0), '\t' );
std::cout << title << "n:" << n << "+State:" << (size_t)a.GetState().debug_int() << std::endl;
StackAllocator<500>::State st = a.GetState();
// Тут что-то рекурсивно аллокируем:
for( int i = 0; i < 5; i++ ) {
a.Alloc(64);
test_it( n-1, max_n );
a.Alloc(64);
}
// Теперь отматываем стек, как было, тем самым освобождая всё аллокированное
a.Rewind( st );
std::cout << title << "n:" << n << "-State:" << (size_t)a.GetState().debug_int() << std::endl;
}
int main() {
test_it( 3 );
// your code goes here
return 0;
}