C# flyweight模式的高效实现
背景 应用程序中最常用的数据结构之一是自定义点结构。最近我们遇到了内存问题,主要是由于这个结构的实例过多造成的 其中许多实例包含相同的数据。共享单个实例将大大有助于减少内存使用。但是,由于我们使用的是结构,实例不能共享。也不可能将其更改为类,因为结构语义很重要 我们的解决方法是使用一个包含对支持类的单个引用的结构,该类包含实际数据。这些flyweight数据类存储在工厂中并从工厂中检索,以确保不存在重复项 代码的缩小版本如下所示:C# flyweight模式的高效实现,c#,design-patterns,C#,Design Patterns,背景 应用程序中最常用的数据结构之一是自定义点结构。最近我们遇到了内存问题,主要是由于这个结构的实例过多造成的 其中许多实例包含相同的数据。共享单个实例将大大有助于减少内存使用。但是,由于我们使用的是结构,实例不能共享。也不可能将其更改为类,因为结构语义很重要 我们的解决方法是使用一个包含对支持类的单个引用的结构,该类包含实际数据。这些flyweight数据类存储在工厂中并从工厂中检索,以确保不存在重复项 代码的缩小版本如下所示: public struct PointD { //Fac
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); }
}
}
公共结构点d
{
//工厂
私有静态类点数据库
{
私有静态只读字典_data=new Dictionary();
公共静态点数据获取(双x,双y)
{
var key=新的点数据(x,y);
如果(!\u数据容器(键))
_数据。添加(键,键);
返回_数据[键];
}
}
//飞锤数据
私有类点数据
{
私人双pX;
私人双pY;
公共点数据(双x,双y)
{
pX=x;
pY=y;
}
公共双X
{
获取{return pX;}
}
公共双Y
{
获取{return pY;}
}
公共覆盖布尔等于(对象对象对象)
{
var other=作为点数据的obj;
如果(其他==null)
返回false;
返回other.X==this.X&&other.Y==this.Y;
}
公共覆盖int GetHashCode()
{
返回X.GetHashCode()*Y.GetHashCode();
}
}
//公共结构
公共点(双x,双y)
{
_data=Point3DDatabase.Get(x,y);
}
公共双X
{
获取{return _data==null?0:_data.X;}
set{u data=PointDatabase.Get(value,Y);}
}
公共双Y
{
获取{return _data==null?0:_data.Y;}
set{u data=PointDatabase.Get(X,value);}
}
}
此实现确保维护结构语义,同时确保仅保留相同数据的一个实例
(请不要提及内存泄漏之类的问题,这是简化的示例代码)
问题
尽管上述方法可以降低内存使用率,但性能却非常糟糕。我们应用程序中的一个项目可以很容易地包含一百万个或更多不同的点。因此,查找PointData
实例的成本非常高。无论何时操作点
,都必须执行此查找,正如您可能猜到的,这就是我们的应用程序的全部内容。因此,这种方法不适合我们
作为替代方案,我们可以制作两个版本的Point
类:一个具有如上所述的支持flyweight,另一个包含自己的数据(可能有重复项)。所有(短期)计算都可以在第二个类中完成,而当将点
存储更长时间时,它们可以转换为第一个内存有效类。然而,这意味着必须检查点
类的所有用户,并根据该方案进行调整,这对我们来说是不可行的
我们正在寻找一种符合以下标准的方法:
- 当有多个
具有相同的数据时,内存使用率应低于每个点都具有不同的结构实例点
- 性能不应该比直接处理结构中的基本数据差太多
- 应该维护结构语义
- “点”接口应保持不变(即使用“点”的类不必更改)
我们有没有办法改进我们对这些标准的做法?或者有人能提出我们可以尝试的不同方法吗?与其重新构建整个数据结构和编程模型,我针对性能和内存问题的解决方案是缓存、预取,最重要的是在不需要数据时剔除数据
这样想吧。在图形上,无法一次显示数百万个点,因为像素已用完(应该剔除这些点)。类似地,在表中,屏幕上没有足够的垂直空间(需要截断数据集)。根据需要从源文件中考虑流数据。如果源数据结构不适合进行动态检索,请考虑中间的、临时的文件格式。strong>这是.Net的JITer工作得如此之快的方式之一与您的问题无关,但:这是一个糟糕的哈希代码方法。整数的哈希代码是整数本身的值。因此,如果X或Y为零,哈希代码将为零。我想这可能会在散列容器中引起很多冲突,比如你正在使用的字典!。您应该使用类似于
returnx+31*Y代码>@MatthewWatson我通常对字段的哈希代码进行异或运算。这更合适吗?@Gusdor XOR不是很好,因为在X==Y的所有情况下,它都是零。如果你有一个数据集,在X==Y的很多情况下,你会遇到很多碰撞。@MatthewWatson fair point,我做过比我想承认的更糟糕的事情。但正如你所指出的,这只是整体表现中的一个次要因素。@Steven嗯,这可能不是一个次要因素。如果要将X==0和Y==0的一百万个点存储到该字典中,它将退化为一个线性搜索的链表,其中