étrange exception de mémoire insuffisante lors de la sérialisation

J’utilise VSTS2008 + C # + .Net 3.5 pour exécuter cette application console sur x64 Server 2003 Enterprise avec une mémoire physique de 12 Go.

Voici mon code, et je trouve lors de l’exécution de l’instruction bformatter.Serialize (stream, table), qu’il existe une exception de mémoire insuffisante. J’ai surveillé l’utilisation de la mémoire via l’onglet Perormance du Gestionnaire des tâches et je constate que seule la mémoire physique 2G est utilisée lors de la levée d’une exception. Elle ne devrait donc pas manquer de mémoire. 🙁

Des idées ce qui ne va pas? Toute limitation de la sérialisation .Net?

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.Ssortingng"); 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; } 

merci d’avance, George

Remarque: DataTable utilise par défaut le format de sérialisation xml utilisé dans 1. *, ce qui est incroyablement inefficace. Une chose à essayer est de passer au nouveau format :

  dt.RemotingFormat = System.Data.SerializationFormat.Binary; 

Concernant le manque de mémoire / 2 Go; Les objects .NET individuels (tels que l’ byte[] derrière un MemoryStream ) sont limités à 2 Go. Peut-être essayez-vous d’écrire sur un FileStream ?

(edit: nope: essayé ça, toujours des erreurs)

Je me demande également si vous pouvez obtenir de meilleurs résultats (dans ce cas) en utilisant table.WriteXml(stream) , peut-être avec une compression telle que GZIP si l’espace est une prime.

Comme indiqué précédemment, il s’agit d’un problème fondamental lorsque l’on souhaite obtenir des blocs de mémoire contigus de la taille d’un gigaoctet.

Vous serez limité par (en difficulté croissante)

  1. La quantité de mémoire adressable
    • puisque vous avez 64 bits, ce sera votre mémoire physique de 12 Go, moins les trous requirejs par les appareils, plus l’espace de fichiers d’échange.
    • Notez que vous devez exécuter une application avec les en-têtes PE appropriés qui indiquent qu’elle peut fonctionner en 64 bits. Sinon, vous utiliserez WoW64 et ne disposerez que de 4 Go d’espace d’adressage.
    • Notez également que la cible par défaut a été modifiée en 2010 , une modification litigieuse .
  2. La limitation du CLR selon laquelle aucun object ne peut occuper plus de 2 Go d’espace .
  3. Recherche d’un bloc contigu dans la mémoire disponible.

Vous pouvez constater que vous manquez d’espace avant la limite CLR de 2 car le tampon de sauvegarde dans le stream est développé de manière à être «doublé», ce qui entraîne rapidement l’allocation du tampon dans le segment d’objects volumineux. Ce tas n’est pas compacté de la même manière que les autres tas (1) et il en résulte que le processus de construction jusqu’à la taille maximale théorique du tampon sous 2 fragments du LOH afin que vous ne trouviez pas un bloc contigu suffisamment grand avant ça arrive.

Par conséquent, si vous approchez de la limite, une approche d’atténuation consiste à définir la capacité initiale du stream de sorte qu’il dispose d’un espace suffisant dès le départ via l’ un des constructeurs .

Étant donné que vous écrivez dans le stream de mémoire dans le cadre d’un processus de sérialisation, il serait logique d’utiliser réellement les stream comme prévu et d’utiliser uniquement les données requirejses.

  • Si vous sérialisez dans un emplacement basé sur un fichier, diffusez-le directement dans cet emplacement.
  • S’il s’agit de données entrant dans une firebase database Sql Server, envisagez d’utiliser:
    • FILESTREAM 2008 seulement j’ai peur.
    • À partir de 2005, vous pouvez lire / écrire en morceaux, mais l’écriture n’est pas bien intégrée dans ADO.Net
    • Pour les versions antérieures à 2005, il existe des solutions relativement désagréables.
  • Si vous sérialisez cela en mémoire pour une utilisation dans une comparaison, envisagez de transmettre en continu les données comparées et de les différer au fur et à mesure.
  • Si vous persistez un object en mémoire pour le recréer plus tard, cela devrait aller dans un fichier ou dans un fichier mappé en mémoire. Dans les deux cas, le système d’exploitation est alors libre de le structurer du mieux qu’il peut (dans les caches de disque ou les pages mappées dans et hors de la mémoire principale) et il y a de fortes chances qu’il obtienne un meilleur résultat que celui que la plupart des gens sont capables de faire. se.
  • Si vous faites cela pour que les données puissent être compressées, envisagez d’utiliser la compression en continu. Tout stream de compression basé sur des blocs peut être assez facilement converti en mode de diffusion en continu avec l’ajout de remplissage. Si votre API de compression ne prend pas cela en charge, envisagez d’en utiliser une qui le fait ou écrivez le wrapper pour le faire.
  • Si vous procédez ainsi, vous écrivez dans un tampon d’octets qui est ensuite épinglé et transmis à une fonction non gérée, puis utilisez plutôt UnmanagedMemoryStream . Cela offre une chance légèrement plus grande de pouvoir allouer un tampon de cette taille, mais n’est toujours pas garanti. faire cela.

Peut-être que si vous nous dites ce que vous sérialisez un object de cette taille, nous pourrons peut-être vous dire de meilleures façons de le faire.


  1. Ceci est un détail de la mise en oeuvre sur lequel vous ne devez pas compter

1) Le système d’exploitation est x64, mais l’application est-elle x64 (ou anycpu)? Sinon, il est limité à 2 Go.

2) Est-ce que cela se produit «tôt» ou après que l’application ait fonctionné pendant un certain temps (c’est-à-dire n sérialisations plus tard)? Cela pourrait-il être le résultat d’une fragmentation de tas d’objects volumineux …?

Fait intéressant, il passe en fait à 3,7 Go avant de générer une erreur de mémoire ici (Windows 7 x64). Apparemment, il faudrait environ le double de ce montant pour terminer.

Étant donné que l’application utilise 1,65 Go après avoir créé la table, il semble probable qu’elle atteigne la limite d’ byte[] (ou tout object isolé) de 2 Go dont parle Marc Gravell (1,65 Go + 2 Go ~ = 3,7 Go).

Sur la base de ce blog , je suppose que vous pouvez allouer votre mémoire à l’aide de WINAPI et écrire votre propre implémentation MemoryStream à l’aide de celle-ci. Autrement dit, si vous vouliez vraiment faire cela. Ou écrivez-en un en utilisant plus d’un tableau de cours 🙂