6

次のオブジェクトがあるとします

public class MyClass
{
    public ReadOnlyDictionary<T, V> Dict
    {
        get
        {
            return createDictionary();
        }
    }
}

ReadOnlyDictionaryが の読み取り専用ラッパーであるとしますDictionary<T, V>

このcreateDictionaryメソッドは完了するまでにかなりの時間がかかり、返されるディクショナリは比較的大きくなります。

明らかに、ある種のキャッシュを実装して結果を再利用できるようにしたいのcreateDictionaryですが、ガベージコレクターを乱用したり、多くのメモリを使用したりしたくありません。

辞書に使用することを考えWeakReferenceましたが、これが最善のアプローチかどうかはわかりません。

あなたは何をお勧めします?複数回呼び出される可能性のあるコストのかかるメソッドの結果を適切に処理するにはどうすればよいですか?

アップデート:

C# 2.0 ライブラリ (単一の DLL、非ビジュアル) に関するアドバイスに興味があります。ライブラリは、Web アプリケーションのデスクトップで使用される場合があります。

更新 2:

この質問は、読み取り専用オブジェクトにも関連しています。プロパティの値を から に変更しDictionaryましたReadOnlyDictionary

更新 3:

T比較的単純な型です (文字列など)。Vカスタムクラスです。のインスタンスを作成するにはコストがかかると考えるかもしれませんV。ディクショナリには、0 から数千の要素が含まれる場合があります。

単一のスレッドから、または外部同期メカニズムを使用して複数のスレッドからアクセスされると想定されるコード。

誰も使用していないときに辞書が GC されていれば問題ありません。createDictionary時間 (何らかの方法で の結果をキャッシュしたい) とメモリ消費 (必要以上に長くメモリを占有したくない)のバランスを見つけようとしています。

4

5 に答える 5

3

他の誰もディクショナリを参照していない場合、オブジェクトは次のGCを生き残ることができないため、WeakReferenceはキャッシュに適したソリューションではありません。作成した値をメンバー変数に格納して単純なキャッシュを作成し、nullでない場合は再利用できます。

これはスレッドセーフではなく、辞書に頻繁に同時にアクセスできる場合は、状況によっては辞書を数回作成することになります。ダブルチェックロックパターンを使用して、パフォーマンスへの影響を最小限に抑えながらこれを防ぐことができます。

さらに役立つように、同時アクセスが問題になるかどうか、辞書が消費するメモリの量とその作成方法を指定する必要があります。たとえば、辞書が高価なクエリの結果である場合は、辞書をディスクにシリアル化し、再作成する必要があるまで再利用すると役立つ場合があります(これは特定のニーズによって異なります)。

オブジェクトをキャッシュから削除する必要があるときに明確なポリシーがない場合、キャッシュはメモリリークの別の言葉です。WeakReferenceを試しているので、キャッシュをクリアするのがいつ正確に適切かわからないと思います。

もう1つのオプションは、辞書を圧縮してメモリをあまり必要としない構造にすることです。辞書にはいくつのキーがあり、その値は何ですか?

于 2012-06-10T20:25:53.830 に答える
1

辞書全体をキャッシュする必要がありますか?

あなたの言うことから、キーと値のペアの最近使用されたリストを保持する方が良いかもしれません。

リストにキーが見つかった場合は、値を返します。

そうでない場合は、1 つの値を作成し (すべての値を作成するよりも高速であり、メモリの使用量も少ないと考えられます)、それをリストに格納します。これにより、最も長く使用されていないキーと値のペアが削除されます。

これは非常に単純な MRU リストの実装です。インスピレーションとして役立つかもしれません。

using System.Collections.Generic;
using System.Linq;

internal sealed class MostRecentlyUsedList<T> : IEnumerable<T>
{
    private readonly List<T> items;
    private readonly int maxCount;

    public MostRecentlyUsedList(int maxCount, IEnumerable<T> initialData)
        : this(maxCount)
    {
        this.items.AddRange(initialData.Take(maxCount));
    }

