代码之家  ›  专栏  ›  技术社区  ›  PVitt

如何减小结构尺寸

  •  1
  • PVitt  · 技术社区  · 16 年前

    我有一个包含几个结构的类库,每个结构都由几个值和引用类型组成。大多数值类型都是必需的,一些值类型和所有引用类型都是可选的。所有结构都是 XmlSerializable (这是强制性的)。

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

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

    [编辑]

    请原谅这个坏问题。我想我必须澄清一些事情,并更具体地说:

    类库设计用于序列化数据信息 GPX (GPS Exchange Format) . 这些结构如航路点或航迹。它们具有纬度、经度等强制字段。可选字段为垂直/水平/位置精度稀释、描述、链接。

    图书馆主要面向移动设备,如 PDA S.RAM很短,但有大量的非易失性内存可用。

    如果没有代码示例,则无法显示代码示例。在开始实现之前,我想考虑几个陷阱。

    6 回复  |  直到 15 年前
        1
  •  8
  •   ShuggyCoUk    16 年前

    这是一种在允许XML序列化的同时大幅减少内存开销的技术。
    更新:Orignal内联链接列表的思想对于1和2个条目比带有count构造的标准列表更有效,但是对于0、1和2个案例使用固定大小选项更有效。

    附加条款:

    这是建立在你知道你确实需要刮去记忆的基础上的。 (因为你还没有做任何编码)这可能是一个严重的过早 优化。

    此外,此设计基于以下可选字段: 非常 稀有。

    我使用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类的iopational字段中。

    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;
      }
    }   
    

    很多人都可以写得比这更好,但它给了你一个想法。 有了受限类型支持,您可以对每个类型“heap”执行全局键-值映射,如下所示:

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

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

    不管内部如何,这一点的主要好处是,waypoint类的公共表面完全隐藏了所有这些内容。

    然后,您可以通过属性IXMLSerializable(这将消除对烦人的指定的XXXX属性的需要)来设置想要序列化的类。

    在我的示例中,为了简单起见,我使用字符串作为ID。
    如果你 真的? 关心大小和速度,您应该将ID更改为枚举。考虑到打包行为,即使您可以将所有需要的值都放入一个字节中,这也不会为您节省太多,但它将为您提供编译时的健全性检查。这些字符串都是编译时常量,因此几乎不占用任何空间(但检查相等性时要慢一些)。

    我劝你只做这样的事 之后 你检查一下是否需要。另一方面,这并不限制您的XML序列化,因此您可以将其建模为您想要的任何格式。此外,“数据包”的公共面也可以保持干净(除了指定的XXXX垃圾)。

    如果您想避免XXXX指定的麻烦,并且知道您有一些“带外”值,可以使用以下技巧:

    [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形式是相同的,这意味着它们可以简单而容易地进行互操作,但是在这些情况下,内存的使用会少得多。

    如果这些组的数量变大,您甚至可以考虑一个代码生成场景(即使在运行时,这会大大增加您的支持负担)

        2
  •  3
  •   Stephan Eggermont    16 年前

    为了好玩: 应用flyweight并将所有实例存储在位图中?对于小型内存设备,您不需要4字节指针。

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

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

        3
  •  2
  •   Ruben    16 年前

    很可能很好地知道xmlserializer不关心您的内部对象布局,它只关心您的公共字段。 和属性 . 您可以在属性访问器后面隐藏内部内存优化,而xmlserializer甚至不知道。

    例如,如果您知道通常只有两个引用集,但有时更多,您可以将这两个频繁的引用集存储为主要对象的一部分,并将不频繁的引用集隐藏在对象[]或listDictionary或自己创建的专用私有类中。但是,请注意,每个间接容器对象也包含开销,因为它需要是引用类型。或者当您有8个可以为空的整数作为公共契约的一部分时,您可以在内部使用8个常规整数和一个包含is this int-null状态的单字节作为其位。

    如果您想进一步专门化,可能需要根据可用的数据创建专门化的子类,那么您必须走IXML可序列化的路线,但通常这不是真正需要的。

        4
  •  2
  •   Matt Brunell    16 年前

    你可以做一些事情:

    • 确保对特定值使用尽可能小的类型。例如,如果查看架构,dgpstationType的最小值为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序列化。

        5
  •  0
  •   Mikael Svenson    16 年前

    构建您自己的序列化,以最小化您的结构。并序列化为二进制而不是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.
    
    }
    
        6
  •  0
  •   Larsenal    16 年前

    某种二进制序列化通常比XML序列化做得更好。您将不得不为您的特定数据结构尝试它,看看您是否获得了很多。

    退房 MSDN 使用BinaryFormatter的示例。