Использование памяти C # для создания объектов в цикле for - PullRequest
0 голосов
/ 22 июля 2011

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

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

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

for (int i = 0; i < 100000; i++)
{
  TestObj testc = new TestObj
    {
      myTest = "testing asdf"
    };
}
public class TestObj
{
    public string myTest;
}

Я думал, что каждый testc, созданный в цикле, не доживет до конца итерации, но при отслеживании памяти кажется, что приложение удерживает каждый экземпляр testc .

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

Ответы [ 10 ]

4 голосов
/ 22 июля 2011

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

3 голосов
/ 22 июля 2011

Жизнь будет немного проще, если вы вместо этого будете использовать структуру ...

for (int i = 0; i < 100000; i++)
{
    TestObj testc = new TestObj
    {
        myTest = "testing asdf"
    };
}

public struct TestObj
{
    public string myTest;
}

Это все еще требует выделения строки, но структура не выживет.Это зависит от того, как на самом деле выглядит ваш класс, если у вас много типов значений, это очень поможет.Если у вас есть куча строковых / ссылочных значений, у вас все еще проблемы.

В противном случае вы можете сделать что-то вроде следующего:

for (int i = 0; i < 100000; i++)
{
    // do your work...

    // then every 1k cycles, see if we have > 100mb allocated
    // and force the GC to free the memory
    if(i % 1000 == 0 && GC.GetTotalMemory(false) > 100000000)
        GC.Collect();
}

Примечание: Этоуродливые «хакерские» вещи;однако, иногда это самое быстрое решение проблемы.

Обновление

Дополнительно вам нужно убедиться, что вы не нажимаете на LOH ( Куча больших объектов ), так как это может стать источником конфликта памяти.Как правило, храните строки, byte [], ect, под 85kb.Это означает, что длина строк не должна превышать 42 000 символов.

2 голосов
/ 22 июля 2011

Вам, вероятно, нужно понимать, что C # использует "сборку мусора" для управления временем жизни объектов.

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

ОБНОВЛЕНИЕ
Я согласен с Санни, что сбор мусора, скорее всего, НЕ ваша проблема.Единственный способ, которым увеличенное использование памяти может повлиять на скорость выполнения вашей программы, - это если вы достигли точки, когда машина переключает много оперативной памяти на диск.

Я бы посоветовал вам вместо этого профилировать взаимодействие с базой данных.В какой момент программа начинает замедляться?Увеличивается ли длина очереди диска на сервере базы данных?Сколько запросов он пытается выполнить одновременно?

Здесь есть две возможности.Во-первых, это замедление , тянущее большие объемы данных по проводам.Во-вторых, это замедление , проталкивающее большие объемы данных по проводам.

Что бы это ни было, посмотрите на конкретный задействованный сервер.Возможно, он не сможет поддерживать время ответа, которое вы запрашиваете.

1 голос
/ 22 июля 2011

Вы можете использовать , используя везде, где это возможно, (для любого типа, реализующего IDisposable интерфейс)

using(var conn = new SqlConnection ())
{
}

Не оставляйте SqlConnection открытым дольше, чем ему действительно нужно, используйте класс WeakReference для представления внутренней памяти вместе с временным хранилищем сериализации / десереализации. Использовать кеширование данных, шаблон посредника, шаблон наблюдателя

1 голос
/ 22 июля 2011

Ваши тестовые объекты будут очищены, когда поколение 1 живых объектов будет заполнено.Или когда кто-то звонит GC.Collect().Попробуйте позвонить, и вы увидите, что ничего не вырастет.

GC не очищает память сразу после того, как на нее никто не ссылается в .NET.И ваш TestObject очень-очень стеснительный в использовании памяти, так как в куче есть только один совместно используемый экземпляр вашей строки.Таким образом, вы могли бы создать многие из них, и GC не вмешивается.Они слишком малы, чтобы что-то изменить.

0 голосов
/ 28 июля 2011

Я предполагаю, что память, которую вы съели, не является управляемой памятью.Вот ключевая проблема: GC знает только об управляемой памяти.Если вы имеете дело с другими объектами, которые имеют «след» за пределами управляемого мира - например,файлы, COM-объекты, соединения с базой данных, окна и т. д. - они будут занимать память в этом процессе, но поскольку GC знает только об управляемой части своего пространства, неуправляемая часть может расти и расти без GC.понимая, что нужна коллекция.