    public MostRecentlyUsedList(int maxCount)
    {
        this.maxCount = maxCount;
        this.items = new List<T>(maxCount);
    }

    /// <summary>
    /// Adds an item to the top of the most recently used list.
    /// </summary>
    /// <param name="item">The item to add.</param>
    /// <returns><c>true</c> if the list was updated, <c>false</c> otherwise.</returns>
    public bool Add(T item)
    {
        int index = this.items.IndexOf(item);

        if (index != 0)
        {
            // item is not already the first in the list
            if (index > 0)
            {
                // item is in the list, but not in the first position
                this.items.RemoveAt(index);
            }
            else if (this.items.Count >= this.maxCount)
            {
                // item is not in the list, and the list is full already
                this.items.RemoveAt(this.items.Count - 1);
            }

            this.items.Insert(0, item);

            return true;
        }
        else
        {
            return false;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return this.items.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

あなたの場合、 T はキーと値のペアです。maxcount を十分に小さくして、検索を高速に保ち、過度のメモリ使用を回避します。アイテムを使用するたびに Add を呼び出します。

于 2012-06-10T20:58:19.007 に答える
1

キャッシュ内のオブジェクトの存在の有用な有効期間がオブジェクトの参照有効期間に匹敵する場合、アプリケーションはWeakReferenceキャッシュ メカニズムとして使用する必要があります。たとえば、 のReadOnlyDictionaryデシリアライズに基づいてを作成するメソッドがあるとしますString。一般的な使用パターンが、文字列を読み取り、辞書を作成し、何かを実行し、それを放棄して、別の文字列でやり直すというものである場合、WeakReferenceおそらく理想的ではありません。一方、目的が多くの文字列 (かなりの数が等しい) を逆シリアル化することである場合は、ReadOnlyDictionaryたとえば、同じ文字列のデシリアライズを繰り返し試みて同じインスタンスが生成される場合は、非常に便利です。節約は、インスタンスを構築する作業を 1 回だけ行う必要があるという事実だけでなく、(1) 複数のインスタンスをメモリに保持する必要がない、(2) という事実からも得られることに注意してください。変数が同じインスタンスを参照する場合ReadOnlyDictionary、インスタンス自体を調べなくても、変数が同等であることがわかります。対照的に、2 つの異なるReadOnlyDictionaryインスタンスが同等かどうかを判断するには、それぞれのすべての項目を調べる必要がある場合があります。このような比較を何度も行う必要があるコードは、WeakReferenceキャッシュを使用することでメリットが得られるため、同等のインスタンスを保持する変数は通常、同じインスタンスを保持します。

于 2012-06-11T20:54:33.507 に答える
1

4 つの主要なメカニズムを利用できます (Lazy は 4.0 に含まれているため、オプションはありません)。

  1. 遅延初期化
  2. 仮想プロキシ
  3. 幽霊
  4. バリューホルダー

それぞれに独自の利点があります。

ホルダーの GetValue メソッドの最初の呼び出しで辞書を作成する値ホルダーを提案します。その後、必要な限りその値を使用でき、かつ一度だけ実行され、必要なときにのみ実行されます。

詳細については、Martin Fowlers のページを参照してください。

于 2012-06-10T20:34:28.363 に答える
0

独自のメカニズムを開発する代わりに、キャッシュに信頼できる2つのメカニズムがあると思います。1つ目は、ご自身が提案したように、WeakReferenceを使用し、ガベージコレクターにこのメモリを解放するタイミングを決定させることでした。

2番目のメカニズムであるメモリページングがあります。辞書が一挙に作成された場合、それはおそらくヒープの多かれ少なかれ連続した部分に格納されます。辞書を存続させ、不要な場合はWindowsにスワップファイルにページアウトさせます。使用法(辞書へのアクセスのランダム性)によっては、WeakReferenceよりもパフォーマンスが向上する場合があります。

この2番目のアプローチは、アドレス空間の制限に近い場合に問題があります(これは32ビットプロセスでのみ発生します)。

于 2012-06-10T20:25:29.447 に答える