减少相似对象的内存消耗

4

我希望减少类似于表格的集合对象的内存消耗。

假设有以下类结构:

Class Cell
{
    public property int Data;
    public property string Format;
}

Class Table
{
    public property Dictionary<Position, Cell> Cells;
}

当单元格数量很多时,Cell类的Data属性可能是可变的,但Format属性可能会重复多次,例如标题单元格可能具有空格式字符串以用作标题,而数据单元格可能都是“0.00”。

一个想法是采取以下措施:

Class Cell
{
    public property int Data;
    public property int FormatId;
}
Class Table
{
    public property Dictionary<Position, Cell> Cells;
    private property Dictionary<Position, string> Formats;

    public string GetCellFormat(Position);
}

这将节省字符串的内存占用,但是 FormatId 整数值仍然会被重复多次。

有没有比这更好的实现方式?我已经看过亨元模式,但不确定是否适用于此。

我正在考虑的一种更复杂的实现方式是完全从 Cell 类中移除 Format 属性,而是将格式存储在一个字典中,以便将相邻单元格组合在一起。
例如,可能会有两个如下所示的条目:
<item rowFrom=1 rowTo=1 format="" />
<item romFrom=2 rowTo=1000 format="0.00" />

3个回答

5

对于字符串,您可以考虑使用intern方式;可以使用内置的interner,或者(最好)使用自定义的interner - 基本上是一个Dictionary<string,string>。这意味着每个相同的字符串使用相同的引用 - 重复的字符串可以被收集。

不要对int进行任何操作; 这已经是最优的。

例如:

using System;
using System.Collections.Generic;
class StringInterner {
    private readonly Dictionary<string, string> lookup
        = new Dictionary<string, string>();
    public string this[string value] {
        get {
            if(value == null) return null;
            if(value == "") return string.Empty;
            string result;
            lock (lookup) { // remove if not needed to be thread-safe     
                if (!lookup.TryGetValue(value, out result)) {
                    lookup.Add(value, value);
                    result = value;
                }
            }
            return result;
        }
    }
    public void Clear() {
        lock (lookup) { lookup.Clear(); }
    }
}
static class Program {
    static void Main() {
        // this line is to defeat the inbuilt compiler interner
        char[] test = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };

        string a = new string(test), b = new string(test);
        Console.WriteLine(ReferenceEquals(a, b)); // false
        StringInterner cache = new StringInterner();
        string c = cache[a], d = cache[b];
        Console.WriteLine(ReferenceEquals(c, d)); // true
    }
}

如果需要,您可以进一步使用WeakReference

重要的是要注意,您不需要更改设计 - 您只需更改填充对象的代码以使用内部器/缓存即可。


谢谢。我想我需要确定CLR是否已经为我完成了这个任务。 - Chris Herring
文档指定CLR内部化字符串分配的内存不太可能在CLR本身终止之前被释放。这就是为什么您更喜欢自定义内部化程序吗? - Chris Herring
正确;通过拥有自己的缓存,我可以控制其生命周期。如果您的字符串与特定于域的数据相关(可能来自数据库),这一点非常重要。而清除它只需要让缓存被收集即可(现有的字符串使用不会受到影响)。 - Marc Gravell
请注意,CLR 仅在少数特定场景中(默认情况下)将字符串池化;例如,您源代码中的字符串文字是已池化的。 - Marc Gravell

4

您是否确定这实际上是一个问题?CLR会为您执行很多string interning,因此可能(取决于CLR版本和代码编译方式)您使用的内存并不像您想象的那样多。

在更改设计之前,强烈建议您验证对内存利用的怀疑。


谢谢,我之前不知道。起始示例是已有设计的简化版,它占用了大量内存,我认为其中的众多字符串是原因之一。很难确定具体的内存使用情况... - Chris Herring

0

正如其他人所提到的,您首先要确定这是否是一个问题,然后再更改您的设计等。如果这是一个问题和/或您正在处理大量的稀疏数据,则稀疏数据结构可能更适用于该问题。稍后我将发布一个非常简单的天真实现(因为我现在无法做到),但是二维稀疏矩阵将实现您所需的功能。

概念是给定单元格范围(例如1-1000)将存储单个格式(字符串或类)。要从中受益,您需要进行一些设计更改... 单元格类中的格式属性将需要被删除。相反,应该在表类或最好是另一个类中注册格式。例如

public class CellFormats 
{ ....
public void Register(int start, int finish, string format);
}

单元格格式类将包含稀疏矩阵,其中包含范围的格式。

表格类将利用单元格格式类。它不会有一个名为 GetCellFormat 的方法,而是会有一个带有以下签名的方法

void ApplyCellFormat(Position cellPosition)

这将从CellFormats类(稀疏矩阵)中检索单元格格式,并将其应用于单元格。

如果数据是常见的并且应用于大范围,这种技术可以显著减少内存使用。但正如我已经说过的,您需要确保这是问题的原因,然后再添加此类代码使设计更加复杂。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接