c# - 享元模式的高效实现

标签 c# design-patterns

背景

我们的应用程序中最常用的数据结构之一是自定义点结构。最近我们遇到了内存问题,主要是由于该结构的实例过多所致。

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

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

代码的精简版如下所示:

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); }
    }
}

此实现确保结构语义得到维护,同时确保仅保留相同数据的一个实例。

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

问题

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

作为替代方案,我们可以制作两个版本的 Point 类:一个具有上述支持享元,另一个包含自己的数据(可能有重复数据)。所有(短暂的)计算都可以在第二个类中完成,而当将 Point 存储较长时间时,它们可以转换为第一个内存高效类。但是,这意味着必须对 Point 类的所有用户进行检查和调整以适应该方案,这对我们来说是不可行的。

我们正在寻找一种满足以下标准的方法:

  • 当有多个 Point 具有相同的数据时,内存使用量应该低于对每个点使用不同的结构实例。
  • 性能应该不会比直接处理结构中的原始数据差很多。
  • 应保持结构语义。
  • “Point”接口(interface)应保持不变(即不必更改使用“Point”的类)。

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

最佳答案

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

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

关于c# - 享元模式的高效实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19855367/

相关文章:

design-patterns - 动态访问对象方法

java - 依赖注入(inject)、多态性和 OOP 模式(MVC、命令等)

c# - 在扩展方法中处理 null

c# - SWI-prolog 到 C# 断言不工作

c# - 使用 FileSavePicker 在 Windows Phone 8.1 中保存图像

java - 我可以并且是否需要避免单例模式?

design-patterns - 服务器控件如何违反 MVC 设计模式?

c# - 使用ImageList对象时出现"Could not load file or assembly..."错误

c# - 什么是 Visual Studio 2013 中的 Fakes 程序集?

c++ - 在软件渲染器中模仿顶点/片段着色器的设计模式是什么?