Memory Collector (не путать с GC)
От: Caracrist https://1pwd.org/
Дата: 04.07.10 08:37
Оценка: -1 :)
В с++ деструктор — это неотъемлемая часть функции. А вот освобождение памяти можно вынести за её пределы почти всегда. Идея очень проста в реализации, но не совсем понятно, как много она может дать. Вот по этому обращаюсь к уважаемой аудитории с просьбой поделиться соображениями, опытом и наработками.

Сама идея:
создать глобальный объект хранящий указатели (void*) на память требующую освобождения. По образу вот этого
Автор: remark
Дата: 10.03.10
, с учетом поправки
Автор: remark
Дата: 10.03.10
... назовём его LockFreeVPBuffer.
создать поток который по мере необходимости(или просто всегда, или по таймеру) освобождает всю память по указателям из LockFreeVPBuffer.
Заменить (overload) операторы new, new[], delete, delete[] на такие, чтоб при delete/delete[] указатель добавлялся в LockFreeVPBuffer (освобождать память на месте если буфер полон)
В new/new[] можно обработать неудачу при выделении. Не сразу бросать исключение, а сначала очистить содержимое LockFreeVPBuffer, и если повторно обламается то, тогда уже бросать.

Предположительные "за":
  • Основная логика будет выполнятся быстрее, и часть работы равномерно разойдётся по всему времени разряжая пиковые моменты.
  • Освобождение памяти будет идти последовательно и возможно меньше чередоваться с выделением, что замедлит фрагментацию памяти.

    Предположительные "против":
  • Оверхед на дополнительный поток, если активных потоков в программе мало, или процессоров(physical threads) мало, то это может стать довольно чувствительным.
  • Работы становится не меньше, весь тот же код всё равно выполнится, плюс добавится работа с LockFreeVPBuffer.

    П.С. Обратите внимание, что речь не идёт о том с каким аллокатором работает программа, системным или кастомным. (хотя это тоже может быть важно )
  • ~~~~~
    ~lol~~
    ~~~ Single Password Solution
    Re: Memory Collector (не путать с GC)
    От: s.ts  
    Дата: 05.07.10 01:10
    Оценка:
    Здравствуйте, Caracrist, Вы писали:

    C>В с++ деструктор — это неотъемлемая часть функции. А вот освобождение памяти можно вынести за её пределы почти всегда. Идея очень проста в реализации, но не совсем понятно, как много она может дать. Вот по этому обращаюсь к уважаемой аудитории с просьбой поделиться соображениями, опытом и наработками.


    C>Сама идея:

    C>создать глобальный объект хранящий указатели (void*) на память требующую освобождения. По образу вот этого
    Автор: remark
    Дата: 10.03.10
    , с учетом поправки
    Автор: remark
    Дата: 10.03.10
    ... назовём его LockFreeVPBuffer.

    C>создать поток который по мере необходимости(или просто всегда, или по таймеру) освобождает всю память по указателям из LockFreeVPBuffer.
    C>Заменить (overload) операторы new, new[], delete, delete[] на такие, чтоб при delete/delete[] указатель добавлялся в LockFreeVPBuffer (освобождать память на месте если буфер полон)
    C>В new/new[] можно обработать неудачу при выделении. Не сразу бросать исключение, а сначала очистить содержимое LockFreeVPBuffer, и если повторно обламается то, тогда уже бросать.

    C>Предположительные "за":

    C>
  • Основная логика будет выполнятся быстрее, и часть работы равномерно разойдётся по всему времени разряжая пиковые моменты.
    C>
  • Освобождение памяти будет идти последовательно и возможно меньше чередоваться с выделением, что замедлит фрагментацию памяти.

    C>Предположительные "против":

    C>
  • Оверхед на дополнительный поток, если активных потоков в программе мало, или процессоров(physical threads) мало, то это может стать довольно чувствительным.
    C>
  • Работы становится не меньше, весь тот же код всё равно выполнится, плюс добавится работа с LockFreeVPBuffer.

    C>П.С. Обратите внимание, что речь не идёт о том с каким аллокатором работает программа, системным или кастомным. (хотя это тоже может быть важно )


    А чем это отличается от GC ?
  • Re: Memory Collector (не путать с GC)
    От: TarasKo Голландия  
    Дата: 05.07.10 01:25
    Оценка:
    Не забудьте ещё что к каждому указателю надо прицепить счётчик ссылок иначе ваш мемориколлектор освободит память которая всё ещё используется. По сути вам всё равно нужны те же самые умные указатели. Только вместо того что бы освобождать память в деструкторе когда исчезает последняя ссылка, вы добавляете себе лишний геморой с дополнительным потоком, глобальным хранилищем указателей и т.п.
    Re[2]: Memory Collector (не путать с GC)
    От: s.ts  
    Дата: 05.07.10 01:39
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    TK>Не забудьте ещё что к каждому указателю надо прицепить счётчик ссылок иначе ваш мемориколлектор освободит память которая всё ещё используется. По сути вам всё равно нужны те же самые умные указатели. Только вместо того что бы освобождать память в деструкторе когда исчезает последняя ссылка, вы добавляете себе лишний геморой с дополнительным потоком, глобальным хранилищем указателей и т.п.

    Если сделать GC, то счетчиков не будет — тут есть выигрыш в использовании процессорного времени не-GC-потоками
    Но топикстартер сказал, что это не д.быть GC
    И мне пока не понятно что же он тогда имеет ввиду и что там будет лучше
    Re[2]: Memory Collector (не путать с GC)
    От: Caracrist https://1pwd.org/
    Дата: 05.07.10 03:33
    Оценка:
    Здравствуйте, s.ts, Вы писали:

    ST>А чем это отличается от GC ?


    Не ведёт подсчет ссылок, не вычисляет круговую зависимость, не выполняет код специализированный для объекто (деструкторы, или дипоузы)
    Этот модуль на порядок легче GC.
    ~~~~~
    ~lol~~
    ~~~ Single Password Solution
    Re[2]: Memory Collector (не путать с GC)
    От: Caracrist https://1pwd.org/
    Дата: 05.07.10 03:38
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    TK>Не забудьте ещё что к каждому указателю надо прицепить счётчик ссылок иначе ваш мемориколлектор освободит память которая всё ещё используется. По сути вам всё равно нужны те же самые умные указатели. Только вместо того что бы освобождать память в деструкторе когда исчезает последняя ссылка, вы добавляете себе лишний геморой с дополнительным потоком, глобальным хранилищем указателей и т.п.


    нет никакой связи между тем, как вы решаете когда освобождать память в своей программе и тем, что я предлогаю. MC подключается там где код выполняет delete, тоесть уже после подсчётов сылок или ещё какой методики.
    ~~~~~
    ~lol~~
    ~~~ Single Password Solution
    Re[2]: Memory Collector (не путать с GC)
    От: CreatorCray  
    Дата: 05.07.10 05:56
    Оценка:
    Здравствуйте, TarasKo, Вы писали:

    TK>Не забудьте ещё что к каждому указателю надо прицепить счётчик ссылок иначе ваш мемориколлектор освободит память которая всё ещё используется. По сути вам всё равно нужны те же самые умные указатели. Только вместо того что бы освобождать память в деструкторе когда исчезает последняя ссылка, вы добавляете себе лишний геморой с дополнительным потоком, глобальным хранилищем указателей и т.п.


    Не.
    Он предлагает сделать deferred HeapFree: в delete вызов HeapFree не происходит, а адрес памяти, которую нужно освободить передаётся в lock-free очередь, которую периодически выгребает специальный поток и для каждого элемента этой очереди зовёт HeapFree.
    Всё, чего он добьётся — потенциальное уменьшение затрат на вызов HeapFree (а в многопоточке огромную долю времени выполнения HeapFree занимает сидение в критической секции).
    Не думаю что овчинка стоит выделки, но в каком то наборе задач может и даст какой нить performance boost.
    По мне так проще сделать нормальный per-thread pool аллокатор. Это и аллокацию ускорит и деаллокацию.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Забанили по IP, значит пора закрыть эту страницу.
    Всем пока
    Re[3]: Memory Collector (не путать с GC)
    От: Pavel Dvorkin Россия  
    Дата: 05.07.10 06:23
    Оценка:
    Здравствуйте, CreatorCray, Вы писали:

    CC>Не.

    CC>Он предлагает сделать deferred HeapFree: в delete вызов HeapFree не происходит, а адрес памяти, которую нужно освободить передаётся в lock-free очередь, которую периодически выгребает специальный поток и для каждого элемента этой очереди зовёт HeapFree.
    CC>Всё, чего он добьётся — потенциальное уменьшение затрат на вызов HeapFree (а в многопоточке огромную долю времени выполнения HeapFree занимает сидение в критической секции).
    CC>Не думаю что овчинка стоит выделки, но в каком то наборе задач может и даст какой нить performance boost.
    CC>По мне так проще сделать нормальный per-thread pool аллокатор. Это и аллокацию ускорит и деаллокацию.

    Хм, а зачем его делать ? Никто же не запрещает иметь кучу для каждого потока. То есть, конечно, она не потоку принадлежит, но из других потоков к ней не обращаться. И кстати, при этом можно будет поставить HEAP_NO_SERIALIZE и убрать это сидение в критической секции

    Ниже выделено мной

    If HEAP_NO_SERIALIZE is not specified (the simple default), the heap serializes access within the calling process. Serialization ensures mutual exclusion when two or more threads attempt simultaneously to allocate or free blocks from the same heap. There is a small performance cost to serialization, but it must be used whenever multiple threads allocate and free memory from the same heap. Setting HEAP_NO_SERIALIZE eliminates mutual exclusion on the heap. Without serialization, two or more threads that use the same heap handle might attempt to allocate or free memory simultaneously, which may cause corruption in the heap. Therefore, HEAP_NO_SERIALIZE can safely be used only in the following situations:


    The process has only one thread.
    The process has multiple threads, but only one thread calls the heap functions for a specific heap.
    The process has multiple threads, and the application provides its own mechanism for mutual exclusion to a specific heap.
    With best regards
    Pavel Dvorkin
    Re[4]: Memory Collector (не путать с GC)
    От: CreatorCray  
    Дата: 05.07.10 07:13
    Оценка:
    Здравствуйте, Pavel Dvorkin, Вы писали:

    PD>Хм, а зачем его делать? Никто же не запрещает иметь кучу для каждого потока.

    Можно и так. Другой момент что со своим пулом можно:
    1) сделать ещё более быструю аллокацию/деаллокацию
    2) проще сделать обработку случая "выделяем в одном потоке, освобождаем в другом" и "насоздавали в этом потоке, отдали указатели в другой и завершили поток"
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Забанили по IP, значит пора закрыть эту страницу.
    Всем пока
    Re[5]: Memory Collector (не путать с GC)
    От: Caracrist https://1pwd.org/
    Дата: 05.07.10 07:25
    Оценка:
    Здравствуйте, CreatorCray, Вы писали:

    CC>Здравствуйте, Pavel Dvorkin, Вы писали:


    PD>>Хм, а зачем его делать? Никто же не запрещает иметь кучу для каждого потока.

    CC>Можно и так. Другой момент что со своим пулом можно:
    CC>1) сделать ещё более быструю аллокацию/деаллокацию
    CC>2) проще сделать обработку случая "выделяем в одном потоке, освобождаем в другом" и "насоздавали в этом потоке, отдали указатели в другой и завершили поток"

    Всё можно... Можно и свой GC построить. Вопрос цены/качества.
    То что я предложил, не универсальное и суперское решение . Это всего лишь элементарное в реализации (несколько часов и готово), очень простое улучшение, которое может улучшить (возможно существенно) работу многопоточного приложения.
    ~~~~~
    ~lol~~
    ~~~ Single Password Solution
    Re[5]: Memory Collector (не путать с GC)
    От: Pavel Dvorkin Россия  
    Дата: 05.07.10 08:04
    Оценка:
    Здравствуйте, CreatorCray, Вы писали:

    CC>2) проще сделать обработку случая "выделяем в одном потоке, освобождаем в другом" и "насоздавали в этом потоке, отдали указатели в другой и завершили поток"


    А это и в том варианте, что я предложил, никому делать не запрещено. Лишь бы два потока не пытались одновременно к куче обратиться.
    With best regards
    Pavel Dvorkin
    Re[6]: Memory Collector (не путать с GC)
    От: Pavel Dvorkin Россия  
    Дата: 05.07.10 08:09
    Оценка:
    Здравствуйте, Caracrist, Вы писали:

    C>Всё можно... Можно и свой GC построить.


    Нельзя. Если ты начнешь двигать объекты в памяти — как быть с сохраненными указателями ?

    C>То что я предложил, не универсальное и суперское решение . Это всего лишь элементарное в реализации (несколько часов и готово), очень простое улучшение, которое может улучшить (возможно существенно) работу многопоточного приложения.


    Насчет существенно- сомневаюсь, но согласен с CreatorCray — иногда может быть полезным. В общем, идея имеет право на существование, но вряд ли особо ценна.
    With best regards
    Pavel Dvorkin
    Re[5]: Memory Collector (не путать с GC)
    От: CreatorCray  
    Дата: 05.07.10 08:45
    Оценка:
    Здравствуйте, CreatorCray, Вы писали:

    CC>2) проще сделать обработку случая "выделяем в одном потоке, освобождаем в другом" и "насоздавали в этом потоке, отдали указатели в другой и завершили поток"

    Впрочем да, это и с Heap просто делается.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Забанили по IP, значит пора закрыть эту страницу.
    Всем пока
    Re[6]: Memory Collector (не путать с GC)
    От: CreatorCray  
    Дата: 05.07.10 08:59
    Оценка: 5 (2) +1
    Здравствуйте, Pavel Dvorkin, Вы писали:

    CC>>2) проще сделать обработку случая "выделяем в одном потоке, освобождаем в другом" и "насоздавали в этом потоке, отдали указатели в другой и завершили поток"

    PD>А это и в том варианте, что я предложил, никому делать не запрещено. Лишь бы два потока не пытались одновременно к куче обратиться.

    Угу, я уже прикинул:
    namespace ThreadPoolAlloc
    {
        void* Alloc    (DWORD size);
        void Free    (const void* ptr);
    }

    #include "Interlocked.h"
    
    class Context;
    
    //////////////////////////////////////////////////////////////////////////
    // Thread context
    
    static __declspec (thread) Context* m_context;
    
    //////////////////////////////////////////////////////////////////////////
    // Memory block header
    
    struct BlockHeader
    {
        union
        {
            Context* m_context;
            BlockHeader* m_next;
        };
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Thread pool context
    
    class Context
    {
    public:
        Context ()
        {
            m_heap = HeapCreate(HEAP_NO_SERIALIZE, 0, 0);
            m_externFreedBlocks = NULL;
            m_isAbandoned = m_numBlocksAlloced = 0;
        }
    
        ~Context ()
        {
            HeapDestroy(m_heap);
        }
    
        //////////////////////////////////////////////////////////////////////////
        // Allocate
    
        void* Alloc (DWORD size)
        {
            size += sizeof (BlockHeader);
    
            BlockHeader* hdr = (BlockHeader*)HeapAlloc(m_heap, HEAP_NO_SERIALIZE, size);
            hdr->m_context = this;
            m_numBlocksAlloced++;
    
            return (void*)(hdr+1);
        }
    
        //////////////////////////////////////////////////////////////////////////
        // Free
    
        void Free (void *ptr)
        {
            m_numBlocksAlloced--;
            HeapFree(m_heap, HEAP_NO_SERIALIZE, ptr);
            FreeDeferredBlocks();
        }
    
        //////////////////////////////////////////////////////////////////////////
        // Shutdown context
    
        void Shutdown ()
        {
            // Cleanup all deferred
            if (!FreeDeferredBlocks())
            {
                // Mark as abandoned
                Interlocked::Exchange (m_isAbandoned, true);
    
                // Try to cleanup again
                if (!FreeDeferredBlocks())
                    return;    // Still has allocated blocks - bail
            }
    
            // Kill self
            delete this;
        }
    
        void DeferredFree (BlockHeader* hdr)
        {
            if (m_isAbandoned)
            {
                // Just decrement allocated blocks counter and delete context when empty
                if (!Interlocked::Decrement (m_numBlocksAlloced))
                    delete this;
            }
            else
            {
                hdr->m_next = m_externFreedBlocks;
                while (Interlocked::CompareExchangePtr (m_externFreedBlocks, hdr->m_next, hdr) == hdr)
                    hdr->m_next = m_externFreedBlocks;
            }
        }
    
    private:
    
        bool FreeDeferredBlocks ()
        {
            BlockHeader* hdr = m_externFreedBlocks;
            if (hdr)
            {
                while (!Interlocked::CompareExchangePtr (m_externFreedBlocks, hdr, NULL))
                    hdr = m_externFreedBlocks;
    
                int numDeferredBlocks = 0;
                while (hdr)
                {
                    BlockHeader* next = hdr->m_next;
                    numDeferredBlocks++;
                    HeapFree (m_heap, HEAP_NO_SERIALIZE, hdr);
                    hdr = next;
                }
    
                Interlocked::Add (m_numBlocksAlloced, -numDeferredBlocks);
            }
    
            return (m_numBlocksAlloced == 0);
        }
    
        BlockHeader*    m_externFreedBlocks;
        HANDLE            m_heap;
        volatile DWORD    m_numBlocksAlloced, m_isAbandoned;
    };
    
    //////////////////////////////////////////////////////////////////////////
    // Alloc/free
    
    void* ThreadPoolAlloc::Alloc (DWORD size)
    {
        // Get thread context or create new
        Context* context = m_context;
        if (!context)
            m_context = context = new Context ();
    
        return context->Alloc(size);
    }
    
    void ThreadPoolAlloc::Free (const void* ptr)
    {
        if (!ptr)
            return;
    
        BlockHeader* hdr = (BlockHeader*)ptr - 1;
        Context *context = m_context;
    
        if ((context) && (hdr->m_context == context))
            context->Free(hdr);
        else hdr->m_context->DeferredFree (hdr);
    }
    
    //////////////////////////////////////////////////////////////////////////
    // Thread detach callback
    
    static void WINAPI __ThreadDetach (HANDLE hDllHandle, DWORD dwReason, LPVOID lpreserved)
    {
        if ((dwReason != DLL_THREAD_DETACH) && (dwReason != DLL_PROCESS_DETACH)) 
            return;
    
        Context* context = m_context;
        if (context)
        {
            context->Shutdown ();
            m_context = NULL;
        }
    }
    
    //typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason, PVOID Reserved);
    
    #pragma data_seg (".CRT$XLD")
        __declspec (allocate (".CRT$XLD")) static PIMAGE_TLS_CALLBACK __g_ThreadPoolAllocDetach = __ThreadDetach;
    #pragma data_seg ()
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Забанили по IP, значит пора закрыть эту страницу.
    Всем пока
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.