バックグラウンド
アプリケーションで最もよく使用されるデータ構造の 1 つは、カスタム 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); }
}
}
この実装により、同じデータのインスタンスが 1 つだけ保持されるようにしながら、構造体のセマンティクスが維持されることが保証されます。
(メモリリークなどについては言及しないでください。これは単純化されたサンプルコードです)
問題
上記のアプローチはメモリ使用量を下げるために機能しますが、パフォーマンスは恐ろしいものです。アプリケーションのプロジェクトには、簡単に 100 万以上の異なるポイントを含めることができます。その結果、PointData
インスタンスのルックアップは非常にコストがかかります。そして、このルックアップは、aPoint
が操作されるたびに実行する必要があります。おそらくご想像のとおり、これがアプリケーションのすべてです。その結果、このアプローチは私たちには適していません。
別の方法として、クラスの 2 つのバージョンを作成することもできます。1 つはPoint
上記のバッキング フライウェイトを持ち、もう 1 つは独自のデータを含みます (重複の可能性があります)。すべての (短期間の) 計算は 2 番目のクラスで行うことができますがPoint
、長期間保存する場合は、メモリ効率の良い最初のクラスに変換できます。ただし、これは、Point
クラスのすべてのユーザーを検査して、このスキームに合わせて調整する必要があることを意味します。これは実現不可能です。
私たちが探しているのは、以下の基準を満たすアプローチです。
- 同じデータを持つ複数
Point
の がある場合、メモリ使用量は、これらのそれぞれに異なる構造体インスタンスを持つよりも低くなるはずです。 - パフォーマンスは、構造体のプリミティブ データを直接操作するよりもそれほど悪くはありません。
- 構造のセマンティクスを維持する必要があります。
- 'Point' インターフェイスは同じままにする必要があります (つまり、'Point' を使用するクラスを変更する必要はありません)。
これらの基準に対するアプローチを改善する方法はありますか? または、私たちが試みることができる別のアプローチを誰かが提案できますか?