6

バックグラウンド

アプリケーションで最もよく使用されるデータ構造の 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' を使用するクラスを変更する必要はありません)。

これらの基準に対するアプローチを改善する方法はありますか? または、私たちが試みることができる別のアプローチを誰かが提案できますか?

4

1 に答える 1

0

データ構造とプログラミング モデル全体を作り直すのではなく、パフォーマンスとメモリの問題に対する私の頼りになる解決策は、データをキャッシュし、プリフェッチし、最も重要なこととして、不要なデータを選別することです。

このように考えてください。グラフでは、ピクセルが不足するため、一度に数百万のポイントを表示することはできません (これらのポイントをオクルージョン カリングする必要があります)。同様に、テーブルでは、画面に十分な垂直方向のスペースがありません (データ セットの切り捨てが必要です)。必要に応じて、ソース ファイルからデータをストリーミングすることを検討してください。ソース データ構造が動的取得に適していない場合は、中間の一時ファイル形式を検討してください。これは、.Net の JITer が非常に迅速に機能する方法の 1 つです。

于 2013-11-08T09:29:24.540 に答える