problem practices net close best c# .net caching

practices - Caché de objetos para C#



httpclient c# best practices (9)

Estoy haciendo un visor de documentos para algún formato de documento. Para hacerlo más fácil, digamos que esto es un visor de PDF, una aplicación de escritorio . Un requisito para el software es la velocidad en el procesamiento. Así que, ahora mismo, guardo en la memoria caché la imagen para las páginas siguientes mientras el usuario se desplaza por el documento.

Esto funciona, la interfaz de usuario es muy sensible y parece que la aplicación puede procesar las páginas casi al instante ... a un costo: el uso de memoria a veces llega a 600 MB. Lo guardo todo en la memoria.

Ahora, puedo guardar en el disco, lo sé, pero hacerlo todo el tiempo es notablemente más lento. Lo que me gustaría hacer es implementar algo de caché (LRU?), Donde algunas de las páginas almacenadas en caché (objetos de imagen) están en la memoria y la mayoría están en el disco.

Antes de embarcarme en esto, ¿hay algo en el marco o en alguna biblioteca que haga esto por mí? Parece un problema bastante común. (Esta es una aplicación de escritorio, no ASP.NET)

Alternativamente, ¿tiene otras ideas para este problema?


¿Cómo estás implementando tu caché?

Puede usar la System.Web.Caching.Cache , incluso en aplicaciones que no sean web, y purgará los elementos en una LRU si / cuando necesita la memoria.

En una aplicación que no sea web, deberá usar HttpRuntime.Cache para acceder a la instancia de Cache .

Tenga en cuenta que la documentación indica que la clase Cache no está destinada a ser utilizada fuera de ASP.NET, aunque siempre me ha funcionado. (Nunca he confiado en él en ninguna aplicación de misión crítica).


.NET Framework siempre ha tenido la capacidad de mantener referencias débiles a los objetos.

Básicamente, las referencias débiles son referencias a objetos que el tiempo de ejecución considera "sin importancia" y que pueden ser eliminados por una recolección de elementos no utilizados en cualquier momento. Esto se puede usar, por ejemplo, para almacenar cosas en caché, pero no tendría control sobre qué se recopila y qué no.

Por otro lado, es muy simple de usar y puede ser justo lo que necesita.

Dave



Escribí un LRU Cache y algunos casos de prueba. Siéntete libre de usarlo.

Puedes leer la fuente en mi blog .

Para los perezosos (aquí está menos los casos de prueba):

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LRUCache { public class IndexedLinkedList<T> { LinkedList<T> data = new LinkedList<T>(); Dictionary<T, LinkedListNode<T>> index = new Dictionary<T, LinkedListNode<T>>(); public void Add(T value) { index[value] = data.AddLast(value); } public void RemoveFirst() { index.Remove(data.First.Value); data.RemoveFirst(); } public void Remove(T value) { LinkedListNode<T> node; if (index.TryGetValue(value, out node)) { data.Remove(node); index.Remove(value); } } public int Count { get { return data.Count; } } public void Clear() { data.Clear(); index.Clear(); } public T First { get { return data.First.Value; } } } }

LRUCache

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LRUCache { public class LRUCache<TKey, TValue> : IDictionary<TKey, TValue> { object sync = new object(); Dictionary<TKey, TValue> data; IndexedLinkedList<TKey> lruList = new IndexedLinkedList<TKey>(); ICollection<KeyValuePair<TKey, TValue>> dataAsCollection; int capacity; public LRUCache(int capacity) { if (capacity <= 0) { throw new ArgumentException("capacity should always be bigger than 0"); } data = new Dictionary<TKey, TValue>(capacity); dataAsCollection = data; this.capacity = capacity; } public void Add(TKey key, TValue value) { if (!ContainsKey(key)) { this[key] = value; } else { throw new ArgumentException("An attempt was made to insert a duplicate key in the cache."); } } public bool ContainsKey(TKey key) { return data.ContainsKey(key); } public ICollection<TKey> Keys { get { return data.Keys; } } public bool Remove(TKey key) { bool existed = data.Remove(key); lruList.Remove(key); return existed; } public bool TryGetValue(TKey key, out TValue value) { return data.TryGetValue(key, out value); } public ICollection<TValue> Values { get { return data.Values; } } public TValue this[TKey key] { get { var value = data[key]; lruList.Remove(key); lruList.Add(key); return value; } set { data[key] = value; lruList.Remove(key); lruList.Add(key); if (data.Count > capacity) { data.Remove(lruList.First); lruList.RemoveFirst(); } } } public void Add(KeyValuePair<TKey, TValue> item) { Add(item.Key, item.Value); } public void Clear() { data.Clear(); lruList.Clear(); } public bool Contains(KeyValuePair<TKey, TValue> item) { return dataAsCollection.Contains(item); } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { dataAsCollection.CopyTo(array, arrayIndex); } public int Count { get { return data.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(KeyValuePair<TKey, TValue> item) { bool removed = dataAsCollection.Remove(item); if (removed) { lruList.Remove(item.Key); } return removed; } public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return dataAsCollection.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.IEnumerable)data).GetEnumerator(); } } }


