2009-09-04 122 views
1

我有一个类库包含几个结构,每个结构由多个值和引用类型组成。大多数值类型是强制性的,一些值类型和所有参考类型是可选的。所有结构都是XmlSerializable(这是强制性的)。如何减小结构尺寸

只要类库定位到移动设备,我想减少内存占用。我的第一个想法是使用Nullable<T>作为值类型,但是这会使内存大小增加4个字节,每个Nullable<T>。我的第二个想法是将所有可选的值类型打包到一个单独的结构中,该结构仅在需要其任何成员时才实例化。但是这会迫使我在“主要”结构上执行IXmlSerializable

是否有其他方法来“收缩”结构?

[编辑]

请原谅这个不好的问题。我想我必须澄清一些事情,并得到更具体的:

该类库旨在序列化数据信息GPX (GPS Exchange Format)。结构例如是航点或轨道。它们具有纬度,经度等强制性字段。可选字段为垂直/水平/位置精度稀释,描述和链接。

该库主要针对移动设备,如PDA s。 RAM很短,但有很多非易失性存储器可用。

只要没有代码示例,就不能显示代码示例。我想在开始执行之前考虑几个陷阱。

+0

为什么实施IXmlSerializable是一个坏主意?选项结构分离的想法听起来很吸引我。 – djna 2009-09-04 20:57:02

+0

所以,你想减少开销,但同时你想保存你的数据为XML? :-) – 2009-09-04 20:58:21

+0

请不要误解我的意思。我并不是说实现IXmlSerializable是个坏主意。我想知道这是不是一个好主意。 一些更多的信息:我们正在谈论一个运行在普通Windows Mobile PDA上的gps记录器,并且能够显示瓷砖地图。我们的RAM很短,但对于xml文件,我们有1到16 GB的SD卡。请帮我一个忙,不要质疑这个问题。 – PVitt 2009-09-04 21:24:53

回答

8

这是一种在允许Xml序列化的同时大量​​减少内存开销的技术。
更新:orignal内联链表思路对于1和2条目比使用count构造的标准列表更高效,但对于零个,一个和两个情况使用固定大小的optionals更加高效。

限制性条款:

这个的前提是你知道你真的需要刮胡子的内存,这样 (因为你没有做任何编码还)这很可能是一个大规模过早 优化。

此外,这种设计是可选字段为非常难得

我使用double作为“占位符”,无论哪种格式最好允许您表示应使用的精度/单位。

public class WayPoint 
{ 
    // consumes IntPtr.Size fixed cost 
    private IOptional optional = OptionalNone.Default; 

    public double Latitude { get; set; } 
    public double Longitude { get; set; } 

    public double Vertical 
    { 
    get { return optional.Get<double>("Vertical") ?? 0.0; } 
    set { optional = optional.Set<double>("Vertical", value); } 
    } 

    [XmlIgnore] // need this pair for every value type 
    public bool VerticalSpecified 
    { 
    get { return optional.Get<double>("Vertical").HasValue; } 
    }   
    public void ClearVertical() 
    { 
    optional = optional.Clear<double>("Vertical"); 
    } 

    public string Description // setting to null clears it 
    { 
    get { return optional.GetRef<string>("Description"); } 
    set { optional = optional.SetRef<string>("Description", value); } 
    } 

    // Horizontal, Position, DilutionOfPrecision etc. 
} 

真正的繁重都是在这里完成:

internal interface IOptional 
{ 
    T? Get<T>(string id) where T : struct; 
    T GetRef<T>(string id) where T : class; 

    IOptional Set<T>(string id, T value); 
    IOptional Clear(string id); 
} 

internal sealed class OptionalNone : IOptional 
{ 
    public static readonly OptionalNone Default = new OptionalNone(); 

    public T? Get<T>(string id) where T : struct 
    { 
    return null; 
    } 

    public T GetRef<T>(string id) where T : class 
    { 
    return null; 
    } 

    public IOptional Set<T>(string id, T value) 
    { 
    if (value == null) 
     return Clear(id); 
    return new OptionalWithOne<T>(id, value); 
    } 

    public IOptional Clear(string id) 
    { 
    return this; // no effect 
    } 
} 

固定大小的人变得更有趣写的,没有一点写这些的结构,因为它们将被装箱放置内WayPoint类中的可选字段。

