【问题标题】:Optimizing memory footprint of a .NET application with a huge number of instances优化具有大量实例的 .NET 应用程序的内存占用
【发布时间】:2010-07-17 09:57:37
【问题描述】:

出于性能原因,我有一个应用程序将大量实例保存在内存中,我不想将其写入磁盘或任何其他地方,只需将其全部保存在内存中.

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 倍的字符串使用的内存。

【问题讨论】:

  • 描述有点模糊。您能否更详细地解释您的方案是什么?你的应用程序在做什么?为什么需要使用这么多内存?
  • 所有对象都是唯一的还是有很多重复项(包含相同数据的单独实例)?
  • @DxCK “名称”的值是唯一的,还是有很多重复的实例? “姓名”代表什么?
  • 顺便说一句。字符串在 .NET 中实习,en.wikipedia.org/wiki/String_interning
  • @Chris 如果它们是在运行时创建的,则不会。

标签: c# .net performance optimization memory


【解决方案1】:

想法:

  • 是否有任何字符串共享?您可以在加载数据时使用自定义内部人员,以确保不会重复这些内部人员(注意:不要使用 inbuilt 内部人员,因为您会使其饱和;即使是 Dictonary<string,string> 也可以)
  • 是否有任何其他常见的元素明智地可能是重复的,并且可能被移动到对象中?您仍然有参考字段的成本,但希望这(和新对象)是净收益
  • 如果您有大量类似的实体,是否可以将它们中的任何一个建模为 不可变 值类型?这通常不是我的首选,但一个优点是您可以将它们放在一个数组中:
    • 您可以获得每个实体的对象引用和对象标头的价格
    • 您可以使用数组中的偏移量 (int) 而不是引用;对于 64 位,加起来可以节省很多
  • 您似乎建议使用稀疏对象方法;确实你想避免笛卡尔积,但同时你描述的成员数量很少一个属性包可能会更多在内存中昂贵;另外,既然你提到你这样做是为了提高性能,我怀疑它也会伤害 CPU
  • DateTimes - 他们(例如)总是整天吗?您会惊讶于仅使用 int 进入一个纪元的天数可以获得什么

【讨论】:

    【解决方案2】:

    通过查看您的个人资料,我要赌一把,猜测“名称”属性实际上是一个文件路径。如果空间比时间更重要,那么您可以使用编码方案来表示路径,其中可能存在大量重复数据。

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

    【讨论】:

      【解决方案3】:

      以 UTF8 存储字符串:

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

      (编辑:起初以为 op 想要 ASCII)

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

      因此,您将拥有而不是 MyObject[] 对象

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

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

      想法 2: 如果您确信这些 DateTimes 在相当小的范围内(大约 30 年),例如 2010 年 1 月 1 日,您可以将其转换为 32 位 int 值,表示自该日期以来/之前的秒数。这将在每个 DateTime 中减少 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
      

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

      【讨论】:

      • 关于“将字符串存储为 ASCII”——OP 实际上希望将它们存储为 UTF-8 而不是默认的 UTF-16。
      【解决方案4】:

      使用System.Tuple 怎么样? 您可以动态指定要使用的字段。

      编辑:
      我肯定会调查String interning。

      还有System.Dynamic.ExpandoObject

      【讨论】:

      • 我需要一个标准接口来访问所有字段。元组可以提供吗?
      • 定义标准接口的含义?
      • 请参阅问题中的接口 IMyObject。我想要一个数组,IMyObject[]
      • 不,它不适用于元组。此外,元组是只读的,一旦创建。
      • 只读就足够了,但问题是我需要将它们全部放入一个数组中,我不想测试类型并为每次访问进行强制转换。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-24
      • 1970-01-01
      • 1970-01-01
      • 2010-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多