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

时间:2010-07-17 09:57:37

标签: c# .net performance optimization memory

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

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 个答案:

答案 0 :(得分:2)

思想:

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

以UTF8存储字符串:

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

string text = System.Text.Encoding.UTF8.GetString(asciiStr);

(编辑:思想操作首先想要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&gt;在外面,它包含对标签的实际引用。

答案 3 :(得分:0)

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

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

此外,还有System.Dynamic.ExpandoObject