关于c#struct memory / serialization overhead

时间:2011-03-06 07:35:37

标签: c#

我的代码是这样的:

[Serializable]
[StructLayout(LayoutKind.Sequential,Pack=1)]
struct Foo
{
    public byte Bar;            
    public Foo(byte b){Bar=b;}
}
public static void Main (string[] args)
{
    Foo[] arr = new Foo[1000];
    for (int i = 0; i < 1000; i++) {
        arr[i]=new Foo(42);            
    }
    var fmt = new BinaryFormatter();
    using(FileStream f= File.Create("test.bin")){
        fmt.Serialize(f,arr);
    }
    Console.WriteLine (new FileInfo("test.bin").Length);
}

结果bin文件大10095字节。为什么我的Foo结构占用了这么多字节?每个结构开销的9个字节到底是什么?

PS:  我正在为中文字符编写查找库(它是关于大约70,000个字符的信息),db4o或其他可嵌入数据库(如sqlite)有点膨胀。我认为以纯字符串格式存储所有信息,这是最友好的,但不太灵活。我想将信息保存在列表中并将它们作为二进制序列化存储到存档中,我选择了DotNetZip进行存档。但序列化开销是一个意想不到的障碍。一个更好的序列化解决方案将是好的,否则我将以纯字符串格式保存信息并通过硬编码解析它。

2 个答案:

答案 0 :(得分:14)

Foo结构不是那么“大”,而是你所观察到的是二进制序列化格式本身的开销。此格式包含标题,描述对象图的信息,描述数组的信息,描述类型和汇编信息的字符串等。也就是说,它包含BinaryFormatter.Deserialize的足够信息,可以返回一个数组Foo就像你期望的那样。

有关详细信息,请参阅以下详细说明格式的规范:http://msdn.microsoft.com/en-us/library/cc236844(PROT.10).aspx

根据您更新的问题进行修改:

如果您希望简单地将结构的内容写入流中,这可以在不安全的上下文中轻松完成(此代码基于您的示例)。

使用小数组写出每个Foo:

unsafe 
{
    byte[] data = new byte[sizeof(Foo)];

    fixed (Foo* ptr = arr)
    {
        for (int i = 0; i < arr.Length; ++i)
        {
            Marshal.Copy((IntPtr)ptr + i, data, 0, data.Length);
            f.Write(data, 0, data.Length);
        }
    }
}

或者使用一个足够大的数组来写出所有Foos:

unsafe 
{
    byte[] data = new byte[sizeof(Foo) * arr.Length];

    fixed (Foo* ptr = arr)
    {
        Marshal.Copy((IntPtr)ptr, data, 0, data.Length);
        f.Write(data, 0, data.Length);
    }
}

根据您的示例,这将写出1000个字节,每个值为42。

然而,这种方法有一些缺点。如果您熟悉用C语言编写结构,其中一些应该是显而易见的:

  • 如果您在具有与用于写入数据的字节序名不同的字节序的计算机上读取数据,则无法获得所需的结果。您需要自己定义一个预期的字节顺序并处理从该顺序转换到的顺序。
  • Foo不能包含作为引用类型的字段。这意味着你需要使用char字段的长度字段+固定大小的缓冲区而不是System.String;这可能是一种真正的痛苦。
  • 如果Foo包含指针类型或IntPtr / UIntPtr,则结构的大小可能因机器架构而异。如果可能的话,您可能希望避免使用这些类型。
  • 您需要应用自己的版本控制方案,以便您可以对回读的数据与预期的结构定义匹配有一定程度的信心。对结构布局的任何更改都需要新版本。

BinaryFormatter 可以为您解决这些问题,但会产生您在执行此操作时观察到的空间开销。它旨在以安全的方式在机器之间交换数据。如果您不想使用 BinaryFormatter ,那么您需要自己定义文件格式并自行处理这种格式的读写,或使用最适合您需求的第三方序列化库(我会把这些图书馆的研究留给你)。

答案 1 :(得分:1)

如果您想测量消耗的内存量,可以使用此类代码:

long nTotalMem1 = System.GC.GetTotalMemory(true);
Foo[] arr = new Foo[1000];
for (int i = 0; i < 1000; i++)
{
    arr[i] = new Foo(42);
}
long nTotalMem2 = System.GC.GetTotalMemory(true);
Console.WriteLine("Memory consumption: " + (nTotalMem2 - nTotalMem1) + " bytes");

剧透:1012字节。 :)

编辑:可能更可靠的方法是使用Marshal.SizeOf方法:

Console.WriteLine("Size of one instance: " + Marshal.SizeOf(arr[0]) + " bytes");

这为我返回了1个字节的结果,当向结构添加另一个字段时,它返回2个字节,因此看起来非常可靠。