0

I am getting an intermittent "out of memory" exception at this statement:

        return ms.ToArray();

In this method:

public static byte[] Serialize(Object inst)
{
    Type t = inst.GetType();
    DataContractSerializer dcs = new DataContractSerializer(t);
    MemoryStream ms = new MemoryStream();
    dcs.WriteObject(ms, inst);
    return ms.ToArray();
}

How can I prevent it? Is there a better way to do this?

The length of ms is 182,870,206 bytes (174.4 MB)

I am putting this into a byte array so that I can then run it through compression and store it to disk. The data is (obviously) a large list of a custom class that I am downloading from a WCF server when my silverlight application starts. I am serializing it and compressing it so it uses only about 6MB in isolated storage. The next time the user visits and runs the silverlight application from the web, I check the timestamp, and if good I just open the file from isolated, decompress it, deserialize it, and load my structure. I am keeping the entire structure in memory because the application is mostly geared around manipulating the contents of this structure.

@configurator is correct. The size of the array was too big. I rolled by own serializer, by declaring a byte array of [list record count * byte count per record], then stuffed it directly myself using statements like this to stuff it:

    Buffer.BlockCopy(
           BitConverter.GetBytes(node.myInt),0,destinationArray,offset,sizeof(int)); 
    offset += sizeof(int);

and this to get it back:

    newNode.myInt= BitConverter.ToInt32(sourceByteArray,offset); 
    offset += sizeof(int);

Then I compressed it and stored it to isolated storage.

My size went from 174MB with the DataContractSerializer to 14MB with mine. After compression it went from a 6MB to a 1MB file in isolated storage.

Thanks to Configurator and Filip for their help.

dbc
  • 104,963
  • 20
  • 228
  • 340
Brad Boyce
  • 1,248
  • 1
  • 17
  • 34
  • How big is your object? This should only happen if your serialized data has a very large size. Or perhaps if you serialize many medium(100k or so) sized objects. – CodesInChaos Jul 14 '11 at 21:50
  • 1
    Are you expecting the serialized contract to be big? If so and you are going to do IO with the resulting bytes, you might try using a FileStream etc. directly-- – antlersoft Jul 14 '11 at 21:51
  • @code Edited post to show stream is 174.4 MB – Brad Boyce Jul 14 '11 at 22:06
  • You can chain streams together -- if you're using the compression APIs they're all stream based anyway, why do you need the data as an array of bytes at all? Just serialize the data directly into a compression stream. – BrainSlugs83 May 08 '12 at 21:23

4 Answers4

3

The problem seems to be that you're expecting to return a 180MB byte array. That means the framework would need to find and allocate a consecutive 180MB of free memory to copy the stream data into, which is usually quite hard - hence the OutOfMemoryException. If you need to continue handling this amount of memory, use the memory stream itself (reading and writing to it as you need) to hold the buffer; otherwise, save it to a file (or to whatever other place you need it, e.g. serving it over a network) directly instead of using the memory stream.

I should mention that the memory stream has a 180MB array of its own in there as well, so is also in a bit of trouble and could cause OutOfMemory during serialization - it would likely be better (as in, more robust) if you could serialize it to a temporary file. You might also want to consider a more compact - but possibly less readable - serialization format, like json, binary serialization, or protocol buffers.


In response to the comment: to serialize directly to disk, use a FileStream instead of a MemoryStream:

