C # System.Buffer.BlockCopy Проблема с памятью? - PullRequest
6 голосов
/ 03 января 2012

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

Похоже, что C # метод System.Buffer.BlockCopy оставляет вас с какими-то призраками памяти. У меня есть например этот метод:

private float[,] readFloatArray2 (byte[] b) {
   int floatSize = sizeof(float);
   float[,] v = new float[2, (b.Length / 2) / floatSize];
   System.Buffer.BlockCopy(b, 0, v, 0, b.Length);

   return v;
}

для преобразования байтового массива в двумерный массив с плавающей точкой. Данные предварительно считываются из потока. Я обнаружил, что проблема в методе System.Buffer.BlockCopy.

Если я удалю команду BlockCopy, память, используемая приложением, будет вдвое меньше. это означает, что я не виноват, что байтовый массив все еще жив. потому что без команды BlockCopy байтовый массив умирает правильно. массив float все равно создается (с копируемой информацией или без нее).

Я не совсем уверен, является ли это проблемой команды BlockCopy или GC, потому что я также пытался вызвать System.GC.Collect (); после BlockCopy, а затем он также работает отлично (я знаю, что вы не должны делать это ... вот почему я прошу совета здесь).

Я бы тоже не стал спрашивать, но проблема связана с несколькими сотнями мег.

Помимо проблем с памятью, метод работает отлично. Кто-нибудь знает, что вызывает проблемы с памятью?

привет и заранее спасибо оли

ps: я использую .NET4.0 с Visual Studio 2010 PRO и WIN7 ... не знаю, уместно ли это.

Ответы [ 4 ]

2 голосов
/ 03 января 2012

Я обнаружил, что проблема в методе System.Buffer.BlockCopy.Если я удалю команду BlockCopy, память, используемая приложением, будет вдвое меньше.это означает, что я не виноват, что байтовый массив все еще жив.Потому что без команды BlockCopy байтовый массив умирает правильно.

Я не согласен с этим выводом.Я вижу несколько этапов:

  1. Массив байтов существует и заполнен данными
  2. Вы выделяете массив с плавающей точкой, но он еще не заполнен данными.
  3. Вы заполняете массив float данными
  4. На байтовый массив больше нет ссылок, но он еще не собран
  5. Байтный массив был собран.

На работу байтового массива не влияет BlockCopy.

Шаг 2 резервирует и фиксирует виртуальную память.Таким образом, размер коммита увеличивается на этом шаге.Но поскольку содержимое массива никогда не записывалось и состоит полностью из 00 байтов, диспетчер памяти Windows не выделяет для него физической памяти.Он просто отмечает, что эти страницы полностью состоят из 00 с.

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


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

  1. Повторное использование буферов.GC хорош для небольших недолговечных объектов, но очень плохо для больших недолговечных объектов.Это означает, что вы не должны использовать функции, которые выделяют и возвращают большие массивы.Вместо этого возьмите их в качестве параметра, чтобы существующий массив можно было повторно использовать.
  2. Если вы используете работу с аудиоданными (кажется вероятным), я бы не использовал сплошной 2D-массив.Я бы вместо этого использовал массив массивов.Где внутренний массив представляет выборки в одном буфере, а внешний массив представляет буферы.Это имеет два преимущества:

    • Вы можете легко написать код, который работает только на одном канале.
    • Сплошные двумерные массивы медленно индексируются, поэтому зачастую они также быстрее.
  3. Вы действительно хотите прочитать все данные сразу?Я читал кусками по несколько килобайт.
1 голос
/ 03 января 2012

Я не совсем уверен, является ли это проблемой команды BlockCopy или GC, потому что я также пытался вызвать System.GC.Collect ();после BlockCopy, а затем он также работает отлично (я знаю, что вы не должны делать это ... вот почему я прошу совета здесь).Я также не стал бы задавать вопросы, если бы речь шла не о нескольких хандретных МБ.

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

Бывают случаи, когда вызов GC.Collect() имеет смысл в реальной программе, и это может быть одним из них, так что если это так "работает отлично ", тогда я бы не стал слишком беспокоиться о том, что это противоречит передовому опыту для 99,9% кода.Причина в том, что это «лучшая практика», а не жесткое правило, состоит в том, что иногда мы находимся в случае 0,1%, а то, что обычно является лучшей практикой, больше не является лучшей практикой вообще.

Кроме того, еслиВы можете заранее предсказать максимальный размер массивов (или, если это невозможно, только из байтовых массивов источника), тогда первый подход CodeInChaos может сработать.На самом деле не больно использовать 10 000 000 байтов для обработки 32, если в какой-то момент вы действительно будете использовать эти 10 000 000.Повторное использование этих 10 000 000 обеспечивает реальную экономию в течение всего жизненного цикла процесса.

1 голос
/ 03 января 2012

Buffer.BlockCopy основано на байтах, а не на индексах.Я бы предложил вам использовать Array.Copy, который в основном делает то же самое.BlockCopy просто немного быстрее.

вам нужно сначала преобразовать байт [] в float []. Посмотрите на это ниже

static float[] ConvertByteArrayToFloat(byte[] bytes)
{
    if(bytes == null)
        throw new ArgumentNullException("bytes");

   if(bytes.Length % 4 != 0)
        throw new ArgumentException
              ("bytes does not represent a sequence of floats");

    return Enumerable.Range(0, bytes.Length / 4)
                     .Select(i => BitConverter.ToSingle(bytes, i * 4))
                     .ToArray();
}
1 голос
/ 03 января 2012

BlockCopy не имеет управляемой реализации .NET.Внутренне он вызывает внешний выигрыш API.

[SecuritySafeCritical]
public static extern void BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count);
...