internal sealed class OptionalWithOne<X> : IOptional 
{ 
    private string id1; 
    private X value1; 

    public OptionalWithOne(string id, X value) 
    { 
    this.id1 = id; 
    this.value1 = value; 
    } 

    public T? Get<T>(string id) where T : struct 
    { 
    if (string.Equals(id, this.id1)) 
     return (T)(object)this.value1; 
    return null; 
    } 

    public T GetRef<T>(string id) where T : class   
    { 
    if (string.Equals(id, this.id1)) 
     return (T)(object)this.value1; 
    return null; 
    } 

    public IOptional Set<T>(string id, T value) 
    { 
    if (string.Equals(id, this.id1)) 
    { 
     if (value == null) 
     return OptionalNone.Default; 
     this.value1 = (X)(object)value; 
     return this; 
    } 
    else 
    { 
     if (value == null) 
     return this; 
     return new OptionalWithTwo<X,T>(this.id1, this.value1, id, value); 
    } 
    } 

    public IOptional Clear(string id) 
    { 
    if (string.Equals(id, this.id1)) 
     return OptionalNone.Default; 
    return this; // no effect 
    } 
} 

然后两(你可以尽可能你想扩展这个想法,但你可以看到代码很快变得不愉快。

internal sealed class OptionalWithTwo<X,Y> : IOptional 
{ 
    private string id1; 
    private X value1; 
    private string id2; 
    private Y value2; 

    public OptionalWithTwo(
    string id1, X value1, 
    string id2, Y value2) 
    { 
    this.id1 = id1; 
    this.value1 = value1; 
    this.id2 = id2; 
    this.value2 = value2; 
    } 

    public T? Get<T>(string id) where T : struct 
    { 
    if (string.Equals(id, this.id1)) 
     return (T)(object)this.value1; 
    if (string.Equals(id, this.id2)) 
     return (T)(object)this.value2; 
    return null; 
    } 

    public T GetRef<T>(string id) where T : class   
    { 
    if (string.Equals(id, this.id1)) 
     return (T)(object)this.value1; 
    if (string.Equals(id, this.id2)) 
     return (T)(object)this.value2; 
    return null; 
    } 

    public IOptional Set<T>(string id, T value) 
    { 
    if (string.Equals(id, this.id1)) 
    { 
     if (value == null) 
     return Clear(id); 
     this.value1 = (X)(object)value; 
     return this; 
    } 
    else if (string.Equals(id, this.id2)) 
    { 
     if (value == null) 
     return Clear(id); 
     this.value2 = (Y)(object)value; 
     return this; 
    } 
    else 
    { 
     if (value == null) 
     return this; 
     return new OptionalWithMany(
     this.id1, this.value1, 
     this.id2, this.value2, 
     id, value); 
    } 
    } 

    public IOptional Clear(string id) 
    { 
    if (string.Equals(id, this.id1)) 
     return new OptionalWithOne<Y>(this.id2, this.value2); 
    if (string.Equals(id, this.id2)) 
     return new OptionalWithOne<X>(this.id1, this.value1); 
    return this; // no effect 
    } 
} 

之前最后与相对低效

internal sealed class OptionalWithMany : IOptional 
{ 
    private List<string> ids = new List<string>(); 
    // this boxes, if you had a restricted set of data types 
    // you could do a per type list and map between them 
    // it is assumed that this is sufficiently uncommon that you don't care 
    private List<object> values = new List<object>(); 

    public OptionalWithMany(
    string id1, object value1, 
    string id2, object value2, 
    string id3, object value3) 
    { 
    this.ids.Add(id1); 
    this.values.Add(value1); 
    this.ids.Add(id2); 
    this.values.Add(value2); 
    this.ids.Add(id3); 
    this.values.Add(value3); 
    } 

    public T? Get<T>(string id) where T : struct 
    { 
    for (int i= 0; i < this.values.Count;i++) 
    { 
     if (string.Equals(id, this.ids[i])) 
     return (T)this.values[i]; 
    } 
    return null; 
    } 

    public T GetRef<T>(string id) where T : class   
    { 
    for (int i= 0; i < this.values.Count;i++) 
    { 
     if (string.Equals(id, this.ids[i])) 
     return (T)this.values[i]; 
    } 
    return null; 
    } 

    public IOptional Set<T>(string id, T value) 
    { 
    for (int i= 0; i < this.values.Count;i++) 
    { 
     if (string.Equals(id, this.ids[i])) 
     { 
     if (value == null) 
      return Clear(id);   
     this.values[i] = value; 
     return this; 
     } 
    } 
    if (value != null) 
    { 
     this.ids.Add(id); 
     this.values.Add(value); 
    } 
    return this; 
    } 

    public IOptional Clear(string id) 
    { 
    for (int i= 0; i < this.values.Count;i++) 
    { 
     if (string.Equals(id, this.ids[i])) 
     { 
     this.ids.RemoveAt(i); 
     this.values.RemoveAt(i); 
     return ShrinkIfNeeded(); 
     } 
    } 
    return this; // no effect 
    } 

    private IOptional ShrinkIfNeeded() 
    { 
    if (this.ids.Count == 2) 
    { 
     //return new OptionalWithTwo<X,Y>(
     // this.ids[0], this.values[0], 
     // this.ids[1], this.values[1]); 
     return (IOptional) 
     typeof(OptionalWithTwo<,>).MakeGenericType(
      // this is a bit risky. 
      // your value types may not use inhertence 
      this.values[0].GetType(), 
      this.values[1].GetType()) 
     .GetConstructors().First().Invoke(
      new object[] 
      { 
      this.ids[0], this.values[0], 
      this.ids[1], this.values[1] 
      }); 
    } 
    return this; 
    } 
} 
结束

OptionalWithMany可以写得比这更好,但它给了你想法。 有了受限制的类型支持,你可以做一个全局的Key - > value map类型如下:

internal struct Key 
{ 
    public readonly OptionalWithMany; 
    public readonly string Id; 
    // define equality and hashcode as per usual 
}  

然后,只需在OptionalToMany中存储当前正在使用的Id的列表。收缩会稍微复杂一点(但从类型的角度来看更好,因为您会扫描每个全局'堆',直到找到匹配项并使用堆的类型构造OptionalWithTwo。这将允许属性值中的多态性。

无论内部结构如何,WayPoint类的公共表面都完全隐藏了这一切。

然后,您可以通过属性IXmlSerializable(这将消除恼人的xxxSpecified属性的需要)设置类,但是您想要序列化。

我在示例中使用了字符串作为Id。
如果你真的关心大小和速度,你应该改变ID为枚举。考虑到打包行为,即使你可以将所有需要的值放入一个字节中,这也不会节省你很多,但它会给你编译时间的完整性检查。这些字符串都是编译时常量,所以占用空间的旁边(但检查相等性较慢)。

我强烈建议你在之后只做这样的事情,你检查是否需要。好的一面是,这不会限制你的xml序列化,所以你可以将它塑造成你想要的任何格式。此外,“数据包”的公开表面可以保持清洁(xxxSpecified垃圾除外)。

如果你想避免xxxSpecified麻烦,你知道你有一些“带外”的值,可以使用下面的技巧:

[DefaultValue(double.MaxValue)] 
public double Vertical 
{ 
    get { return optional.Get<double>("Vertical") ?? double.MaxValue; } 
    set { optional = optional.Set<double>("Vertical", value); } 
} 

public void ClearVertical() 
{ 
    optional = optional.ClearValue<double>("Vertical"); 
} 

但是您API的其余部分必须能够检测这些特殊的价值。一般来说,我会说指定的路线更好。

如果一组特定的属性在某些设备上变为“始终可用”,或者在某些模式下,您应该切换到替代类,其中的属性是简单类。由于xml表单将是相同的,这意味着它们可以简单方便地进行互操作,但在这些情况下的内存使用量会少得多。

如果这些群体的数量变大,你甚至可以考虑代码生成方案(在运行时甚至,虽然这大大增加您的支持负担)

+1

会让-1喜欢说为什么? – ShuggyCoUk 2009-09-06 10:12:51

+0

-1你没有试图衡量这一点,是吗?它为这些可选属性分配了太多的对象。它需要太多的内存。 – 2009-09-06 10:15:48

+0

在航点,你知道你有多少可选项。更好的设计是分配一个足够大的块来保存所有可选值。当你需要更大或更小的区块时重新分配。 – 2009-09-06 10:31:02

3

对于一些严重的乐趣: 申请Flyweight并将所有实例存储在位图中?使用小型存储设备时,您不需要4个字节的指针。

[编辑]使用Flyweight,你可以为每个领域有一个单独的存储策略。我不建议直接将字符串值存储在位图中,但可以存储索引。

该类型未存储在位图中,而是存储在唯一对象工厂中。

+0

如何在您的位图模型中指出存储数据的类型?你一直在解析块吗?使用你描述的模式是可行的,但是很棘手。例如存储字符串。 – ShuggyCoUk 2009-09-06 13:15:37

+0

我把你的想法合并到WithOne/WithTwo等等中,并制作成社区维基。如果你觉得它可以进一步得到改善,感觉自由。 – ShuggyCoUk 2009-09-07 09:03:47

0

某种二进制序列化通常比XML序列化要好得多。你必须尝试一下你的具体数据结构,看看你是否获得了很多。

检出MSDN一个使用BinaryFormatter的例子。

+0

XmlSerialization是此类库的唯一用途 - – PVitt 2009-09-04 21:31:42

+0

感谢您澄清。 – Larsenal 2009-09-04 21:35:07

0

构建您自己的序列化以最小化您的结构。并序列化为二进制而不是xml。

线沿线的东西:

internal void Save(BinaryWriter w) 
{ 
    w.Write(this.id); 
    w.Write(this.name); 
    byte[] bytes = Encoding.UTF8.GetBytes(this.MyString); 
    w.Write(bytes.Length); 
    w.Write(bytes); 

    w.Write(this.tags.Count); // nested struct/class   
    foreach (Tag tag in this.tags) 
    { 
     tag.Save(w); 
    } 
} 

,并有它建立它备份构造

public MyClass(BinaryReader reader) 
{ 
    this.id = reader.ReadUInt32(); 
    etc. 

} 
2

这可能是很好的了解,XmlSerializer的不关心你的内部对象布局,它只关心你的公共领域和属性。您可以隐藏属性访问器后面的内部内存优化,并且XmlSerializer甚至不知道。例如,如果你知道你通常只设置了2个引用,但偶尔更多,你可以将这两个频繁的元素作为主对象的一部分存储起来,并将不经常的元素隐藏在一个对象[]或者ListDictionary中或者你自己制作的专门的私人课程。但是,请注意,每个间接容器对象也包含开销,因为它需要是引用类型。或者当你有8个可为空的整数作为你的公共约定的一部分时,你可以在内部使用8个常量整数和一个包含is-this-int-null状态作为其位的单个字节。

如果你想进一步专注,也许根据可用的数据创建专门的子类,你将不得不去IXmlSerializable的路线,但通常不是那么需要。

2

你可以做两件事情:

  • 确保为特定值使用可能的最小类型。例如,如果查看模式,dgpsStationType的最小值为0,最大值为1023.这可以存储为ushort。尽可能减小这些物品的尺寸。

  • 确保您的字段是4字节对齐的。结构的最终结果大小将为4字节的几倍(假设为32位)。一个类的默认布局具有顺序存储的项目。如果字段未正确打包,编译器将浪费空间,确保字段是4字节对齐的。您可以使用StructLayoutAttribute明确指定布局。

错误示例:类中的这些字段占用12个字节。 int必须占用4个连续字节,而其他成员必须是4字节对齐的。

public class Bad { 
    byte a; 
    byte b; 
    int c; 
    ushort u; 
} 

更好的例子:一个类中的这些字段占用8个字节。这些字段被高效打包。

public class Better { 
    byte a; 
    byte b; 
    ushort u; 
    int c; 
} 
  • 减少你的对象图的大小。每个引用类型占用8个字节的开销。如果你有一个深刻的图表,这是一个很大的开销。把你所能做的所有事情都转换成对你的主要数据进行操作的函数。多想'C',少点OOD。

  • 它延迟加载一些可选参数仍然是一个好主意,但你应该清楚地画出你的线。创建1个或2个可以加载的“可选”值或空集。每个集合都将要求一个引用类型和它的开销。

  • 尽你所能使用结构。虽然小心值类型的语义,但它们可能会很棘手。

  • 考虑不实施ISerializable。接口方法根据定义是虚拟的。任何具有虚拟方法的类都包含对vtable的引用(另外4个字节)。而是在外部类中手动实现xml序列化。