2009-09-04 167 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序列化。