有效实施flyweight模式

Ste*_*ven 6 c# design-patterns

背景

我们的应用程序中最常用的数据结构之一是自定义Point结构.最近我们遇到了内存问题,主要是由于这个结构的实例数过多引起的.

其中许多实例包含相同的数据.共享单个实例将显着有助于减少内存使用.但是,由于我们使用的是结构,因此无法共享实例.也不可能将其更改为类,因为结构语义很重要.

我们的解决方法是使结构包含对支持类的单个引用,该引用类包含实际数据.这些flyweight数据类存储在工厂中并从工厂中检索,以确保不存在重复项.

缩小版本的代码看起来像这样:

public struct PointD
{
    //Factory
    private static class PointDatabase
    {
        private static readonly Dictionary<PointData, PointData> _data = new Dictionary<PointData, PointData>();

        public static PointData Get(double x, double y)
        {
            var key = new PointData(x, y);
            if (!_data.ContainsKey(key))
                _data.Add(key, key);

            return _data[key];
        }
    }

    //Flyweight data
    private class PointData
    {
        private double pX;
        private double pY;

        public PointData(double x, double y)
        {
            pX = x;
            pY = y;
        }

        public double X
        {
            get { return pX; }
        }

        public double Y
        {
            get { return pY; }
        }

        public override bool Equals(object obj)
        {
            var other = obj as PointData;
            if (other == null)
                return false;
            return other.X == this.X && other.Y == this.Y;
        }
        public override int GetHashCode()
        {
            return X.GetHashCode() * Y.GetHashCode();
        }
    }


    //Public struct

    public Point(double x, double y)
    {
        _data = Point3DDatabase.Get(x, y);
    }

    public double X
    {
        get { return _data == null ? 0 : _data.X; }
        set { _data = PointDatabase.Get(value, Y); }
    }

    public double Y
    {
        get { return _data == null ? 0 : _data.Y; }
        set { _data = PointDatabase.Get(X, value); }
    }
}
Run Code Online (Sandbox Code Playgroud)

此实现确保维护结构语义,同时确保只保留相同数据的一个实例.

(请不要提及内存泄漏等,这是简化的示例代码)

问题

虽然上述方法可以降低我们的内存使用量,但性能却是可怕的.我们的应用程序中的项目可以轻松包含一百万个不同点或更多.结果,查找PointData实例非常昂贵.每次Point操作时都必须执行此查找,正如您可能猜到的那样,这是我们的应用程序的全部内容.因此,这种方法不适合我们.

作为替代方案,我们可以创建Point该类的两个版本:一个具有如上所述的支持flyweight,另一个包含其自己的数据(可能具有重复数据).所有(短期)计算都可以在第二类中完成,而当存储Point更长的持续时间时,它们可以转换为第一个节省内存的类.但是,这意味着必须对该Point类计划的所有用户进行检查和调整,这对我们来说是不可行的.

我们正在寻找的是一种符合以下标准的方法:

  • 当存在多个Point具有相同数据的s时,内存使用量应低于为每个数据库设置不同的结构实例.
  • 性能不应比直接处理结构中的原始数据更糟糕.
  • 应该保持结构语义.
  • 'Point'接口应保持不变(即不必更改使用'Point'的类).

我们有什么方法可以改进我们对这些标准的态度?或者任何人都可以建议我们尝试不同的方法吗?

Gus*_*dor 0

我针对性能和内存问题的首选解决方案是缓存、预取,最重要的是在不需要时剔除数据,而不是重新设计整个数据结构和编程模型。

这样想吧。在图表上,您无法一次显示数百万个点,因为您用完了像素(您应该遮挡剔除这些点)。同样,在表格中,屏幕上没有足够的垂直空间(您需要截断数据集)。根据需要考虑从源文件流式传输数据。如果您的源数据结构不适合动态检索,请考虑使用中间临时文件格式。这是 .Net 的 JITer 如此快速地工作的方式之一!