C# 的对象缓存

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/581119/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 09:07:42  来源:igfitidea点击:

Object cache for C#

c#.netcaching

提问by moogs

I'm doing a document viewer for some document format. To make it easier, let's say this is a PDF viewer, a Desktop application. One requirement for the software is the speed in rendering. So, right now, I'm caching the image for the next pages while the user is scrolling through the document.

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

This works, the UI is very responsive and it seems like the application is able to render the pages almost instantly....at a cost : the memory usage sometimes goes to 600MB. I cache it all in memory.

这是有效的,用户界面非常敏感,并且应用程序似乎能够几乎立即呈现页面......代价是:内存使用有时会达到 600MB。我把它全部缓存在内存中。

Now, I can cache to disk, I know, but doing that all the time is noticeably slower. What I would like to do is implement some cache (LRU?), where some of the cached pages (image objects) are on memory and most of them are on disk.

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

Before I embark on this, is there something in the framework or some library out there that will do this for me? It seems a pretty common enough problem. (This is a desktop application, not ASP.NET)

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

Alternatively, do you have other ideas for this problem?

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

采纳答案by Sam Saffron

I wrote an LRU Cache and some test cases, feel free to use it.

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

You can read through the source on my blog.

您可以通读我博客上的源代码。

For the lazy (here it is minus the test cases):

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

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

    }
}

回答by Anton Gogolev

There's patterns & practices Enterprise Library(more specifically, Caching Application Block), but it IMO tends to be over-engineered and overly complex.

模式和实践企业库(更具体地说,缓存应用程序块),但 IMO 往往过度设计且过于复杂。

回答by LukeH

How are you implementing your cache?

你如何实现你的缓存?

You can use the Cacheclass from System.Web.Caching, even in non-web applications, and it will purge items on an LRU basis if/when it needs the memory.

您可以使用Cachefrom 类System.Web.Caching,即使在非 Web 应用程序中,如果/当它需要内存时,它将基于 LRU 清除项目。

In a non-web application you'll need to use HttpRuntime.Cacheto access the Cacheinstance.

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

Note that the documentation states that the Cacheclass isn't intended to be used outside of ASP.NET, although it's always worked for me. (I've never relied on it in any mission-critical app though.)

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

回答by Dave Van den Eynde

The .NET Framework has always had the ability to keep weak referencesto objects.

.NET Framework 始终能够保持对对象的弱引用

Basically, weak references are references to objects that the runtime considers "unimportant" and that may be removed by a garbage collection run at any point in time. This can be used, for example, to cache things, but you'd have no control over what gets colected and what not.

基本上,弱引用是对运行时认为“不重要”的对象的引用,并且可以在任何时间点被垃圾收集运行删除。例如,这可用于缓存事物,但您无法控制收集什么,不收集什么。

On the other hand, it's very simple to use and it may just be what you need.

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

Dave

戴夫

回答by Paul Stovell

Caching application block and ASP.NET cache are both options however, although they do LRU, the only kind of disk utilization that happens is by memory paging. I think there are ways you can optimize this that are more specific to your goal to get a better output. Here are some thoughts:

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

  • Since it's an ASP.NET app, why not generate the images, write them to disk, and when the browser requests the next page have IIS serve it up. That keeps your ASP.NET worker process lower while letting IIS do what it's good at.
  • Use statistics about how a user interacts. LRU as implemented in the ASP.NET cache will typically apply to the individual image files - not much use for this scenario by the sounds of it. Instead, maybe some logic like: "If the user has scrolled X pages in the last Y seconds, then general the next X*Y images". Users scrolling quickly will get more pages generated; users reading slowly will need less cached.
  • Have IIS serve images from disk, and use a custom HTTP handler for images you really want to control the caching of.
  • Have the browser request the files ahead of time too, and rely on browser caching.
  • Once an image has been served to the client, is it fair to say it can pretty much be removed from the cache? That could reduce the footprint substantially.
  • 既然它是一个 ASP.NET 应用程序,为什么不生成图像,将它们写入磁盘,当浏览器请求下一个页面时,让 IIS 为其提供服务。这可以降低 ASP.NET 工作进程的速度,同时让 IIS 做它擅长的事情。
  • 使用有关用户如何交互的统计信息。在 ASP.NET 缓存中实现的 LRU 通常适用于单个图像文件 - 在这种情况下,它的声音没有太大用处。相反,可能有一些逻辑,例如:“如果用户在过去 Y 秒内滚动了 X 个页面,则一般显示下一个 X*Y 个图像”。用户快速滚动会生成更多页面;阅读速度慢的用户将需要较少的缓存。
  • 让 IIS 从磁盘提供图像,并为您真正想要控制缓存的图像使用自定义 HTTP 处理程序。
  • 让浏览器也提前请求文件,并依赖浏览器缓存。
  • 一旦图像已提供给客户端,是否可以说它几乎可以从缓存中删除?这可以大大减少足迹。

I'd certainly avoid using a plain hash table though.

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

回答by CraigTP

A classic trade-off situation. Keeping everything in memory will be fast at the cost of massively increased memory consumption, whilst retrieving from disc decreases memory consumption, but isn't as performant. However, you already know all this!

一个经典的权衡情况。将所有内容保存在内存中会很快,但会大量增加内存消耗,而从磁盘中检索会减少内存消耗,但性能不高。然而,你已经知道这一切!

The built-in System.Web.Caching.Cacheclass is great, and I've used it to good effect many times myself in my ASP.NET applications (although mostly for database record caching), however, the drawback is that the cache will only run on one machine (typically a sole web server) and cannot be distributed across multiple machines.

内置的System.Web.Caching.Cache类很棒,我自己在 ASP.NET 应用程序中多次使用它,效果很好(虽然主要用于数据库记录缓存),但是,缺点是缓存只会在一台机器上运行(通常是一个单独的 Web 服务器),不能分布在多台机器上。

If it's possible to "throw some hardware" at the problem, and it doesn't necessarily need to be expensive hardware, just boxes with plenty of memory, you could always go with a distributed caching solution. This will give you much more memory to play with whilst retaining (nearly) the same level of performance.

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

Some options for a distributed caching solution for .NET are:

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

Memcached.NET

内存缓存.NET

indeXus.Net

索引网

or even Microsoft's own Velocityproject.

甚至微软自己的Velocity项目。

回答by rsbarro

For .NET 4.0, you can also use the MemoryCachefrom System.Runtime.Caching.

对于 .NET 4.0,您还可以使用MemoryCachefrom System.Runtime.Caching.

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

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

回答by Michael Freidgeim

回答by Gerardo Recinto

There is an efficient, open sourced RAM virtualizer that uses MRU algorithm to keep freshest referenced objects in-memory and uses a fast, lightweight backing store (on Disk) for "paging".

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

Here is link in Code Project for a mini-article about it: http://www.codeproject.com/Tips/827339/Virtual-Cache

这是代码项目中关于它的迷你文章的链接:http: //www.codeproject.com/Tips/827339/Virtual-Cache

I hope you find it useful.

希望对你有帮助。