10
votes

Étrange d'exception hors mémoire lors de la sérialisation

J'utilise VSTS2008 + C # + .NET 3.5 Pour exécuter cette application de console sur X64 Server 2003 Enterprise avec une mémoire physique 12G.

Voici mon code, et je trouve lors de l'exécution de la déclaration Bformater.Serialize (flux, table) , il n'y a pas d'exception de mémoire. J'ai surveillé l'utilisation de la mémoire par l'intermédiaire de l'onglet de la perorance du gestionnaire de tâches et je trouve que seule la mémoire physique 2G est utilisée lorsque l'exception est lancée, il ne faut donc pas sortir de mémoire. : - ( p>

Des idées Qu'est-ce qui ne va pas? Toute limitation de la sérialisation .NET? P>

    static DataTable MakeParentTable()
    {
        // Create a new DataTable.
        System.Data.DataTable table = new DataTable("ParentTable");
        // Declare variables for DataColumn and DataRow objects.
        DataColumn column;
        DataRow row;

        // Create new DataColumn, set DataType, 
        // ColumnName and add to DataTable.    
        column = new DataColumn();
        column.DataType = System.Type.GetType("System.Int32");
        column.ColumnName = "id";
        column.ReadOnly = true;
        column.Unique = true;
        // Add the Column to the DataColumnCollection.
        table.Columns.Add(column);

        // Create second column.
        column = new DataColumn();
        column.DataType = System.Type.GetType("System.String");
        column.ColumnName = "ParentItem";
        column.AutoIncrement = false;
        column.Caption = "ParentItem";
        column.ReadOnly = false;
        column.Unique = false;
        // Add the column to the table.
        table.Columns.Add(column);

        // Make the ID column the primary key column.
        DataColumn[] PrimaryKeyColumns = new DataColumn[1];
        PrimaryKeyColumns[0] = table.Columns["id"];
        table.PrimaryKey = PrimaryKeyColumns;

        // Create three new DataRow objects and add 
        // them to the DataTable
        for (int i = 0; i <= 5000000; i++)
        {
            row = table.NewRow();
            row["id"] = i;
            row["ParentItem"] = "ParentItem " + i;
            table.Rows.Add(row);
        }

        return table;
    }

    static void Main(string[] args)
    {
        DataTable table = MakeParentTable();
        Stream stream = new MemoryStream();
        BinaryFormatter bformatter = new BinaryFormatter();
        bformatter.Serialize(stream, table);   // out of memory exception here
        Console.WriteLine(table.Rows.Count);

        return;
    }


1 commentaires

Merci Marc, votre réponse est si grande et je l'ai marquée comme répondu. Appréciez si vous pouviez partager votre expertise sur la mémoire virtuelle ici, Stackoverflow.com/Questtions/1297797/...


4 Réponses :


13
votes

Remarque: DataTable par défaut du format de sérialisation XML utilisé dans 1. *, qui est incroyablement inefficace. Une chose à essayer est passer au format plus récent : xxx


RE le hors mémoire / 2GB; Les objets individuels .NET (tels que le octet [] derrière un Morthstream ) sont limités à 2 Go. Essayez peut-être d'écrire sur un filtream à la place?

(éditer: Nope: Essayé que, toujours erreurs)

Je me demande aussi que vous pouvez obtenir de meilleurs résultats ( Dans ce cas) en utilisant table.writeexml (flux) , peut-être avec une compression telle que GZIP si l'espace est une prime.


9 commentaires

Bonjour Marc, merci pour votre précieuse réponse. Pour les octets [] Limitation de la taille 2G, pourriez-vous me recommander d'autres documents sur ce sujet s'il vous plaît? Je ne le sais jamais et je veux apprendre plus d'arrière-plans.


"Edit: Nope: Essayé que, toujours erreurs" - que voulez-vous dire avoir essayé cela, des erreurs toujours? Vous voulez dire quelle méthode a toujours des erreurs?


Marc, j'ai essayé d'utiliser WrecXML et ça marche. Mais la taille du fichier de résultat n'est que de 520 m. C'est beaucoup moins de 2g. Comment prouvez-vous que mon code d'origine frappe la limitation 2G?


Re dernier ... je ne le fais pas; Je souligne simplement que même avec une quantité de mémoire Stonking, certaines choses restent plafonnées de taille. Les tableaux (qui sous-tendent d'autres choses comme des listes et de la mémoire Morthstream) et des cordes sont les plus évidentes. Si vous frappez une autre autre limitation de Binaryformatter , alors pour être honnête, vous êtes scapperé, qu'il s'agisse de la mémoire système, de la taille de la liste ou de tout simplement du karma. Donc, si WRITOXML fonctionne, collez-vous avec cela ;-p


Re toujours erreurs: je veux dire que la commutation à un filtream et en utilisant votre code "tel quel" a toujours le problème; Donc, ce n'est pas nécessairement purement limité aux limitations MemoryStream; Mais notez que, puisque Binaryformatter effectue un suivi de l'objet en interne, il y a beaucoup d'autres choses qui se passent de toute façon.


Re 2GB; Cela pourrait être dans la spécification CLI (ECMA335) - Je ne peux pas être sûr; Mais il est discuté plus ici: blogs.msdn.com/joshwil /Rarchive/2005/08/10/450202.aspx


Quant à la définition de la mémoire. Une partie de la réponse est que les chaînes de la mémoire dans .NET sont UTF-16, mais UTF-8 lorsque vous les écrivez (probablement). Cela signifie dans votre test que vous allez la moitié des octets pour la chaîne lorsque vous les écrivez.


Merci Marc et Mikael, votre réponse est si grande! J'ai une question connexe ici, apprécie si vous pouviez aider. Stackoverflow.com/ Questions / 1297797 / ...


J'essayais de Serialize Binary Serialize 100k Rowo et de sortir des exceptions de mémoire ... Modification de cette propriété comme recommandé et maintenant 100k Rows Serialized et Gzipped est de 7,9 Mo ... Merci pour l'aide!



1
votes

1) Le système d'exploitation est X64, mais est l'application X64 (ou AnyCPU)? Sinon, il est plafonné à 2 Go.

2) Est-ce que cela arrive "tôt", ou après l'exécution de l'application depuis quelque temps (c'est-à-dire des sérialisations plus tard)? Pourrait-il peut-être être le résultat d'une grande fragmentation de tas d'objets ...?


1 commentaires

Merci Kristofera, pour vos préoccupations, 1. Je construit dans n'importe quel processeur, il devrait donc pouvoir consommer plus de 2g de mémoire physique, correct? 2. L'exception hors de la mémoire se produit après 1 minute, après que la méthode, le ralentissement de la duent de la méthode réussisse. Donc, je pense que cela ne devrait pas être le résultat d'une grande fragmentation de tas d'objets? Des commentaires?



1
votes

Fait intéressant, il monte en fait jusqu'à 3,7 Go avant de donner une erreur de mémoire ici (Windows 7 x64). Apparemment, il aurait besoin d'environ le double de cette quantité à compléter.

Étant donné que l'application utilise 1,65 Go après avoir créé la table, il semble probable qu'il frappe le 2GB octet [] (ou tout objet unique) LIMIT MARC Gravell parle de (1,65 Go + 2GB ~ = 3,7 Go)

basé sur ce blog , je suppose Vous pouvez attribuer votre mémoire à l'aide de Winapi et écrivez votre propre implémentation Morthstream en utilisant cela. C'est-à-dire que si vous vouliez vraiment faire cela. Ou écrire un en utilisant plus d'un tableau bien sûr :)


4 commentaires

Bonjour Thorarin, 1. Pour la limitation de la taille 2G de l'octet [], je veux en savoir plus. Avez-vous des documents plus connus? 2. Le nombre de 1,65 Go et 2GB signifie que l'empreinte de votre calcul dans votre calcul?


1,65 Go pour le datatable lui-même. Tout ce que je pouvais trouver sur la limite de 2 Go (autre que celui qu'il existe) est blogs.msdn.com/joshwil/archive/2005/08/10/450202.aspx


Merci Thorarin, 1. Je comprends la limitation de 2 Go. Mais comment prouvez-vous que le 2GB est effectivement atteint? :-) 2. Pour le 1.65G Comment calculez-vous un tel nombre précis? :-)


