C#的对象缓存

时间:2009-02-24 09:44:39

标签: c# .net caching

我正在为一些文档格式做文档查看器。为了方便起见,我们假设这是一个PDF查看器,一个桌面应用程序。该软件的一个要求是渲染速度。所以,现在,当用户滚动浏览文档时,我正在为下一页缓存图像。

这很有效,UI非常灵敏,似乎应用程序几乎可以立即呈现页面......但代价是:内存使用量有时会达到600MB。我将它全部缓存在内存中。

现在,我知道,我可以缓存到磁盘,但是一直这样做会明显变慢。我想要做的是实现一些缓存(LRU?),其中一些缓存页面(图像对象)在内存中,大多数都在磁盘上。

在我开始这个之前,框架中有什么东西或某些库可以为我做这个吗?这似乎是一个非常普遍的问题。 (这是一个桌面应用程序,而不是ASP.NET)

或者,您对此问题有其他想法吗?

9 个答案:

答案 0 :(得分:21)

我写了一个LRU Cache和一些测试用例,随时可以使用它。

您可以在my blog上阅读来源。

对于懒惰(这里是减去测试用例):

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

    }
}

答案 1 :(得分:13)

对于.NET 4.0,您还可以使用MemoryCache中的System.Runtime.Caching

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

答案 2 :(得分:8)

patterns & practices Enterprise Library(更具体地说,Caching Application Block),但IMO往往过度设计并且过于复杂。

答案 3 :(得分:7)

.NET Framework始终能够将weak references保留在对象上。

基本上,弱引用是对运行时认为“不重要”的对象的引用,并且可以在任何时间点通过垃圾回收运行来删除。例如,这可以用来缓存事物,但是你无法控制被收集的东西和不被收集的内容。

另一方面,它使用起来非常简单,可能就是您所需要的。

戴夫

答案 4 :(得分:4)

经典的权衡情况。将所有内容保存在内存中会以大量增加的内存消耗为代价,而从光盘中检索可以减少内存消耗,但效果不是很好。但是,你已经知道了这一切!

内置的System.Web.Caching.Cache类非常棒,我在ASP.NET应用程序中使用它很多次(尽管主要用于数据库记录缓存)但是,缺点是缓存只能在一台计算机上运行(通常是唯一的Web服务器),并且不能分布在多台计算机上。

如果有可能“抛出一些硬件”来解决问题,并且它不一定需要昂贵的硬件,只需要有大量内存的盒子,你就可以随时使用分布式缓存解决方案。这将为您提供更多的内存,同时保持(几乎)相同的性能水平。

.NET的分布式缓存解决方案的一些选项是:

Memcached.NET

indeXus.Net

甚至是微软自己的Velocity项目。

答案 5 :(得分:3)

您如何实施缓存?

即使在非Web应用程序中,您也可以使用Cache class from System.Web.Caching,如果/需要内存,它将以LRU为基础清除项目。

在非网络应用程序中,您需要使用HttpRuntime.Cache来访问Cache实例。

请注意,该文档指出Cache类不打算在ASP.NET之外使用,尽管它总是适用于我。 (但我从来没有在任何关键任务应用程序中依赖它。)

答案 6 :(得分:0)

高速缓存应用程序块和ASP.NET高速缓存都是选项,但是,尽管它们执行LRU,但发生的唯一磁盘利用率是内存分页。我认为有一些方法可以优化这一点,更具体地针对您的目标,以获得更好的输出。以下是一些想法:

  • 由于它是一个ASP.NET应用程序,为什么不生成图像,将它们写入磁盘,当浏览器请求下一页时,IIS会为其提供服务。这可以让你的ASP.NET工作进程更低,同时让IIS做它擅长的事情。
  • 使用有关用户如何互动的统计信息。在ASP.NET缓存中实现的LRU通常适用于单个图像文件 - 通过它的声音对此场景没有多大用处。相反,也许有些逻辑如下:“如果用户在最后Y秒内滚动了X页,那么一般是下一个X * Y图像”。用户快速滚动会生成更多页面;用户阅读缓慢需要较少缓存。
  • 让IIS从磁盘提供图像,并为您真正想要控制缓存的图像使用自定义HTTP处理程序。
  • 让浏览器提前请求文件,并依赖浏览器缓存。
  • 一旦将图像提供给客户端,可以说它几乎可以从缓存中删除吗?这可以大大减少占地面积。

我当然会避免使用普通的哈希表。

答案 7 :(得分:0)

答案 8 :(得分:0)

有一个高效的开源RAM虚拟器,它使用MRU算法将最新鲜的引用对象保留在内存中,并使用快速,轻量级的后备存储(在磁盘上)进行“分页”。

以下是Code Project中有关它的迷你文章的链接:http://www.codeproject.com/Tips/827339/Virtual-Cache

我希望你觉得它很有用。