public static void Serialize(Object inst, string filename)
{
    Type t = inst.GetType();
    DataContractSerializer dcs = new DataContractSerializer(t);
    using (FileStream stream = File.OpenWrite(filename)) {
        dcs.WriteObject(ms, inst);
    }
}
configurator
  • 40,828
  • 14
  • 81
  • 115
  • Really, I would think that `MemoryStream.ToArray()` would return the internal array that contained the stream data. – John Saunders Jul 14 '11 at 22:35
  • @John: It returns a copy of it. First, it needs to be trimmed down to the memory stream's length; second, otherwise modifications to it or the memory stream would overlap - but only sometimes. – configurator Jul 14 '11 at 22:44
  • @configurator Could you outline an approach (or even code) that would let me serialize and compress this object directly to disk. I don't care if it's readable, nobody is going to look at it. I just want to save it in a small space on disk and load it directly in to memory next time they load up the app. – Brad Boyce Jul 14 '11 at 23:21
  • @Brad: "I don't care if it's readable, nobody is going to look at it" - you should **always** care if it's readable; if they code isn't readable you can never be sure if it's correct! I've updated the answer with the changes you need to make to save it to file. – configurator Jul 14 '11 at 23:31
  • 1
    @configurator: I didn't mean the code, I meant the serialized/compressed file :) – Brad Boyce Jul 14 '11 at 23:38
  • Oh, that makes a lot more sense, sorry. If you're writing to file and you want to use a binary serializer, have a look at [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter](http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter.aspx). – configurator Jul 14 '11 at 23:41
  • @configurator: Thanks for the code. How would I throw some compression in there? That's going to result in a file 170MB, right? Is that a lot of space to take up on web user's drives? You mentioned some other more compact serialization? – Brad Boyce Jul 14 '11 at 23:43
  • I've never used it, but [protocol buffers](http://code.google.com/p/protobuf-net/) is a much more efficient serialization format. – configurator Jul 14 '11 at 23:50
  • I'd go with Json.net as first attempt. Its use of string property names makes it simpler to use that protobuf. And then put the output through a compression stream dotnetzip. If you really need the lower space protobuf will be better, but it typically requires a bit more configuration, like giving properties integer IDs. – CodesInChaos Jul 16 '11 at 19:26
0

I don't know how you use that code, but one thing that strikes me is that you don't release your resources. For instance, if you call Serialize(obj) a lot of times with a lot of large objects, you will end up having a lot of memory being used that is not released directly, however the GC should handle that properly, but you should always release your resources.

I've tried this piece of code:

public static byte[] Serialize(object obj)
{
    Type type = obj.GetType();
    DataContractSerializer dcs = new DataContractSerializer(type);

    using (var stream = new MemoryStream())
    {
        dcs.WriteObject(stream, obj);
        return stream.ToArray();
    }
}

With the following Main-method in a Console Application

static void Main(string[] args)
{
    var filipEkberg = new Person {Age = 24, Name = @"Filip Ekberg"};

    var obj = Serialize(filipEkberg);
}

However, my byte-array is not nearly as big as yours. Having a look at this similar issue, you might want to consider checking out protobuf-net.

It might also be interesting to know what you are intending to do with the serialized data, do you need it as a byte-array or could it just as well be XML written to a text-file?

Community
  • 1
  • 1
Filip Ekberg
  • 36,033
  • 20
  • 126
  • 183
  • @Brad, as @configurator said and as the link I provided also says, it's a limitation and @configurator explains sort of why it's like that. You will need to look into alternatives like serializing to disk instead of memory. Hope this helps. – Filip Ekberg Jul 14 '11 at 22:54
  • Ok, I've been testing with your approach Filip, and it cleared up the write, but then I'm still getting outOfMemory on the read (even with a using statement). So this approach won't work for me, although I agree that it is better to wrap in a using that to not have one. @configurator 's approach won't work for me either, because I can't put a 174 M file on my user's drive, and his approach doesn't compress. I hesitant to use protobuf-net as I am concerned about adding an external dependency that may not migrate well as new silverlight versions are released. – Brad Boyce Jul 15 '11 at 20:09
  • @Brad, I am tempted to reproduce the problem, can you tell me a little bit about the system specs on the machine that gets `OutOfMemoryException` ? Maybe using external dependencies is the only way, you can always deploy it within your binary if the licens allowes that. – Filip Ekberg Jul 15 '11 at 21:54
  • it's my co-worker that is having the problem running this - I can't repeat it on my machine. If I'm still stuck by Monday I'll see if I can get his specs. Right now I'm converting the structure manually and stuffing it directly into a 14.6 Mbyte byte array. Then I'll compress it and write to isolated storage. I think it might work. – Brad Boyce Jul 16 '11 at 17:54
-1

Try to serialize to a stream (i.e. FileStream) instead of byte array. This way you can serialize gigabytes of data without OutOfMemory exception.

        public static void Serialize<T>(T obj, string path)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(T));
            Stream stream = File.OpenWrite(path);
            serializer.WriteObject(stream, obj);
        }

        public static T Deserialize<T>(string path)
        {
            DataContractSerializer serializer = new DataContractSerializer(typeof(T));
            Stream stream = File.OpenRead(path);
            return (T)serializer.ReadObject(stream);
        }
TWT
  • 2,511
  • 1
  • 23
  • 37
-4

Try to set memory stream position to 0 and after only call ToArray().

Regards.

Tigran
  • 61,654
  • 8
  • 86
  • 123