Merci Thorarin, votre réponse est si grande! J'ai une question connexe ici, apprécie si vous pouviez aider. Stackoverflow.com/ Questions / 1297797 / ...



6
votes

Comme déjà discuté, il s'agit d'un problème fondamental qui tente d'obtenir des blocs de mémoire contigus dans le type de gigaoctet de taille.

Vous serez limité par (en difficulté croissante)

  1. la quantité de mémoire adressable
    • Depuis que vous êtes 64 bits, ce sera une mémoire physique de 12 Go, moins de trous de l'information requis par des appareils plus tout espace de fichier de swap.
    • Notez que vous devez exécuter une application avec le PE pertinent Les en-têtes qui l'indiquent peuvent exécuter 64 bits ou vous exécuterez sous WOW64 et que 4 Go d'espace d'adresses.
    • Notez également que la cible par défaut était modifié en 2010 , a changement contenteux .
    • the limitation de la CLR qu'aucun objet unique ne peut consommer plus moins de 2 Go d'espace .
    • Trouver un bloc contigu dans la mémoire disponible.

      Vous pouvez trouver que vous manquez d'espace avant la limite CLR de 2 car le tampon de support dans le flux est élargi dans une mode "doublée" et cela donne rapidement le tampon alloué dans le grand tas d'objets. Ce tas n'est pas compacté de la même manière que les autres tas sont (1) et, par conséquent, le processus de construction de la taille maximale théorique de la mémoire tampon sous 2 fragmente le loh de sorte que vous échouiez de Trouvez un bloc contigu suffisamment important avant que cela ne se produise.

      Ainsi une approche d'atténuation si vous êtes fermeture à la limite consiste à définir la capacité initiale du flux de telle sorte qu'il a définitivement un espace suffisant à partir du démarrage via L'un des constructeurs .

      Étant donné que vous écrivez dans le flux de mémoire dans le cadre d'un processus de sérialisation, il serait logique d'utiliser des flux tels que destinés et d'utiliser uniquement les données requises.

      • Si vous êtes sérialisé dans un emplacement basé sur un fichier, plongez-le directement dans celui-ci.
      • S'il s'agit de données dans une base de données SQL Server envisagez d'utiliser:
        • filtream 2008 seulement j'ai peur.
        • à partir de 2005, vous pouvez lire / écrire dans des morceaux mais écrire est Pas bien intégré à Ado.net
        • Pour les versions antérieures à 2005, il existe relativement désagréable Solutions de contournement
        • Si vous êtes sérialisé cela en mémoire pour une utilisation en disant une comparaison, envisagez de streamer les données étant comparées et diffèrent comme vous le suivez.
        • Si vous persistez un objet en mémoire de recréer ce dernier, cela devrait vraiment aller dans un fichier ou un fichier mappé de mémoire. Dans les deux cas, le système d'exploitation est alors libre de le structurer de mieux qu'il peut (dans les caches de disque ou les pages étant cartographiés dans la mémoire principale) et il est probable qu'il fera un meilleur travail que la plupart des gens puissent faire eux-mêmes.
        • Si vous le faites de manière à ce que les données puissent être comprimées, envisagez d'utiliser la compression en continu. Tout flux de compression à base de bloc peut être assez facilement converti en mode de diffusion en continu avec l'addition de remplissage. Si votre API de compression ne prend pas en charge cet envisagez de manière native en utilisant un qui fait ou écrit le wrapper pour le faire.
        • Si vous faites cela pour écrire sur un tampon d'octet qui est ensuite épinglé et transmis à une fonction non gérée, utilisez le UniversaindMemoryStream est plutôt de meilleure chance de pouvoir attribuer un tampon de ce type de taille mais n'est toujours pas garanti.

          Peut-être que si vous nous dites quel vous êtes sérialisé un objet de cette taille pour que nous puissions pouvoir vous dire de meilleures façons de le faire.


          1. Il s'agit d'un détail de mise en œuvre que vous ne devriez pas compter sur

1 commentaires

Merci Shuggycouk, votre réponse est si grande! J'ai une question connexe ici, apprécie si vous pouviez aider. Stackoverflow.com/ Questions / 1297797 / ...