繁体   English   中英

关于c#struct memory / serialization overhead

[英]About c# struct memory/serialization overhead

我的代码是这样的:

[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进行存档。 但序列化开销是一个意想不到的障碍。 一个更好的序列化解决方案将是好的,否则我将以纯字符串格式保存信息并通过硬编码解析它。

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 ,那么您需要定义自己的文件格式并自行处理这种格式的读写,或使用最适合您需求的第三方序列化库(我将离开研究)这些图书馆由您决定)。

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

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个字节,因此它看起来非常可靠。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM