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

优化具有大量实例的.NET应用程序的内存占用

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

    由于性能原因,我有一个在内存中保存大量实例的应用程序,而且我 不要 想把它写到磁盘或其他地方,只需把它全部保存在内存中。

    public class MyObject
    {
        public string Name;
        public object Tag;
        public DateTime DateTime1;
        public DateTime DateTime2;
        public DateTime DateTime3;
        public long Num1;
        public uint Num2;
        public uint Num3;
        public ushort Num4;
    }
    

    在许多情况下,我实际上并没有使用所有字段,或者没有利用字段的整体大小。所以我想也许可以将整个类转移到一个具有属性的接口中,并使许多实现类以不同的方式存储数据:使用较小的字段(例如int而不是long)并省略一些未使用的字段。

    例子:

    public interface IMyObject
    {
        string Name { get; set; }
        object Tag { get; set; }
        DateTime DateTime1 { get; set; }
        DateTime DateTime2 { get; set; }
        DateTime DateTime3 { get; set; }
        long Num1 { get; set; }
        uint Num2 { get; set; }
        uint Num3 { get; set; }
        ushort Num4 { get; set; }
    }
    
    public class MyObject1 : IMyObject
    {
        public string Name { get; set; }
        public object Tag { get; set; }
        public DateTime DateTime1 { get; set; }
        public DateTime DateTime2 { get; set; }
        public DateTime DateTime3 { get; set; }
        public long Num1 { get; set; }
        public uint Num2 { get; set; }
        public uint Num3 { get; set; }
        public ushort Num4 { get; set; }
    }
    
    public class MyObject2 : IMyObject
    {
        private int _num1;
    
        public string Name { get; set; }
        public object Tag { get; set; }
        public DateTime DateTime1 { get; set; }
        public DateTime DateTime2 { get; set; }
        public DateTime DateTime3 { get; set; }
        public long Num1
        {
            get { return _num1; }
            set { _num1 = (int)value; }
        }
        public uint Num2 { get; set; }
        public uint Num3 { get; set; }
        public ushort Num4 { get; set; }
    }
    
    public class MyObject3 : IMyObject
    {
        public string Name { get; set; }
        public object Tag { get; set; }
        public DateTime DateTime1
        {
            get { return DateTime.MinValue; }
            set { throw new NotSupportedException(); }
        } 
        public DateTime DateTime2 { get; set; }
        public DateTime DateTime3 { get; set; }
        public long Num1 { get; set; }
        public uint Num2 { get; set; }
        public uint Num3 { get; set; }
        public ushort Num4 { get; set; }
    }
    
    // ...
    

    理论上,使用这种方法,我实际上可以减少内存占用,但实际上,正如您所看到的,这种方法的问题在于,它将导致所有情况下的笛卡尔积,具有更小和省略的字段,丑陋和大的代码,在将来编写之后无法维护。

    关于弦的另一个想法:

    .NET应用程序中以UTF-16编码表示的所有字符串。如果我能让它用UTF-8编码,它将减少x2倍的字符串使用的内存。

    4 回复  |  直到 16 年前
        1
  •  2
  •   Marc Gravell    16 年前

    思想:

    • 是否共享任何字符串?您可以在加载数据时使用自定义Interner,以确保这些数据都不重复(注意:不要使用 内置的 实习生,因为你会让它饱和;甚至 Dictonary<string,string> 会做)
    • 有没有其他共同的元素可以 明智地 可能是重复的,并且可能被移动到对象中?您仍然有参考字段的成本,但希望这个(和新对象)是净收益。
    • 如果你有大量相似的实体,它们中的任何一个都可以被建模为 不变的 值类型?这通常不是我的首选选项,但这样做的一个好处是,您可以将它们粘贴到一个数组中:
      • 获得对象引用和每个实体的对象标题的价格
      • 可以在数组中使用偏移量( int )而不是一个引用;对于64位,这是一个相当不错的节省加起来
    • 您似乎建议使用稀疏对象方法;实际上,您希望避免使用笛卡尔积,但同时 对于您描述的成员数量较少 一个财产袋可能是 更多 内存很贵;另外,既然您提到您这样做是为了提高性能,我怀疑这也会损害CPU。
    • 这个 DateTime 他们(例如)总是整天吗?你会惊讶地发现,仅仅使用 int 进入一个时代的天数
        2
  •  1
  •   Tim Lloyd    16 年前

    从你的个人资料来看,我会用双关语猜测“name”属性实际上是一个文件路径。如果空间比时间更重要,那么可以使用编码方案来表示路径,其中可能存在大量重复数据。

    将文件路径表示为一个int数组的路径,以及一个字符串和实际文件名的文件名(这可能更独特,因此不值得编码)。您可以将路径拆分为其组成部分,然后使用两个字典来存储正向和反向查找。通过这种方式,可以减少到整数数组的路径。比绳子小得多。

        3
  •  1
  •   Rei Miyasaka    16 年前

    以utf8格式存储字符串:

    byte[] asciiStr = System.Text.Encoding.UTF8.GetBytes("asdf");
    
    string text = System.Text.Encoding.UTF8.GetString(asciiStr);
    

    (编辑:一开始想用ASCII码)

    想法1 : 如果您希望大多数值在大多数时间内都不会被填充,那么您可以将每个字段存储在某种单独的键值查找数据结构中——字典、带二进制搜索的有序列表、二进制树等。使用二进制搜索的有序列表可能是最节省空间的,但是查找将是O(log n)。

    因此,您应该

    Dictionary<int, string> names; // or List<Tuple<int,string>> names;
    Dictionary<int, object> tags;
    Dictionary<int, DateTime> datetime1s;
    ...
    

    其中,每个值中的int键是一个条目的ID。

    想法2 : 如果您确信这些日期时间在一个相当小的范围内(大约30年),例如2010年1月1日,您可以将其转换为一个32位的int值,表示自该日期以来/之前的秒数。这将使每个日期时间减少4个字节。

    想法3 : 您可以考虑制定一个真正节省空间的序列化方案,其中每个字段的第一个字节指定类中后续字节所包含的哪个字段。字符串值可能只是用\n或其他东西分隔。将整个内容存储在一个字节数组中,并按需反序列化它。

    所以类似这样,没有空格,在适当的情况下使用二进制值:

    1 //indicates field 1 (Name)
    
    beck.asf\n //the value
    
    6 //indicates field 6 (Num1)
    
    3545623 //the value, in a 64-bit binary int
    

    如果标记引用了一个活动对象,您可能需要将其单独地抛出序列化之外的包装结构中。或者,就像第一个想法一样,您可以只存储一个int,标识标签,然后在外部有一个列表>,其中包含对标签的实际引用。

        4
  •  0
  •   Chris Bednarski    16 年前

    用什么呢 System.Tuple ? 可以动态指定要使用的字段。

    编辑:
    我肯定会调查 String 实习。

    此外,还有 System.Dynamic.ExpandoObject