Existe un virtualizador RAM de fuente abierta y eficiente que utiliza el algoritmo MRU para mantener los objetos de referencia más recientes en la memoria y utiliza una tienda de respaldo rápida y liviana (en disco) para "paginación".

Aquí hay un enlace en Code Project para un mini artículo al respecto: http://www.codeproject.com/Tips/827339/Virtual-Cache

Espero que le sea útil.




Sin embargo, el bloque de aplicación de caché y el caché ASP.NET son ambas opciones, aunque lo hacen LRU, el único tipo de utilización de disco que ocurre es mediante paginación de memoria. Creo que hay formas en que puede optimizar esto que son más específicas para su objetivo de obtener un mejor resultado. Aquí hay algunos pensamientos:

  • Como se trata de una aplicación ASP.NET, ¿por qué no generar las imágenes, escribirlas en el disco y cuando el navegador solicita la siguiente página, IIS la debe publicar? Eso mantiene su proceso de trabajo ASP.NET más bajo al mismo tiempo que permite a IIS hacer lo que es bueno.
  • Use estadísticas sobre cómo un usuario interactúa. LRU como se implementa en la memoria caché de ASP.NET normalmente se aplicará a los archivos de imagen individuales, no mucho uso para este escenario por el sonido de la misma. En cambio, tal vez alguna lógica como: "Si el usuario ha desplazado páginas X en los últimos Y segundos, entonces general las próximas imágenes X * Y". Los usuarios que se desplazan rápidamente obtendrán más páginas generadas; los usuarios que lean lentamente necesitarán menos memoria caché.
  • Haga que IIS sirva imágenes del disco y use un controlador HTTP personalizado para las imágenes que realmente desea controlar el almacenamiento en caché.
  • Haga que el navegador solicite los archivos con anticipación y confíe en el almacenamiento en caché del navegador.
  • Una vez que se le ha entregado una imagen al cliente, ¿es justo decir que puede ser eliminada del caché? Eso podría reducir la huella sustancialmente.

Sin duda, evitaría usar una tabla hash simple.


Una clásica situación de compromiso. Mantener todo en la memoria será rápido a costa de un consumo de memoria enormemente aumentado, mientras que la recuperación desde el disco reduce el consumo de memoria, pero no es tan eficiente. Sin embargo, ¡ya sabes todo esto!

La clase integrada System.Web.Caching.Cache es genial, y la he usado muchas veces en mis aplicaciones ASP.NET (aunque principalmente para el almacenamiento en caché de registros de la base de datos), sin embargo, el inconveniente es que el caché solo se ejecutará en una máquina (por lo general, un único servidor web) y no se puede distribuir entre varias máquinas.

Si es posible "arrojar algo de hardware" al problema, y ​​no necesariamente tiene que ser un hardware costoso, solo cajas con mucha memoria, siempre se puede ir con una solución de caché distribuida. Esto le dará mucha más memoria para jugar al tiempo que conserva (casi) el mismo nivel de rendimiento.

Algunas opciones para una solución de almacenamiento en caché distribuida para .NET son:

Memcached.NET

indeXus.Net

o incluso el propio proyecto Velocity Microsoft.