.NET小类多实例优化场景

本文关键字:优化 实例 小类 NET | 更新日期: 2023-09-27 18:20:02

我有数百万个类Data实例,我寻求优化建议。

有没有一种方法可以以任何方式优化它——例如通过以某种方式序列化它来节省内存,尽管这会影响检索速度,这也很重要。也许将类转换为struct,但似乎该类对于struct来说相当大。

对这些对象的查询一次可能会占用数亿个这样的对象。它们位于一个列表中,并按DateTime进行查询。结果以不同的方式汇总,可以应用许多计算。

[Serializable]
[DataContract]
public abstract class BaseData {}
[Serializable]
public class Data : BaseData {
    public byte     member1;
    public int      member2;
    public long     member3;
    public double   member4;
    public DateTime member5;
}

.NET小类多实例优化场景

不幸的是,虽然您确实指定了要"优化",但没有指定要解决的确切问题。因此,我真的不能给你更多的一般性建议。

序列化对您没有帮助。您的Data对象已作为字节存储在内存中。把它变成一个结构也无济于事。结构和类之间的区别在于它们的寻址和引用行为,而不是内存占用。

我能想到的减少拥有"数亿"这些对象的集合的内存占用的唯一方法是序列化并压缩整个对象。但这是不可行的。在访问之前,你总是必须对整个数据进行解压缩,这会让你的性能一落千丈,而且实际上访问时的内存消耗几乎是原来的两倍(压缩和解压缩的数据都在内存中)。

我能给你的最好的一般建议是不要自己尝试优化这个场景,而是使用专门的软件。我所说的专用软件是指(内存中的)数据库。可以在内存中使用的数据库的一个例子是SQLite,您已经在.NET框架中拥有了所需的一切。

正如您所暗示的,我假设您有一个具有许多成员的类,有大量实例,并且需要将它们同时保存在内存中以执行计算。

我进行了一些测试,看看你是否真的可以为你描述的类获得不同的大小。

我使用了一个简单的方法来查找对象的内存大小:

private static void MeasureMemory()
{
    int size = 10000000;
    object[] array = new object[size];
    long before = GC.GetTotalMemory(true);
    for (int i = 0; i < size; i++)            
    {
        array[i] = new Data();
    }
    long after = GC.GetTotalMemory(true);
    double diff = after - before;
    Console.WriteLine("Total bytes: " + diff);
    Console.WriteLine("Bytes per object: " + diff / size);
}

它可能很原始,但我发现它适用于这种情况。

正如预期的那样,对该类所做的任何事情(将其转换为结构、删除继承或方法属性)都不会影响单个实例所使用的内存。就内存使用而言,它们都是等效的。然而,一定要尝试篡改实际的类,并通过给定的代码运行它们。

真正减少实例内存占用的唯一方法是使用较小的结构来保存数据(例如int而不是long)。如果有大量布尔值,可以将它们分组为一个字节或整数,并使用简单的属性包装器来处理它们(布尔值占用一个字节的内存)。在大多数情况下,这些可能是无关紧要的事情,但对于一亿个对象来说,删除布尔值可能会产生100MB的内存。此外,请注意,为应用程序选择的平台目标可能会对对象的内存占用产生影响(x64构建比x86构建占用更多内存)。

序列化数据不太可能有帮助。内存中的数据库有它的优点,尤其是在进行复杂查询时。然而,实际上不太可能减少数据的内存使用量。不幸的是,减少基本数据类型占用空间的方法并不多。在某个时刻,您只需要移动到基于文件的数据库。

然而,这里有一些想法。请注意,它们很粗糙,条件很强,会降低计算性能,并使代码更难维护。

  1. 在大型数据结构中,通常情况下,处于不同状态的对象将只填充一些属性,而另一个属性将设置为null或默认值。如果你能识别这样的属性组,也许你可以把它们移到一个子类中,并有一个可能为null的引用,而不是让几个属性占用空间。然后,只在需要时实例化子类。您可以编写属性包装器,将其隐藏在代码的其余部分之外。请记住,最坏的情况是将所有属性都保留在内存中,再加上几个对象标头和指针。

  2. 您也许可以将可能采用默认值的成员转换为二进制表示,然后将它们打包到字节数组中。您将知道哪些字节位置代表哪个数据成员,并且可以编写可以读取它们的属性。将最有可能具有默认值的属性放在字节数组的末尾(例如,几个长度通常为0)。然后,在创建对象时,调整字节数组大小以排除具有默认值的属性,从列表的末尾开始,直到找到第一个具有非默认值的成员。当外部代码请求一个属性时,可以检查字节数组是否足够大以容纳该属性,如果不足够大,则返回默认值。这样,您可能会节省一些空间。在最好的情况下,您将有一个指向字节数组的空指针,而不是几个数据成员。最坏的情况是,全字节数组占用的空间与原始数据一样多,再加上数组的一些开销。有用性取决于实际数据,并假设您的写入次数相对较少,因为数组的重新计算将非常昂贵。

希望这些都有帮助:)