30 votes

Pourquoi le flux de mémoire C # réserve-t-il autant de mémoire?

Notre logiciel est la décompression de certains octets de données par le biais d'un GZipStream, qui lit des données à partir d'un MemoryStream. Ces données sont décompressés dans des blocs de 4 ko et écrit dans un autre MemoryStream.

Nous avons réalisé que la mémoire de l'allocation, il est beaucoup plus élevé que le réel décompressé de données.

Exemple: Un comprimé tableau d'octets avec 2,425,536 octets obtient décompressé à 23,050,718 octets. Le profileur de mémoire nous utilisons montre que la Méthode MemoryStream.set_Capacity(Int32 value) alloués 67,104,936 octets. C'est un facteur de 2,9 entre réservés et écrit de mémoire.

Remarque: MemoryStream.set_Capacity est appelée à partir d' MemoryStream.EnsureCapacity qui est elle-même appelée à partir d' MemoryStream.Write dans notre fonction.

Pourquoi ne l' MemoryStream de la réserve tellement de capacités, même si elle n'ajoute des blocs de 4 KO?

Voici l'extrait de code qui décompresse les données:

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();
    }
}

Remarque: le Cas échéant, c'est la configuration du système:

  • Windows XP 32 bits,
  • .NET 3.5
  • Compilé avec Visual Studio 2008

44voto

Scott Chamberlain Points 32782

Parce que c'est l'algorithme de la manière dont il développe sa capacité.

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;
        }
    }
}

Donc, chaque fois que vous frappez la limite de capacité de doubler la taille de la capacité. La raison pour laquelle il ne c'est qu' Buffer.InternalBlockCopy opération est lente pour les grands tableaux donc si il avait souvent redimensionner chaque appel d'Écriture de la performance chuter de manière significative.

Quelques choses que vous pourriez faire pour améliorer la performance pour vous est que vous pouvez définir la capacité initiale d'au moins la taille de votre comprimé tableau et vous pouvez alors augmenter la taille par un facteur plus petit que 2.0 afin de réduire la quantité de mémoire que vous utilisez.

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();
    }
}

Si vous vouliez, vous pourriez faire encore plus de fantaisie algorithmes comme le redimensionnement en vigueur en fonction du taux de compression

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();
    }
}

16voto

usr Points 74796

MemoryStream double son tampon interne lorsqu'il manque d'espace. Cela peut entraîner 2 fois plus de déchets. Je ne peux pas dire pourquoi vous voyez plus que cela. Mais ce comportement de base est attendu.

Si vous n'aimez pas ce comportement, écrivez votre propre flux qui stocke ses données en petits morceaux (par exemple un List<byte[1024 * 64]> ). Un tel algorithme limiterait sa quantité de déchets à 64 Ko.

6voto

Alexei Levenkov Points 49945

On dirait que vous regardez la quantité totale de mémoire allouée, pas le dernier appel. Puisque le flux de mémoire double sa taille lors de la réallocation, il augmentera environ deux fois à chaque fois - la mémoire totale allouée serait donc approximativement la somme de puissances de 2 comme:

Somme i = 1k (2 i ) = 2 k + 1 -1.

(où k est le nombre de réallocations comme k = 1 + log 2 StreamSize

C'est à peu près ce que vous voyez.

2voto

Luaan Points 8934

Ainsi, l'augmentation de la capacité des cours d'eau signifie la création d'un nouveau tableau avec les nouvelles capacités, et la copie de l'ancien cours. C'est très cher, et si vous l'avez fait pour chaque Write, les performances de votre souffrirait beaucoup. Donc, au lieu de cela, l' MemoryStream se dilate plus que nécessaire. Si vous souhaitez améliorer le comportement et vous savez la capacité totale requise, il suffit d'utiliser l' MemoryStream constructeur avec l' capacity paramètre :) Vous pouvez ensuite utiliser MemoryStream.GetBuffer au lieu de ToArray trop.

Vous êtes également voir les rebuts de vieux tampons dans la mémoire de profils (par exemple à partir de 8 mo à 16 MiB etc.).

Bien sûr, vous ne se soucient pas d'avoir une seule consécutives tableau, de sorte qu'il pourrait être une meilleure idée pour vous de simplement avoir un flux de mémoire de votre propre qui utilise plusieurs tableaux créés en tant que de besoin, en tant que de gros morceaux, puis il suffit de copier tous à la fois à la sortie byte[] (si vous avez besoin même de l' byte[] à tous - tout à fait probable, c'est un problème de conception).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X