91

アプリケーションで.NET4.0MemoryCacheクラスを使用していて、最大キャッシュサイズを制限しようとしていますが、テストでは、キャッシュが実際に制限に準拠しているようには見えません

MSDNによると、キャッシュサイズを制限することになっている設定を使用しています。

  1. CacheMemoryLimitMegabytes:オブジェクトのインスタンスが拡張できる最大メモリサイズ(メガバイト単位)。」
  2. PhysicalMemoryLimitPercentage "キャッシュが使用できる物理メモリの割合。1から100までの整数値で表されます。デフォルトはゼロです。これは、 MemoryCacheインスタンスが、にインストールされているメモリの量に基づいて独自のメモリ1を管理することを示します。コンピューター。" 1.これは完全に正しいわけではありません。4未満の値は無視され、4に置き換えられます。

キャッシュをパージするスレッドはx秒ごとに起動され、ポーリング間隔やその他の文書化されていない変数にも依存するため、これらの値は概算であり、ハード制限ではないことを理解しています。ただし、これらの差異を考慮しても、CacheMemoryLimitMegabytesPhysicalMemoryLimitPercentageを一緒に設定した後、またはテストアプリで単独で設定した後、最初のアイテムがキャッシュから削除されると、キャッシュサイズに非常に一貫性がなくなります。確かに、各テストを10回実行し、平均値を計算しました。

これらは、3GBのRAMを搭載した32ビットのWindows7PCで以下のサンプルコードをテストした結果です。キャッシュのサイズは、各テストでCacheItemRemoved()を最初に呼び出した後に取得されます。(キャッシュの実際のサイズはこれより大きくなることを認識しています)

MemLimitMB    MemLimitPct     AVG Cache MB on first expiry    
   1            NA              84
   2            NA              84
   3            NA              84
   6            NA              84
  NA             1              84
  NA             4              84
  NA            10              84
  10            20              81
  10            30              81
  10            39              82
  10            40              79
  10            49              146
  10            50              152
  10            60              212
  10            70              332
  10            80              429
  10           100              535
 100            39              81
 500            39              79
 900            39              83
1900            39              84
 900            41              81
 900            46              84

 900            49              1.8 GB approx. in task manager no mem errros
 200            49              156
 100            49              153
2000            60              214
   5            60              78
   6            60              76
   7           100              82
  10           100              541

テストアプリケーションは次のとおりです。

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
namespace FinalCacheTest
{       
    internal class Cache
    {
        private Object Statlock = new object();
        private int ItemCount;
        private long size;
        private MemoryCache MemCache;
        private CacheItemPolicy CIPOL = new CacheItemPolicy();

        public Cache(long CacheSize)
        {
            CIPOL.RemovedCallback = new CacheEntryRemovedCallback(CacheItemRemoved);
            NameValueCollection CacheSettings = new NameValueCollection(3);
            CacheSettings.Add("CacheMemoryLimitMegabytes", Convert.ToString(CacheSize)); 
            CacheSettings.Add("physicalMemoryLimitPercentage", Convert.ToString(49));  //set % here
            CacheSettings.Add("pollingInterval", Convert.ToString("00:00:10"));
            MemCache = new MemoryCache("TestCache", CacheSettings);
        }

        public void AddItem(string Name, string Value)
        {
            CacheItem CI = new CacheItem(Name, Value);
            MemCache.Add(CI, CIPOL);

            lock (Statlock)
            {
                ItemCount++;
                size = size + (Name.Length + Value.Length * 2);
            }

        }

        public void CacheItemRemoved(CacheEntryRemovedArguments Args)
        {
            Console.WriteLine("Cache contains {0} items. Size is {1} bytes", ItemCount, size);

            lock (Statlock)
            {
                ItemCount--;
                size = size - 108;
            }

            Console.ReadKey();
        }
    }
}

namespace FinalCacheTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            int MaxAdds = 5000000;
            Cache MyCache = new Cache(1); // set CacheMemoryLimitMegabytes

            for (int i = 0; i < MaxAdds; i++)
            {
                MyCache.AddItem(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
            }

            Console.WriteLine("Finished Adding Items to Cache");
        }
    }
}

MemoryCacheが構成されたメモリ制限に従わないのはなぜですか?

4

7 に答える 7

103

うわー、それで私はリフレクターを使ってCLRを掘り下げるのにあまりにも多くの時間を費やしましたが、私はついにここで何が起こっているのかをうまく理解できたと思います。

設定は正しく読み込まれていますが、CLR自体に根深い問題があり、メモリ制限設定が本質的に役に立たないように見えます。

次のコードは、CacheMemoryMonitorクラスのSystem.Runtime.Caching DLLから反映されます(物理メモリを監視し、他の設定を処理する同様のクラスがありますが、これはより重要なものです)。

