[解決済み] MemoryCacheが設定上のメモリ制限に従わない
質問
私は、.NET 4.0を使用しています。 MemoryCache クラスを作成し、最大キャッシュ サイズを制限しようとしていますが、私のテストでは、キャッシュが実際に制限に従っているようには見えません。
私は、以下の設定を使用しています。 MSDN によると によると、キャッシュ サイズを制限することになっています。
- キャッシュメモリ制限メガバイト : オブジェクトのインスタンスが成長できる最大メモリサイズ、単位はメガバイトです。
- 物理メモリ制限の割合 (PhysicalMemoryLimitPercentage) : キャッシュが使用できる物理メモリの割合で、1 から 100 までの整数値で表されます。デフォルトは 0 で、これは メモリキャッシュ インスタンスは自分自身のメモリを管理します。 1 コンピュータに搭載されているメモリの量に応じて、" 1. これは完全に正しいわけではありません。4 以下の値は無視され、4 に置き換えられます。
キャッシュをパージするスレッドは x 秒ごとに起動され、ポーリング間隔や他の文書化されていない変数にも依存するため、これらの値は概算値であり、厳しい制限値ではないことは理解しています。しかし、これらのばらつきを考慮しても、最初のアイテムが CacheMemoryLimitMegabytes を設定した後、最初のアイテムがキャッシュから追い出されるときに、キャッシュの大きさに乱れが生じます。 と 物理メモリ制限のパーセンテージ を一緒に、または単体でテストアプリで実行しました。 念のため、各テストを 10 回実行し、平均値を計算しました。
これらは、3GB の RAM を搭載した 32 ビットの Windows 7 PC 上で、以下のサンプル コードをテストした結果です。キャッシュのサイズは、以下の最初の呼び出しの後に取得されます。 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 は設定されたメモリ制限に従わないのですか?
どのように解決するのですか?
Reflector を使って CLR を掘り下げるのに完全に長い時間を費やしてしまいましたが、ここで起こっていることについてようやく良いハンドルができたようです。
設定は正しく読み込まれていますが、CLR 自体に根深い問題があるようで、メモリ制限の設定が本質的に無意味になっているように見えます。
次のコードは System.Runtime.Caching DLL の CacheMemoryMonitor クラスに反映されています (物理メモリを監視して他の設定を処理する同様のクラスもありますが、より重要なのはこのクラスです)。
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 に保存された既存のサイズ値にフォールバックしています。 そのため、ターゲットを正確にヒットすることはできませんが、残りの部分が機能すれば、実際に問題を起こす前に、少なくともサイズの測定値を得ることができます。
それは ref2.ApproximateSize がキャッシュのサイズを実際に近似するのにひどい仕事をするという問題です。 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 が現時点では完全に破綻していると仮定します。 それを 10 MB に設定し、キャッシュを ~2GB まで満タンにして、アイテム削除のトリップなしでメモリ不足の例外を吹き飛ばすことを続行できます。
関連
-
[解決済み】「未割り当てのローカル変数を使用」とはどういう意味ですか?
-
[解決済み】パディングが無効で、削除できない?
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】EF 5 Enable-Migrations : アセンブリにコンテキストタイプが見つかりませんでした
-
[解決済み】Visual Studio: 操作を完了できませんでした。パラメータが正しくありません
-
[解決済み】ファイルへの読み書きの際に共有違反のIOExceptionが発生する C#
-
[解決済み】aspNetCore 2.2.0 - AspNetCoreModuleV2 エラー
-
[解決済み】Unityでゲームオブジェクトのすべての子をループスルーして破壊する方法?
-
VSでscanfエラーを恒久的に解決するには、ソースファイルを作成し、自動的に#define _CRT_SECURE_NO_WARNINGS 1を追加してください。
-
[解決済み] フォルダが存在しない場合、作成する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】指定されたキャストが有効でない?
-
[解決済み】"出力タイプがクラスライブラリのプロジェクトは直接起動できない"
-
[解決済み】GDI+、JPEG画像をMemoryStreamに変換する際にジェネリックエラーが発生しました。
-
[解決済み】文字列が有効な DateTime " format dd/MM/yyyy " として認識されなかった。
-
[解決済み】Excel "外部テーブルが期待された形式ではありません。"
-
[解決済み】C# ASP.NET使用時に「WebClientのリクエスト中に例外が発生しました。
-
[解決済み】取り消せないメンバはメソッドのように使えない?
-
[解決済み】C# - パスに不正な文字がある場合
-
[解決済み] 関数を終了するには?
-
[解決済み】ユーザー設定値を別のユーザー設定値で設定する