Кэш объектов для C # - PullRequest
       68

Кэш объектов для C #

22 голосов
/ 24 февраля 2009

Я делаю просмотрщик документов для некоторого формата документа. Для упрощения предположим, что это программа просмотра PDF, Настольное приложение . Одним из требований к программному обеспечению является скорость рендеринга. Итак, прямо сейчас я кэширую изображение для следующих страниц, пока пользователь просматривает документ.

Это работает, пользовательский интерфейс очень отзывчивый, и кажется, что приложение может отображать страницы практически мгновенно ... по цене: использование памяти иногда достигает 600 МБ. Я кеширую все это в памяти.

Теперь я могу кэшировать на диск, я знаю, но делать это все время заметно медленнее. Что я хотел бы сделать, так это реализовать некоторый кеш (LRU?), Где некоторые из кэшированных страниц (объекты изображений) находятся в памяти, а большинство из них - на диске.

Прежде чем я приступлю к этому, есть ли что-то в фреймворке или какой-нибудь библиотеке, которая сделает это для меня? Это кажется довольно распространенной проблемой. (Это настольное приложение, а не ASP.NET)

В качестве альтернативы, у вас есть другие идеи для этой проблемы?

Ответы [ 9 ]

21 голосов
/ 26 февраля 2009

Я написал LRU Cache и некоторые тестовые случаи, не стесняйтесь использовать его.

Вы можете прочитать источник в моем блоге .

Для ленивых (здесь минус тестовые случаи):

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();
        }

    }
}
13 голосов
/ 16 марта 2011

Для .NET 4.0 вы также можете использовать MemoryCache из System.Runtime.Caching.

http://msdn.microsoft.com/en-us/library/system.runtime.caching.aspx

8 голосов
/ 24 февраля 2009

Существуют шаблоны и методы. Корпоративная библиотека (точнее, Блок кэширования приложений ), но IMO имеет тенденцию быть чрезмерно сложным и чрезмерно сложным.

7 голосов
/ 24 февраля 2009

.NET Framework всегда могла хранить слабые ссылки на объекты.

По сути, слабые ссылки - это ссылки на объекты, которые среда выполнения считает "неважными" и которые могут быть удалены сборкой мусора в любой момент времени. Это может быть использовано, например, для кэширования, но вы не сможете контролировать, что выбирается, а что нет.

С другой стороны, он очень прост в использовании и может быть именно тем, что вам нужно.

Dave

4 голосов
/ 24 февраля 2009

Классическая компромиссная ситуация. Хранение всего в памяти будет быстрым за счет огромного увеличения потребления памяти, в то время как извлечение с диска уменьшает потребление памяти, но не так эффективно. Тем не менее, вы уже знаете все это!

Встроенный класс System.Web.Caching.Cache великолепен, и я много раз использовал его в своих приложениях ASP.NET много раз (хотя в основном для кэширования записей базы данных) однако недостатком является то, что кэш будет работать только на одном компьютере (обычно это единственный веб-сервер) и не может быть распределен по нескольким машинам.

Если есть возможность «бросить какое-то оборудование» в проблему, и это не обязательно должно быть дорогостоящее оборудование, просто коробки с большим количеством памяти, вы всегда можете воспользоваться решением распределенного кэширования. Это даст вам гораздо больше памяти для игры, сохранив (почти) тот же уровень производительности.

Некоторые варианты решения распределенного кэширования для .NET:

Memcached.NET

indeXus.Net

или даже собственный проект Microsoft * Velocity .

3 голосов
/ 24 февраля 2009

Как вы реализуете свой кеш?

Вы можете использовать класс Cache из System.Web.Caching даже в не веб-приложениях, и он будет очищать элементы на основе LRU, если / когда ему потребуется память.

В не-веб-приложении вам нужно использовать HttpRuntime.Cache для доступа к экземпляру Cache.

Обратите внимание, что в документации говорится, что класс Cache не предназначен для использования вне ASP.NET, хотя он всегда работал для меня. (Я никогда не полагался на это ни в одном критически важном приложении.)

0 голосов
/ 11 февраля 2015

Существует эффективный виртуализатор ОЗУ с открытым исходным кодом, который использует алгоритм MRU для хранения самых свежих объектов, на которые ссылаются, в памяти и использует быстрое и легкое резервное хранилище (на диске) для «подкачки страниц».

Вот ссылка в Code Project на мини-статью об этом: http://www.codeproject.com/Tips/827339/Virtual-Cache

Надеюсь, вы найдете это полезным.

0 голосов
/ 19 января 2013

В дополнение к rsbarro ответ для использования MemoryCache Я рекомендую использовать PostSharp AOP, как описано в

http://www.codeproject.com/Articles/493971/Leveraging-MemoryCache-and-AOP-for-expensive-calls

0 голосов
/ 24 февраля 2009

Кэширование блока приложения и кеша ASP.NET - оба варианта, однако, хотя они и используют LRU, единственный вид использования диска, который происходит, - это подкачка памяти. Я думаю, что есть способы, которые вы можете оптимизировать, которые более специфичны для вашей цели, чтобы получить лучший результат. Вот некоторые мысли:

  • Так как это приложение ASP.NET, почему бы не сгенерировать изображения, записать их на диск, и когда браузер запросит следующую страницу, IIS предоставит их. Это поддерживает ваш рабочий процесс ASP.NET ниже, позволяя IIS делать то, что хорошо.
  • Использовать статистику о том, как взаимодействует пользователь. LRU, реализованный в кеше ASP.NET, обычно применяется к отдельным файлам изображений, что не очень полезно для этого сценария. Вместо этого может быть какая-то логика, например: «Если пользователь прокручивал X страниц за последние Y секунд, то обобщает следующие X * Y изображения». При быстрой прокрутке пользователи получают больше страниц; пользователям, которые читают медленно, потребуется меньше кэшироваться.
  • Пусть IIS будет обслуживать изображения с диска и использовать собственный обработчик HTTP для изображений, которыми вы действительно хотите управлять кэшированием.
  • Пусть браузер также запрашивает файлы заранее и полагается на кэширование браузера.
  • Как только изображение будет передано клиенту, справедливо ли будет сказать, что его можно в значительной степени удалить из кэша? Это может существенно уменьшить площадь.

Хотя я бы определенно избегал использования простой хеш-таблицы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...