Как я могу освободить память, используемую Parallel.Task?

У меня есть программа, которая выполняет интенсивную симуляцию памяти. Ниже я написал небольшое консольное приложение, которое воспроизводит мою проблему.

class Program {
    static void Main(string[] args) {
        var t = new Task(() => DoMemoryHog(20000000));
        t.Start();
        t.Wait();
        t.Dispose();
        t = null;
        GC.Collect();
        Console.WriteLine("Done");
        Console.ReadLine();
    }

    static void DoMemoryHog(int n) {
        ConcurrentBag<double> results = new ConcurrentBag<double>();

        Parallel.For(0, n, (i) => {
            results.Add(Math.Sqrt(i.GetHashCode()));
        });
    }
}

Когда я запускаю программу, я вижу увеличение объема используемой памяти в диспетчере задач Windows, но когда задача завершена (и отображается «Готово»), память не возвращается к исходному уровню, это происходит только когда я закрываю приложение.

Кто-нибудь знает, как освободить память, используемую параллельной задачей, в то время как основное приложение продолжает работать? Как вы можете видеть, я уже пытался избавиться от него, устанавливая его ссылку на null и запуская сборщик мусора вручную (что вам не следует делать, я знаю).


person jkokorian    schedule 28.11.2011    source источник
comment
Пожалуйста, пометьте ответ как решенный, если у вас больше нет проблем. Спасибо!   -  person oberfreak    schedule 30.11.2011


Ответы (2)


Я уверен, что у вас нет проблем с вашей памятью, .NET просто не сокращает используемую память, чтобы ее можно было назначить в будущем. Это экономит время для будущего распределения памяти. Попробуйте перезапустить цикл после его завершения, я уверен, что память не увеличится.

Так что, пожалуйста, просто попробуйте это, я был бы заинтересован в результате!

class Program {
    static void Main(string[] args) {
        Process currentProcess = Process.GetCurrentProcess();
        for (int i = 0; i < 10; i++) {
            var t = new Task(() => DoMemoryHog(20000000));
            t.Start();
            t.Wait();
            t.Dispose();
            t = null;
            GC.Collect();
            Console.WriteLine("Done" +i);
            Console.WriteLine("Memory: " + GC.GetTotalMemory(false));
            Console.WriteLine("Paged: " + currentProcess.PagedMemorySize64);
            Console.WriteLine("-------------------------");
        }
        Console.ReadLine();
    }

    static void DoMemoryHog(int n) {
        ConcurrentBag<double> results = new ConcurrentBag<double>();

        Parallel.For(0, n, (i) =>
        {
            results.Add(Math.Sqrt(i.GetHashCode()));
        });
    }
}

Эти два метода используются для распечатки использования памяти:

GC.GetTotalMemory();
currentProcess.PagedMemorySize64;

Мой вывод:

Done0
Memory: 480080992
Paged: 520753152
-------------------------
Done1
Memory: 480081512
Paged: 520753152
-------------------------
Done2
Memory: 480082160
Paged: 520753152
-------------------------
Done3
Memory: 480083476
Paged: 520753152
-------------------------
Done4
Memory: 480083496
Paged: 520753152
-------------------------
Done5
Memory: 480083516
Paged: 520753152
-------------------------
Done6
Memory: 480083536
Paged: 520753152
-------------------------
Done7
Memory: 480084204
Paged: 520753152
-------------------------
Done8
Memory: 480084204
Paged: 520753152
-------------------------
Done9
Memory: 480085500
Paged: 520753152
-------------------------

Насколько я вижу, здесь нет проблем с памятью. Объекты очищаются, а память используется повторно. Проблема решена или?

Обновлять

Поведение частных байтов:

PrivateBytes приложения

Как видите, сборщик мусора собирает объекты и освобождает память, но в настоящее время не освобождает ее, поэтому можно выделить новые объекты.

person oberfreak    schedule 28.11.2011
comment
Как поживают приватные байты? - person oberfreak; 29.11.2011
comment
ВЫ должны посмотреть на это. Не я. - person leppie; 29.11.2011
comment
Okidoki, добавлена ​​трассировка приватных байтов консольного приложения - person oberfreak; 29.11.2011
comment
То же самое происходит и со мной, так что это кажется правильным. Я посмотрю на свой исходный код, так как проблема не в concurrentbag, а в чем-то другом. - person jkokorian; 06.12.2011
comment
Кстати: что это за хороший инструмент для повышения производительности, скриншот которого вы разместили? Выглядит очень полезно! - person jkokorian; 06.12.2011
comment
Я использую technet.microsoft.com/en-us/sysinternals/bb896653 Microsoft Обозреватель процессов. Вы можете проанализировать каждый процесс до глубины души, если хотите :-) (я также заменяю им TaskManager) - person oberfreak; 07.12.2011

Это связано с природой concurrentBag (см. мой предыдущий вопрос о ConcurrentBag ( Возможная утечка памяти в ConcurrentBag?)).

По сути, параллельная сумка хранит элементы в локальном потоке. Если вы не потребляете предметы, они останутся в локальном потоке. Пожалуйста, проверьте следующий пример:

    class TrackedItem
    {
        public TrackedItem()
        {
            Console.WriteLine("Constructor!");
        }
        ~TrackedItem()
        {
            Console.WriteLine("Destructor!");
        }
    }

    static void Main(string[] args)
    {
        Action allCollect = () =>
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            };

        // create a single item and loose it directly thereafter
        TrackedItem item = new TrackedItem();
        item = null;
        allCollect();
        // Constructor!, Destructor!

        ConcurrentBag<TrackedItem> bag = new ConcurrentBag<TrackedItem>();
        bag.Add(new TrackedItem());
        bag = null;
        allCollect();
        // Constructor!
        // Note that the destructor was not called since there is still a collection on the local thread

        Console.ReadLine();
    }

ConcurrentBag использует класс ThreadLocal, который позволяет удобно хранить 1 экземпляр на поток. Предполагаемый способ удаления данных в ThreadLocal — вызов метода Dispose в ThreadLocal (ThreadLocal реализует IDisposable). Это удаляет только данные для текущего потока. Однако concurrentBag не использует ThreadLocals. Вместо этого он полагается на все потребляемые элементы или на поток, в котором размещается ThreadLocal. однако это может быть очень неприятно, когда вы делитесь потоками, например, в ThreadPool.

person Polity    schedule 28.11.2011
comment
Как оказалось, никакой утечки памяти не было. График частных байтов показывает, что первые несколько раз общая память, зарезервированная для приложения, продолжает увеличиваться. Он стабилизируется примерно после 5 итераций. Прежде чем он смог достичь «5 раз», возникло исключение нехватки памяти, потому что приложение WCF, в котором выполнялся проблемный процесс, было размещено на сервере разработки ISS (который является 32-разрядным) и одновременно отлаживалось (что по-видимому, ограничивает использование памяти процессом). Я установил все на 64 бит и запустил службу WCF в IIS: проблема решена. - person jkokorian; 21.01.2012