Что здесь происходит? (.Net) GC.CollectionCount (0) продолжает расти - PullRequest
5 голосов
/ 27 октября 2010

При тестировании производительности приложений я столкнулся с довольно странным поведением GC.Короче говоря, GC запускается даже на пустой программе без выделения времени выполнения!

Следующее приложение демонстрирует проблему:

using System;
using System.Collections.Generic;

public class Program
{
    // Preallocate strings to avoid runtime allocations.
    static readonly List<string> Integers = new List<string>();
    static int StartingCollections0, StartingCollections1, StartingCollections2;

    static Program()
    {
        for (int i = 0; i < 1000000; i++)
            Integers.Add(i.ToString());
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }

    static void Main(string[] args)
    {
        DateTime start = DateTime.Now;
        int i = 0;
        Console.WriteLine("Test 1");
        StartingCollections0 = GC.CollectionCount(0);
        StartingCollections1 = GC.CollectionCount(1);
        StartingCollections2 = GC.CollectionCount(2);
        while (true)
        {
            if (++i >= Integers.Count)
            {
                Console.WriteLine();
                break;
            }

            // 1st test - no collections!
            {
                if (i % 50000 == 0)
                {
                    PrintCollections();
                    Console.Write(" - ");
                    Console.WriteLine(Integers[i]);
                    //System.Threading.Thread.Sleep(100);
                    // or a busy wait (run in debug mode)
                    for (int j = 0; j < 50000000; j++)
                    { }
                }
            }
        }

        i = 0;
        Console.WriteLine("Test 2");
        StartingCollections0 = GC.CollectionCount(0);
        StartingCollections1 = GC.CollectionCount(1);
        StartingCollections2 = GC.CollectionCount(2);
        while (true)
        {
            if (++i >= Integers.Count)
            {
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey(true);
                return;
            }

            DateTime now = DateTime.Now;
            TimeSpan span = now.Subtract(start);
            double seconds = span.TotalSeconds;

            // 2nd test - several collections
            if (seconds >= 0.1)
            {
                PrintCollections();
                Console.Write(" - ");
                Console.WriteLine(Integers[i]);
                start = now;
            }
        }
    }

    static void PrintCollections()
    {
        Console.Write(Integers[GC.CollectionCount(0) - StartingCollections0]);
        Console.Write("|");
        Console.Write(Integers[GC.CollectionCount(1) - StartingCollections1]);
        Console.Write("|");
        Console.Write(Integers[GC.CollectionCount(2) - StartingCollections2]);
    }
}

Может кто-нибудь объяснить, что здесь происходит?У меня сложилось впечатление, что ГХ не запустится, если давление памяти не достигнет определенных пределов.Тем не менее, кажется, что он запускается (и собирает) все время - это нормально?

Редактировать: Я изменил программу, чтобы избежать всех распределений времени выполнения.

Редактировать 2: Хорошо, новая итерацияи кажется, что DateTime является виновником.Один из методов DateTime выделяет память (возможно, Subtract), что приводит к запуску GC.Первый тест теперь вызывает абсолютно нет коллекций - как и ожидалось - в то время как второй вызывает несколько.

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

Ответы [ 4 ]

5 голосов
/ 27 октября 2010

GC.CollectionCount(0) возвращает следующее:

Количество раз, когда сборка мусора происходила для указанного поколения с момента запуска процесса.

Следовательно, вы должны увидеть увеличение чисел, и это увеличение не означает, что память просачивается, а что ГХ работает.

Также в первом случае вы можете увидеть это увеличение. Это просто произойдет гораздо медленнее, потому что очень медленный Console.WriteLine метод вызывается гораздо чаще, что сильно замедляет процесс.

3 голосов
/ 27 октября 2010

Следует также отметить, что GC.Collect() является , а не синхронным вызовом функции.Он запускает сборку мусора, но эта сборка мусора происходит в фоновом потоке и теоретически может не завершиться к тому времени, когда вы приступите к проверке вашей GC статистики.

Есть вызов GC.WaitForPendingFinalizersкоторый вы можете сделать после GC.Collect, чтобы заблокировать, пока не произойдет сборка мусора.

Если вы действительно хотите попытаться точно отслеживать статистику GC в различных ситуациях, я бы вместо этого использовал Windows Performance Monitor в вашем процессе, гдеВы можете создавать мониторы для всех видов вещей, включая статистику .NET Heap.

1 голос
/ 27 октября 2010

Если вы подождете несколько секунд, вы увидите, что в первом тесте число сборов также увеличивается, но не так быстро.

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

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

Я посчитал итерации и распечатал количество итераций на сборку мусора.На моем компьютере первый тест стабилизирует около 45000 итераций на GC, в то время как второй тест стабилизирует около 130000 итераций на GC.

Итак, первый тест фактически на больше сборок мусора, чем второй тестпримерно в три раза больше.

0 голосов
/ 29 октября 2010

Спасибо всем! Ваши предложения помогли выявить виновника: DateTime выделяет кучу памяти.

ГХ работает не все время, а только при выделении памяти. Если объем используемой памяти невелик, GC никогда не будет работать, а GC.CollectionCount(0) всегда будет возвращать 0, как и ожидалось.

Последняя итерация теста демонстрирует это поведение. Первый тестовый прогон не выделяет кучи памяти (GC.CollectionCount(0) остается 0), а второй выделяет память неочевидным образом: через DateTime.Subtract() -> Timespan.

Теперь и DateTime, и Timespan являются типами значений, поэтому я считаю это поведение удивительным. Тем не менее, у вас это есть: в конце концов, произошла утечка памяти.

...