Saya bekerja dengan kelas .NET 4.0 MemoryCache dalam sebuah aplikasi dan mencoba membatasi ukuran cache maksimum, tetapi dalam pengujian saya tidak tampak bahwa cache sebenarnya mematuhi batas.
Saya menggunakan pengaturan yang, menurut MSDN , seharusnya membatasi ukuran cache:
- CacheMemoryLimitMegabytes : Ukuran memori maksimum, dalam megabyte, yang dapat digunakan untuk mengembangkan instance objek. "
- PhysicalMemoryLimitPercentage : "Persentase memori fisik yang dapat digunakan cache, dinyatakan sebagai nilai integer dari 1 hingga 100. Standarnya adalah nol, yang menunjukkan bahwainstance MemoryCache mengelola memori 1 mereka sendiriberdasarkan jumlah memori yang diinstal di komputer." 1. Ini tidak sepenuhnya benar - nilai apa pun di bawah 4 akan diabaikan dan diganti dengan 4.
Saya mengerti bahwa nilai-nilai ini adalah perkiraan dan bukan batas yang sulit karena utas yang membersihkan cache dipecat setiap x detik dan juga bergantung pada interval polling dan variabel tidak berdokumen lainnya. Namun, bahkan dengan mempertimbangkan perbedaan ini, saya melihat ukuran cache yang sangat tidak konsisten saat item pertama dikeluarkan dari cache setelah menyetel CacheMemoryLimitMegabytes dan PhysicalMemoryLimitPercentage bersama-sama atau secara tunggal dalam aplikasi pengujian. Untuk memastikan saya menjalankan setiap tes 10 kali dan menghitung angka rata-rata.
Ini adalah hasil pengujian kode contoh di bawah ini pada PC Windows 7 32-bit dengan RAM 3GB. Ukuran cache diambil setelah panggilan pertama ke CacheItemRemoved () pada setiap pengujian. (Saya tahu ukuran cache sebenarnya akan lebih besar dari ini)
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
Berikut adalah aplikasi tesnya:
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");
}
}
}
Mengapa MemoryCache tidak mematuhi batas memori yang dikonfigurasi?
sumber
Jawaban:
Wow, jadi saya menghabiskan terlalu banyak waktu untuk menggali di dalam CLR dengan reflektor, tapi saya rasa akhirnya saya bisa memahami dengan baik apa yang terjadi di sini.
Pengaturan sedang dibaca dengan benar, tetapi tampaknya ada masalah mendalam di CLR itu sendiri yang sepertinya akan membuat pengaturan batas memori pada dasarnya tidak berguna.
Kode berikut tercermin dari System.Runtime.Caching DLL, untuk kelas CacheMemoryMonitor (ada kelas serupa yang memantau memori fisik dan berurusan dengan pengaturan lain, tetapi ini yang lebih penting):
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); }
Hal pertama yang mungkin Anda perhatikan adalah bahwa ia bahkan tidak mencoba melihat ukuran cache sampai setelah pengumpulan sampah Gen2, alih-alih hanya mengembalikan nilai ukuran tersimpan yang ada di cacheSizeSamples. Jadi Anda tidak akan pernah bisa mencapai target dengan tepat, tapi jika yang lainnya berhasil kita setidaknya akan mendapatkan ukuran ukuran sebelum kita mendapat masalah yang nyata.
Jadi dengan asumsi Gen2 GC telah terjadi, kita mengalami masalah 2, yaitu ref2.ApproximateSize melakukan pekerjaan yang mengerikan untuk benar-benar memperkirakan ukuran cache. Bekerja keras melalui sampah CLR saya menemukan bahwa ini adalah System.SizedReference, dan inilah yang dilakukannya untuk mendapatkan nilai (IntPtr adalah pegangan ke objek MemoryCache itu sendiri):
[SecurityCritical] [MethodImpl(MethodImplOptions.InternalCall)] private static extern long GetApproximateSizeOfSizedRef(IntPtr h);
Saya berasumsi bahwa deklarasi eksternal berarti bahwa itu masuk ke jendela yang tidak terkelola pada saat ini, dan saya tidak tahu bagaimana mulai mencari tahu apa fungsinya di sana. Dari apa yang saya amati meskipun itu melakukan pekerjaan yang mengerikan mencoba memperkirakan ukuran keseluruhannya.
Hal ketiga yang terlihat adalah panggilan ke manager.UpdateCacheSize yang sepertinya harus melakukan sesuatu. Sayangnya, dalam contoh normal cara kerja ini, s_memoryCacheManager akan selalu bernilai null. Bidang ini disetel dari ObjectCache.Host anggota statis publik. Ini terbuka bagi pengguna untuk mengacaukannya jika dia memilihnya, dan saya benar-benar dapat membuat hal ini berfungsi seperti yang seharusnya dengan menggabungkan implementasi IMemoryCacheManager saya sendiri, mengaturnya ke ObjectCache.Host, dan kemudian menjalankan sampel . Pada titik itu, sepertinya Anda sebaiknya membuat implementasi cache Anda sendiri dan bahkan tidak peduli dengan semua hal ini, terutama karena saya tidak tahu apakah mengatur kelas Anda sendiri ke ObjectCache.Host (statis,
Saya harus percaya bahwa setidaknya sebagian dari ini (jika bukan beberapa bagian) hanyalah bug langsung. Senang sekali mendengar dari seseorang di MS apa kesepakatannya dengan hal ini.
Versi TLDR dari jawaban raksasa ini: Asumsikan bahwa CacheMemoryLimitMegabytes benar-benar rusak pada saat ini. Anda dapat mengaturnya menjadi 10 MB, dan kemudian melanjutkan untuk mengisi cache hingga ~ 2GB dan menghilangkan pengecualian memori tanpa tersandung penghapusan item.
sumber
Saya tahu jawaban ini sangat terlambat, tetapi lebih baik terlambat daripada tidak sama sekali. Saya ingin memberi tahu Anda bahwa saya menulis versi
MemoryCache
yang menyelesaikan masalah Koleksi Gen 2 secara otomatis untuk Anda. Oleh karena itu, ia memangkas setiap kali polling interval menunjukkan tekanan memori. Jika Anda mengalami masalah ini, cobalah!http://www.nuget.org/packages/SharpMemoryCache
Anda juga dapat menemukannya di GitHub jika Anda penasaran bagaimana saya menyelesaikannya. Kodenya agak sederhana.
https://github.com/haneytron/sharpmemorycache
sumber
2n + 20
berukuran terkait dengan byte, di manan
adalah panjang string. Ini sebagian besar disebabkan oleh dukungan Unicode.Saya juga mengalami masalah ini. Saya menyimpan objek yang ditembakkan ke dalam proses saya puluhan kali per detik.
Saya telah menemukan konfigurasi dan penggunaan berikut membebaskan item setiap 5 detik sebagian besar waktu .
App.config:
Catat cacheMemoryLimitMegabytes . Jika ini disetel ke nol, rutinitas pembersihan tidak akan diaktifkan dalam waktu yang wajar.
<system.runtime.caching> <memoryCache> <namedCaches> <add name="Default" cacheMemoryLimitMegabytes="20" physicalMemoryLimitPercentage="0" pollingInterval="00:00:05" /> </namedCaches> </memoryCache> </system.runtime.caching>
Menambahkan ke cache:
MemoryCache.Default.Add(someKeyValue, objectToCache, new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddSeconds(5), RemovedCallback = cacheItemRemoved });
Mengonfirmasi bahwa penghapusan cache berfungsi:
void cacheItemRemoved(CacheEntryRemovedArguments arguments) { System.Diagnostics.Debug.WriteLine("Item removed from cache: {0} at {1}", arguments.CacheItem.Key, DateTime.Now.ToString()); }
sumber
Saya telah melakukan beberapa pengujian dengan contoh @Canacourse dan modifikasi @woany dan menurut saya ada beberapa panggilan penting yang memblokir pembersihan cache memori.
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(); }
Tapi kenapa modifikasi @woany nampaknya menjaga memori pada level yang sama? Pertama, RemovedCallback tidak disetel dan tidak ada keluaran konsol atau menunggu masukan yang dapat memblokir utas cache memori.
Kedua...
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("..."); }
Sebuah Thread.Sleep (1) setiap ~ 1000 AddItem () akan memiliki efek yang sama.
Yah, ini bukan penyelidikan yang sangat mendalam tentang masalahnya, tetapi sepertinya utas MemoryCache tidak mendapatkan cukup waktu CPU untuk membersihkan, sementara banyak elemen baru ditambahkan.
sumber
Saya (untungnya) menemukan posting yang berguna ini kemarin ketika pertama kali mencoba menggunakan MemoryCache. Saya pikir ini akan menjadi kasus sederhana dalam menetapkan nilai dan menggunakan kelas tetapi saya mengalami masalah serupa yang diuraikan di atas. Untuk mencoba dan melihat apa yang sedang terjadi, saya mengekstrak sumbernya menggunakan ILSpy dan kemudian menyiapkan tes dan melangkah melalui kode. Kode pengujian saya sangat mirip dengan kode di atas jadi saya tidak akan mempostingnya. Dari pengujian saya, saya memperhatikan bahwa pengukuran ukuran cache tidak pernah terlalu akurat (seperti yang disebutkan di atas) dan mengingat implementasi saat ini tidak akan pernah berfungsi dengan andal. Namun pengukuran fisiknya baik-baik saja dan jika memori fisik diukur pada setiap polling maka menurut saya kode tersebut akan bekerja dengan andal. Jadi, saya menghapus pemeriksaan pengumpulan sampah gen 2 dalam MemoryCacheStatistics;
Dalam skenario pengujian, ini jelas membuat perbedaan besar karena cache dipukul terus-menerus sehingga objek tidak pernah memiliki kesempatan untuk mencapai gen 2. Saya pikir kita akan menggunakan versi modifikasi dll ini pada proyek kita dan menggunakan MS resmi membangun ketika .net 4.5 keluar (yang menurut artikel terhubung yang disebutkan di atas seharusnya ada perbaikan di dalamnya). Secara logis saya dapat melihat mengapa pemeriksaan gen 2 telah diberlakukan tetapi dalam praktiknya saya tidak yakin apakah itu masuk akal. Jika memori mencapai 90% (atau berapa pun batas yang telah ditetapkan) maka tidak masalah apakah koleksi gen 2 telah terjadi atau tidak, item harus dikeluarkan.
Saya membiarkan kode pengujian saya berjalan selama sekitar 15 menit dengan physicalMemoryLimitPercentage yang disetel ke 65%. Saya melihat penggunaan memori tetap antara 65-68% selama pengujian dan melihat hal-hal digusur dengan benar. Dalam pengujian saya, saya menetapkan pollingInterval ke 5 detik, physicalMemoryLimitPercentage ke 65 dan physicalMemoryLimitPercentage ke 0 sebagai default.
Mengikuti saran di atas; implementasi IMemoryCacheManager dapat dilakukan untuk mengeluarkan sesuatu dari cache. Namun itu akan mengalami masalah pemeriksaan gen 2 yang disebutkan. Meskipun, tergantung pada skenarionya, ini mungkin tidak menjadi masalah dalam kode produksi dan dapat bekerja cukup untuk orang-orang.
sumber
Ternyata itu bukan bug, yang perlu Anda lakukan hanyalah mengatur rentang waktu penggabungan untuk memaksakan batasan, sepertinya jika Anda membiarkan penggabungan tidak disetel, itu tidak akan pernah memicu. Saya hanya mengujinya dan tidak perlu membungkusnya atau kode tambahan apa pun:
private static readonly NameValueCollection Collection = new NameValueCollection { {"CacheMemoryLimitMegabytes", "20"}, {"PollingInterval", TimeSpan.FromMilliseconds(60000).ToString()}, // this will check the limits each 60 seconds };
Setel nilai "
PollingInterval
" berdasarkan seberapa cepat cache berkembang, jika tumbuh terlalu cepat, tingkatkan frekuensi pemeriksaan polling jika tidak, jaga agar pemeriksaan tidak terlalu sering agar tidak menyebabkan overhead.sumber
Jika Anda menggunakan kelas yang dimodifikasi berikut dan memonitor memori melalui Task Manager sebenarnya dipangkas:
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()); } }
sumber
MemoryCache
. Saya bertanya-tanya mengapa sampel ini berhasil.