Подтвердить что ты не робот

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

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

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

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

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

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

4b9b3361

Ответ 1

Я написал 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();
        }

    }
}

Ответ 4

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

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

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

Dave

Ответ 5

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

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

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

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

Memcached.NET

indeXus.Net

или даже собственный

Ответ 6

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

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

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

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

Ответ 7

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

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

Я бы, конечно, избегал использования простой хеш-таблицы.

Ответ 9

Существует эффективный открытый открытый RAM-виртуализатор, который использует алгоритм MRU для хранения самых свежих ссылочных объектов в памяти и использует быстрое и легкое хранилище резервных копий (на диске) для "подкачки".

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

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