protected override int GetCurrentPressure()
{
  int num = GC.CollectionCount(2);
  SRef ref2 = this._sizedRef;
  if ((num != this._gen2Count) && (ref2 != null))
  {
    this._gen2Count = num;
    this._idx ^= 1;
    this._cacheSizeSampleTimes[this._idx] = DateTime.UtcNow;
    this._cacheSizeSamples[this._idx] = ref2.ApproximateSize;
    IMemoryCacheManager manager = s_memoryCacheManager;
    if (manager != null)
    {
      manager.UpdateCacheSize(this._cacheSizeSamples[this._idx], this._memoryCache);
    }
  }
  if (this._memoryLimit <= 0L)
  {
    return 0;
  }
  long num2 = this._cacheSizeSamples[this._idx];
  if (num2 > this._memoryLimit)
  {
    num2 = this._memoryLimit;
  }
  return (int) ((num2 * 100L) / this._memoryLimit);
}

最初に気付くかもしれないのは、Gen2ガベージコレクションが完了するまでキャッシュのサイズを調べようとせず、代わりにcacheSizeSamplesに保存されている既存のサイズ値にフォールバックすることです。したがって、ターゲットをすぐにヒットすることはできませんが、残りが機能した場合は、実際に問題が発生する前に、少なくともサイズの測定値を取得します。

したがって、Gen2 GCが発生したと仮定すると、問題2が発生します。これは、ref2.ExplicitSizeが、実際にキャッシュのサイズを概算するという恐ろしい仕事をするということです。CLRジャンクをスロッグすると、これがSystem.SizedReferenceであり、値を取得するためにこれが実行されていることがわかりました(IntPtrはMemoryCacheオブジェクト自体へのハンドルです)。

[SecurityCritical]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern long GetApproximateSizeOfSizedRef(IntPtr h);

extern宣言は、この時点で管理されていないウィンドウランドに飛び込むことを意味していると思いますが、そこで何をしているのかを知る方法がわかりません。私が観察したことから、それは全体のサイズを概算しようとするという恐ろしい仕事をしますが。

3つ目の注目すべき点は、manager.UpdateCacheSizeの呼び出しです。これは、何かを実行する必要があるように聞こえます。残念ながら、これがどのように機能するかについての通常のサンプルでは、​​s_memoryCacheManagerは常にnullになります。このフィールドは、パブリック静的メンバーObjectCache.Hostから設定されます。これは、ユーザーが選択した場合に混乱する可能性があります。実際には、独自のIMemoryCacheManager実装をまとめて、ObjectCache.Hostに設定し、サンプルを実行することで、このような動作を想定どおりに行うことができました。 。ただし、その時点では、独自のキャッシュ実装を作成するだけで、これらすべてを気にする必要はないようです。特に、独自のクラスをObjectCache.Host(static、

私は、これの少なくとも一部(いくつかの部分ではないにしても)は単なるバグであると信じなければなりません。MSの誰かから、このことに関する取引が何であったかを聞くのは素晴らしいことです。

この巨大な答えのTLDRバージョン:CacheMemoryLimitMegabytesがこの時点で完全に無効になっていると仮定します。これを10MBに設定してから、キャッシュを最大2 GBまでいっぱいにして、アイテムの削除を行わずにメモリ不足の例外を解消できます。

于 2011-09-11T05:51:52.663 に答える
30

私はこの答えが遅く狂っていることを知っていますが、決して遅くないよりはましです。MemoryCacheGen2コレクションの問題を自動的に解決するバージョンを作成したことをお知らせします。したがって、ポーリング間隔がメモリの負荷を示している場合は常にトリミングされます。この問題が発生している場合は、試してみてください。

http://www.nuget.org/packages/SharpMemoryCache

私がどのように解決したか知りたい場合は、GitHubでも見つけることができます。コードはやや単純です。

https://github.com/haneytron/sharpmemorycache

于 2014-04-06T21:21:21.550 に答える
5

私もこの問題に遭遇しました。1秒間に数十回プロセスに起動されるオブジェクトをキャッシュしています。

次の構成と使用法では、ほとんどの場合5秒ごとにアイテムが解放されることがわかりました。

App.config:

cacheMemoryLimitMegabytesに注意してください。これがゼロに設定されている場合、パージルーチンは妥当な時間内に起動しませんでした。

   <system.runtime.caching>
    <memoryCache>
      <namedCaches>
        <add name="Default" cacheMemoryLimitMegabytes="20" physicalMemoryLimitPercentage="0" pollingInterval="00:00:05" />
      </namedCaches>
    </memoryCache>
  </system.runtime.caching>  

キャッシュへの追加:

MemoryCache.Default.Add(someKeyValue, objectToCache, new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddSeconds(5), RemovedCallback = cacheItemRemoved });

キャッシュの削除が機能していることを確認します。

void cacheItemRemoved(CacheEntryRemovedArguments arguments)
{
    System.Diagnostics.Debug.WriteLine("Item removed from cache: {0} at {1}", arguments.CacheItem.Key, DateTime.Now.ToString());
}
于 2015-08-12T18:18:18.800 に答える
4

@Canacourseの例と@woanyの変更を使用していくつかのテストを行いましたが、メモリキャッシュのクリーニングをブロックする重要な呼び出しがいくつかあると思います。

