Оптимизация цикла в 10 000 000 повторов

68
18 января 2022, 16:30

Мой код

    public static class Canal
    {
        public static List<byte> A = new List<byte>();
        public static List<byte> R = new List<byte>();
        public static List<byte> G = new List<byte>();
        public static List<byte> B = new List<byte>();
    }
    public static void Load()
    {
        using (var fs = new FileStream("Canal_A.txt", FileMode.Open))
        {
            var array = new byte[(int)fs.Length];
            fs.Read(array, 0, (int)fs.Length);
            Canal.A.AddRange(array);
        }
        using (var fs = new FileStream("Canal_R.txt", FileMode.Open))
        {
            var array = new byte[(int)fs.Length];
            fs.Read(array, 0, (int)fs.Length);
            Canal.R.AddRange(array);
        }
        using (var fs = new FileStream("Canal_G.txt", FileMode.Open))
        {
            var array = new byte[(int)fs.Length];
            fs.Read(array, 0, (int)fs.Length);
            Canal.G.AddRange(array);
        }
        using (var fs = new FileStream("Canal_B.txt", FileMode.Open))
        {
            var array = new byte[(int)fs.Length];
            fs.Read(array, 0, (int)fs.Length);
            Canal.B.AddRange(array);
        }
    }
    public void F()
    {
        var color = new List<Color>();
        for (int i = 0; i <Canal.A.Count; i++)
        {
            color.Add(Color.FromArgb(Canal.A[i], Color.FromArgb(Canal.R[i], Canal.G[i], Canal.B[i])));
        }
    }

У меня в файлах раздельно хранится ARGB каналы картинки. Я считываю все каналы, потом создаю из них цвета, затем из цветов делаю картинку. Все работает. Но когда я вызываю F() то цикл отжерает около 1 Гб оперативки, в зависимости от размера картинки, и при этом память не освобождается после отработки функции. Как результат каждый вызов функции + 1 Гб занятой оперативы(( Как это можно оптимизировать? Я так понял что память отжерает из за того что цикл больше 10 мил раз отрабатует, но почему тогда после отработки память остается забитой?

Вот полная функция, больше программа не делает ничего.

    public static void Decoder(string fileName)
    {
        var color = new List<Color>();
        Load();
        for (int i = 0; i < Canal.A.Count; i++)
        {
            color.Add(Color.FromArgb(Canal.A[i], Color.FromArgb(Canal.R[i], Canal.G[i], Canal.B[i])));
        }
        Console.WriteLine(".....");
        using (var bmp = new Bitmap(fileName))
        {
            int counter = 0;
            for (int i = 0; i < bmp.Height; i++)
            {
                for (int j = 0; j < bmp.Width; j++)
                {
                    bmp.SetPixel(j, i, color[counter]);
                    counter++;
                }
            }
            bmp.Save($"_{fileName}");
        }
        color.Clear();
        Console.WriteLine("FINISH");
    }
Answer 1

На сколько я знаю, статические классы сборщик мусора не забирает, т.к. фактически они не являются объектами и не потребляют память. НО статические поля статического класса - уже являются объектами и хранятся в памяти и не будут собраны GC, потому что будут доступны в течение всего жизненного цикла приложения. В вашем случае, можно попытаться принудительно присвоить значение null полям класса Canal: (A,R,G,B), чтобы они стали доступны для GC.

Answer 2

У вас в Decoder вызывается функция Load, заполняющая листы каналов, но при этом ни где не происходит очистка каналов => память закономерно будет утекать, т.к. листы каналов будут постоянно увеличиваться.

Добавьте предварительную очистку каналов, например так:

...
using (var fs = new FileStream("Canal_A.txt", FileMode.Open))
{
    Canal.A.Clear();
    var array = new byte[fs.Length];
    fs.Read(array, 0, (int)fs.Length);
    Canal.A.AddRange(array);
}
...

для каждого соответственно. При каждой следующей загрузке данных, предыдущие будут гарантировано выгружены и, позже, съедены GC. Если скорость реакции GC будет маловата, а данных очень много, то можно его принудительного пнуть с помощью GC.Collect(). Только не нужно трогать GC при малом объеме данных, это ни чего не даст кроме ухудшения производительности.

Еще можно модифицировать ваш класс с каналами

public static class Canal
{
    public static byte[] A;
    public static byte[] R;
    public static byte[] G;
    public static byte[] B;
}

Тогда заполнение делать так:

public static void Load()
{
    Canal.A = File.ReadAllBytes("Canal_A.txt");
    Canal.R = File.ReadAllBytes("Canal_R.txt");
    Canal.G = File.ReadAllBytes("Canal_G.txt");
    Canal.B = File.ReadAllBytes("Canal_B.txt");
}

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

READ ALSO
C# WPF Как в List View заменить ListViewItem на класс-наследник?

C# WPF Как в List View заменить ListViewItem на класс-наследник?

У меня есть например класс-наследник от ListViewItem - AdvancedListViewItemВ XAML я могу задать

80
Атомарный метод Interlocked.Add

Атомарный метод Interlocked.Add

По какой причине метод InterlockedAdd принимает только int? Чем double мешает при атомарности?

94
WinForms Telerik RadGridView, как добавлять данные с помощью GridViewDataRowInfo

WinForms Telerik RadGridView, как добавлять данные с помощью GridViewDataRowInfo

При добавлении данных с помощью GridViewDataRowInfo в цикле, добавляется только последняя запись в чём может быть проблема?

164
Как работает пул потоков?

Как работает пул потоков?

ВNET есть пул потоков, - это заготовленные потоки, готовые к выполнению какой-то задачи

72