c# - 如何减小结构尺寸

标签 c# .net data-structures

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

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

是否有任何其他方法可以“缩小”结构?

[编辑]

请原谅这个糟糕的问题。我想我必须澄清一些事情并变得更具体:

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

该库主要针对移动设备,例如 PDA秒。 RAM 很短,但有足够的非 volatile 内存可用。

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

最佳答案

这是一种在允许 Xml 序列化的同时积极减少内存开销的技术。
更新:原始内联链表思想对于 1 和 2 条目比具有计数结构的标准列表更有效,但对于零、一和二情况使用固定大小的可选值甚至更有效。

附带条件:

This is predicated on you knowing that you really do need to shave the memory, as such (since you haven't done any coding yet) this may well be a massively premature optimization.

此外,此设计的前提是可选字段非常很少见。

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

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 可以写得比这更好,但它给了你想法。 使用受限类型支持,您可以像这样为每个类型“堆”执行全局键 -> 值映射:

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

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

不管内部结构如何,这样做的主要好处是 WayPoint 类的公共(public)表面完全隐藏了所有这些。

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

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

我强烈建议您只在确认需要之后才做这样的事情。好的一面是,这不会限制您的 xml 序列化,因此您可以将其塑造成您想要的任何格式。也可以保持“数据包”的公共(public)面孔干净(xxxSpecified junk 除外)。

如果您想避免 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 格式相同,这意味着它们可以简单轻松地进行互操作,但在这些情况下内存使用量会少得多。

如果这些组的数量变大,您甚至可以考虑代码生成方案(甚至在运行时,尽管这会大大增加您的支持负担)

关于c# - 如何减小结构尺寸,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1381465/

相关文章:

c# - 复杂的 C# Linq 算法

c# - asp.net 中的文件处理程序

c# - 如何连接.Net Webservice 与本地数据库

python - 使用 for 循环在 PIL 上打开多个图像

c# - 通过重定向进行基本身份验证

c# - MVVM没有收到消息

.net - 如何提高 .NET 中的 WMI 性能?

c# - 网络核心 : Swashbuckle Set operationId Automatically on to Controller Action Method

javascript - 我将如何通过嵌套函数将元素作为参数传递?

arrays - 使用给定的字典从一个字符串到达​​另一个字符串