0

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

有没有办法以任何方式优化它 - 例如通过以某种方式对其进行序列化来节省内存,尽管它会损害检索速度,这也很重要。也许将类转换为 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;
}
4

2 回答 2

2

不幸的是,虽然您确实指定了要“优化”,但您没有指定要解决的确切问题。所以我不能给你更多的一般建议。

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

我能想到的减少具有“数亿”这些对象的集合的内存占用的唯一方法是序列化和压缩整个事物。但这是不可行的。你总是必须在访问它之前解压缩整个东西,这会使你的性能下降,实际上访问时的内存消耗几乎翻了一番(压缩和解压缩的数据都在内存中)。

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

于 2013-06-27T09:59:07.760 回答
1

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

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

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

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)。如果您有大量布尔值,您可以将它们分组为一个字节或整数,并使用简单的属性包装器来处理它们(布尔值占用一个字节的内存)。在大多数情况下,这些可能是微不足道的事情,但对于一亿个对象,删除一个布尔值可能会产生 100 MB 的内存差异。此外,请注意,您为应用程序选择的平台目标可能会影响对象的内存占用(x64 构建占用的内存比 x86 构建的要多)。

序列化数据不太可能有帮助。内存数据库有其优点,尤其是在您进行复杂查询时。但是,实际上不太可能减少数据的内存使用量。不幸的是,没有很多方法可以减少基本数据类型的占用。在某些时候,您只需要移动到基于文件的数据库。

但是,这里有一些想法。请注意,它们是 hacky、高度条件化的,会降低计算性能并且会使代码更难维护。

  1. 在大型数据结构中经常出现这样的情况,不同状态的对象只会填充一些属性,而另一些会被设置为 null 或默认值。如果您可以识别此类属性组,也许您可​​以将它们移动到子类中,并拥有一个可能为空的引用,而不是让多个属性占用空间。然后,您只需在需要时实例化子类。您可以编写可以将其隐藏在其余代码中的属性包装器。请记住,这里最糟糕的情况是将所有属性以及多个对象头和指针保留在内存中。

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

希望这有帮助:)

于 2013-06-27T13:00:30.350 回答