public void CacheItemRemoved(CacheEntryRemovedArguments Args)
{
    // this WriteLine() will block the thread of
    // the MemoryCache long enough to slow it down,
    // and it will never catch up the amount of memory
    // beyond the limit
    Console.WriteLine("...");

    // ...

    // this ReadKey() will block the thread of 
    // the MemoryCache completely, till you press any key
    Console.ReadKey();
}

しかし、なぜ@woanyを変更すると、メモリが同じレベルに保たれるように見えるのでしょうか。まず、RemovedCallbackが設定されておらず、メモリキャッシュのスレッドをブロックする可能性のあるコンソール出力または入力の待機がありません。

第二に...

public void AddItem(string Name, string Value)
{
    // ...

    // this WriteLine will block the main thread long enough,
    // so that the thread of the MemoryCache can do its work more frequently
    Console.WriteLine("...");
}

〜1000番目のPASSWORD()ごとにThread.Sleep(1)を使用すると、同じ効果が得られます。

まあ、それは問題のそれほど深い調査ではありませんが、多くの新しい要素が追加されている間、MemoryCacheのスレッドがクリーニングのための十分なCPU時間を取得していないように見えます。

于 2013-11-11T14:20:11.163 に答える
3

私は(ありがたいことに)昨日、MemoryCacheを最初に使用しようとしたときに、この便利な投稿に出くわしました。値を設定してクラスを使用するという単純なケースだと思いましたが、上記で概説した同様の問題が発生しました。何が起こっているのかを確認するために、ILSpyを使用してソースを抽出し、テストを設定してコードをステップ実行しました。私のテストコードは上記のコードと非常に似ていたので、投稿しません。私のテストから、キャッシュサイズの測定は(上記のように)特に正確ではなく、現在の実装では確実に機能しないことに気づきました。ただし、物理的な測定は問題なく、すべてのポーリングで物理的なメモリが測定された場合、コードは確実に機能するように見えました。そこで、MemoryCacheStatistics内の第2世代のガベージコレクションチェックを削除しました。

テストシナリオでは、キャッシュが絶えずヒットしているため、オブジェクトが第2世代に到達する機会がないため、これは明らかに大きな違いになります。プロジェクトでこのdllの変更されたビルドを使用し、公式のMSを使用する予定です。 .net 4.5がリリースされたときにビルドします(上記の接続記事によると、修正が含まれているはずです)。論理的には、第2世代のチェックが実施された理由はわかりますが、実際には、それが理にかなっているのかどうかはわかりません。メモリが90%(または設定されている制限)に達した場合、第2世代のコレクションが発生したかどうかは関係ありません。アイテムは、関係なく削除する必要があります。

私は、physicalMemoryLimitPercentageを65%に設定して、テストコードを約15分間実行したままにしました。テスト中、メモリ使用量が65〜68%のままであり、問​​題が適切に排除されることを確認しました。私のテストでは、polingIntervalを5秒に設定し、physicalMemoryLimitPercentageを65に設定し、physicalMemoryLimitPercentageを0に設定してこれをデフォルトにしました。

上記のアドバイスに従ってください。IMemoryCacheManagerの実装は、キャッシュから物事を排除するために行うことができます。ただし、前述の第2世代のチェックの問題が発生します。ただし、シナリオによっては、これは本番コードでは問題にならない場合があり、人々にとっては十分に機能する場合があります。

于 2011-11-24T09:44:08.247 に答える
3

バグではないことが判明しました。制限を適用するためにプーリングの期間を設定するだけです。プーリングを設定しないままにしておくと、トリガーされないようです。テストしただけで、ラッパーを作成する必要はありません。または追加のコード:

 private static readonly NameValueCollection Collection = new NameValueCollection
        {
            {"CacheMemoryLimitMegabytes", "20"},
           {"PollingInterval", TimeSpan.FromMilliseconds(60000).ToString()}, // this will check the limits each 60 seconds

        };

キャッシュの増加速度に基づいて「」の値を設定しますPollingInterval。キャッシュの増加が速すぎる場合は、ポーリングチェックの頻度を増やします。それ以外の場合は、チェックの頻度を低くして、オーバーヘッドが発生しないようにします。

于 2018-12-07T14:12:33.727 に答える
1

次の変更されたクラスを使用し、タスクマネージャを介してメモリを監視すると、実際にはトリミングされます。

internal class Cache
{
    private Object Statlock = new object();
    private int ItemCount;
    private long size;
    private MemoryCache MemCache;
    private CacheItemPolicy CIPOL = new CacheItemPolicy();

    public Cache(double CacheSize)
    {
        NameValueCollection CacheSettings = new NameValueCollection(3);
        CacheSettings.Add("cacheMemoryLimitMegabytes", Convert.ToString(CacheSize));
        CacheSettings.Add("pollingInterval", Convert.ToString("00:00:01"));
        MemCache = new MemoryCache("TestCache", CacheSettings);
    }

    public void AddItem(string Name, string Value)
    {
        CacheItem CI = new CacheItem(Name, Value);
        MemCache.Add(CI, CIPOL);

        Console.WriteLine(MemCache.GetCount());
    }
}
于 2012-07-12T10:48:02.847 に答える