Подтвердить что ты не робот

Почему поток памяти С# резервирует столько памяти?

Наше программное обеспечение распаковывает определенные байтовые данные через GZipStream, который считывает данные из MemoryStream. Эти данные распаковываются в блоках 4 КБ и записываются в другой MemoryStream.

Мы поняли, что память, которую выделяет процесс, намного выше, чем фактические распакованные данные.

Пример: Сжатый массив байтов с 2425536 байтами распадается до 23 050 718 байт. Профайлер памяти, который мы используем, показывает, что метод MemoryStream.set_Capacity(Int32 value) выделил 67,104,936 байт. Это коэффициент 2,9 между зарезервированной и фактически записанной памятью.

Примечание: MemoryStream.set_Capacity вызывается из MemoryStream.EnsureCapacity, который сам вызывается из MemoryStream.Write в нашей функции.

Почему резервная копия MemoryStream резервирует столько возможностей, даже если она добавляет только блоки размером 4 КБ?

Вот фрагмент кода, который распаковывает данные:

private byte[] Decompress(byte[] data)
{
    using (MemoryStream compressedStream = new MemoryStream(data))
    using (GZipStream zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
    using (MemoryStream resultStream = new MemoryStream())
    {
        byte[] buffer = new byte[4096];
        int iCount = 0;

        while ((iCount = zipStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            resultStream.Write(buffer, 0, iCount);
        }
        return resultStream.ToArray();
    }
}

Примечание. Если это необходимо, это конфигурация системы:

  • Windows XP 32bit,
  • .NET 3.5
  • Скомпилирован с Visual Studio 2008
4b9b3361

Ответ 1

Потому что это алгоритм для того, как он расширяет свою емкость.

public override void Write(byte[] buffer, int offset, int count) {

    //... Removed Error checking for example

    int i = _position + count;
    // Check for overflow
    if (i < 0)
        throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));

    if (i > _length) {
        bool mustZero = _position > _length;
        if (i > _capacity) {
            bool allocatedNewArray = EnsureCapacity(i);
            if (allocatedNewArray)
                mustZero = false;
        }
        if (mustZero)
            Array.Clear(_buffer, _length, i - _length);
        _length = i;
    }

    //... 
}

private bool EnsureCapacity(int value) {
    // Check for overflow
    if (value < 0)
        throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong"));
    if (value > _capacity) {
        int newCapacity = value;
        if (newCapacity < 256)
            newCapacity = 256;
        if (newCapacity < _capacity * 2)
            newCapacity = _capacity * 2;
        Capacity = newCapacity;
        return true;
    }
    return false;
}

public virtual int Capacity 
{
    //...

    set {
         //...

        // MemoryStream has this invariant: _origin > 0 => !expandable (see ctors)
        if (_expandable && value != _capacity) {
            if (value > 0) {
                byte[] newBuffer = new byte[value];
                if (_length > 0) Buffer.InternalBlockCopy(_buffer, 0, newBuffer, 0, _length);
                _buffer = newBuffer;
            }
            else {
                _buffer = null;
            }
            _capacity = value;
        }
    }
}

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

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

const double ResizeFactor = 1.25;

private byte[] Decompress(byte[] data)
{
    using (MemoryStream compressedStream = new MemoryStream(data))
    using (GZipStream zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
    using (MemoryStream resultStream = new MemoryStream(data.Length * ResizeFactor)) //Set the initial size to be the same as the compressed size + 25%.
    {
        byte[] buffer = new byte[4096];
        int iCount = 0;

        while ((iCount = zipStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            if(resultStream.Capacity < resultStream.Length + iCount)
               resultStream.Capacity = resultStream.Capacity * ResizeFactor; //Resize to 125% instead of 200%

            resultStream.Write(buffer, 0, iCount);
        }
        return resultStream.ToArray();
    }
}

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

const double MinResizeFactor = 1.05;

private byte[] Decompress(byte[] data)
{
    using (MemoryStream compressedStream = new MemoryStream(data))
    using (GZipStream zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
    using (MemoryStream resultStream = new MemoryStream(data.Length * MinResizeFactor)) //Set the initial size to be the same as the compressed size + the minimum resize factor.
    {
        byte[] buffer = new byte[4096];
        int iCount = 0;

        while ((iCount = zipStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            if(resultStream.Capacity < resultStream.Length + iCount)
            {
               double sizeRatio = ((double)resultStream.Position + iCount) / (compressedStream.Position + 1); //The +1 is to prevent divide by 0 errors, it may not be necessary in practice.

               //Resize to minimum resize factor of the current capacity or the 
               // compressed stream length times the compression ratio + min resize 
               // factor, whichever is larger.
               resultStream.Capacity =  Math.Max(resultStream.Capacity * MinResizeFactor, 
                                                 (sizeRatio + (MinResizeFactor - 1)) * compressedStream.Length);
             }

            resultStream.Write(buffer, 0, iCount);
        }
        return resultStream.ToArray();
    }
}

Ответ 2

MemoryStream удваивает свой внутренний буфер, когда заканчивается пробел. Это может привести к образованию 2-х отходов. Я не могу понять, почему вы видите больше этого. Но это основное поведение ожидается.

Если вам не нравится это поведение, напишите свой собственный поток, который хранит свои данные в меньших фрагментах (например, a List<byte[1024 * 64]>). Такой алгоритм ограничивал бы его количество отходов до 64 КБ.

Ответ 3

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

Сумма я = 1 k (2 i) = 2 k + 1 -1.

(где k - количество перераспределений, таких как k = 1 + log 2 StreamSize

Что вы видите.

Ответ 4

Ну, увеличение пропускной способности потоков означает создание всего нового массива с новой емкостью и копирование старого. Это очень дорого, и если вы сделали это для каждого Write, ваша производительность сильно пострадает. Вместо этого MemoryStream расширяется больше, чем необходимо. Если вы хотите улучшить это поведение и знаете общую требуемую емкость, просто используйте конструктор MemoryStream с параметром capacity:) Затем вы можете использовать MemoryStream.GetBuffer вместо ToArray.

Вы также видите отброшенные старые буферы в профилировщике памяти (например, от 8 MiB до 16 MiB и т.д.).

Конечно, вы не заботитесь о том, чтобы иметь один последовательный массив, поэтому для вас может быть лучше всего иметь собственный собственный поток памяти, который использует по мере необходимости несколько массивов в виде больших кусков по мере необходимости, а затем просто скопируйте его все сразу на вывод byte[] (если вам вообще нужен byte[] вообще - вполне вероятно, что проблема дизайна).