Или, другими словами, GC отлично подходит для управления чистой памятью , но паршиво управляет ресурсами (файлами, объектами COM,РУЧКИ, окна и т. Д.) - если вы интенсивно их используете, вам может потребоваться активно закрывать / утилизировать или иным образом очищать их, как только вы покончили с ними, и не полагаться на GC.

пример, который вы приводите с объектом + string, является чисто управляемым, здесь нет ресурсов, поэтому, скорее всего, он достигнет некоторого верхнего предела, начнется сбор, и он выровняется, но не замедлит работу системы (по крайней мере, не слишком сильно)!).

Какие объекты вы фактически создаете / используете в цикле?Если они являются оболочками для внешних ресурсов, таких как соединения с базой данных или аналогичные, проверьте, реализуют ли они IDispose, а затем используйте .Dispose или шаблон using (), или посмотрите, есть ли Close / Disconnect или другой метод для освобождения ресурса.

0 голосов
/ 22 июля 2011

Я бы просто попробовал это:

TestObj testc;
for (int i = 0; i < 100000; i++)
{
  testc = new TestObj
    {
      myTest = "testing asdf"
    };
}

Или, как сказал Артур, оператор использования, если ваш класс IDisposable.

0 голосов
/ 22 июля 2011

Первое, что я бы попробовал, это отделить код, который идет внутри цикла, от кода цикла, добавив метод.

public void DoProcessing()
{
    for (int i = 0; i < 100000; i++)
    {
        ProcessItem();
    }
}

private void ProcessItem()
{
    TestObj testc = new TestObj
    {
        myTest = "testing asdf"
    };
}

public class TestObj
{
    public string myTest;
}
0 голосов
/ 22 июля 2011

Сборка мусора не является детерминированной, поэтому вы не можете ожидать немедленного просмотра результатов, если только вы явно не вызовете GC.Collect().Далее, если вы работаете с ADO.NET, вам нужно убедиться, что вы утилизируете каждый объект, который реализует IDisposable.Отказ от этого приведет к ненужной задержке сбора.

Кроме того, убедитесь, что ваш упрощенный пример действительно имитирует то, что происходит в вашем реальном коде.Например, если ваши объекты прикрепляют свои методы в качестве обработчиков событий для какого-либо другого долгоживущего объекта, они будут поддерживаться этой ссылкой до тех пор, пока этот единственный объект живет.

Чтобы проверить фактическое состояние вашей памяти,список всех живых объектов в определенный момент и фактические ссылки, которые поддерживают их, незаменимым инструментом является WinDbg с расширениями SoS .

В вашем примереКстати, невозможно, чтобы сборщик мусора не очищал , в конечном итоге .

  1. Вы не создаете новые String экземпляры или что-либо, кромеваши тестовые объекты.
    Из-за интернирования строк в .NET в вашем приложении существует только одна строковая константа, и ваш тестовый объект получает только свою ссылку (не копию).

  2. Ваш объект занимает всего 16 байтов в куче (в 32-битной среде)
    Минимум 12 байтов плюс ссылка на одну строку составляет 16байт большой o▪ Таблица.Это довольно мало, даже если их миллион.

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

0 голосов
/ 22 июля 2011

Каждая итерация цикла создаст новый объект в памяти размером с (TestObj). Конечный результат заключается в том, что при выходе из цикла for использование памяти будет sizeof (TestObj) * 100000; Поскольку ваши объекты никогда не выходят из области видимости (согласно вашему коду), они никогда не будут GCed.

Если вас беспокоит память, вы можете поместить TestObj в оператор using (используя (obj = new TestObj ()), если ваш тестовый объект реализует IDisposible. Вы также можете попробовать многопоточный подход, если ваша обработка будет работать в многопоточным способом и порождает рабочий поток на каждой итерации. TestObj, вероятно, будет собираться примерно в то же время, что и поток, он также может ускорить общее время, необходимое приложению для выполнения всей работы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...