C# 缓存IEnumerable的性能<;T>;实施

C# 缓存IEnumerable的性能<;T>;实施,c#,performance,multithreading,caching,ienumerable,C#,Performance,Multithreading,Caching,Ienumerable,[编辑] 新方法使用扩展方法System.Linq.EnumerableEx.MemoizeAll()解决了下面概述的问题 在内部,MemoizeAll()使用一个System.Linq.EnumerableEx.memoizealnumerable(可在System.Interactive程序集中找到),它类似于我的ThreadSafeCachedEnumerable(sorta) 下面是一个精心设计的示例,它非常缓慢地打印可枚举(数字1-10)的内容,然后第二次快速打印内容(因为它缓存了值)

[编辑]

新方法使用扩展方法
System.Linq.EnumerableEx.MemoizeAll()
解决了下面概述的问题

在内部,
MemoizeAll()
使用一个
System.Linq.EnumerableEx.memoizealnumerable
(可在System.Interactive程序集中找到),它类似于我的
ThreadSafeCachedEnumerable
(sorta)

下面是一个精心设计的示例,它非常缓慢地打印可枚举(数字1-10)的内容,然后第二次快速打印内容(因为它缓存了值):

//使用Thread.Sleep()模拟工作,创建一个包含数字1-10的可枚举项
var slowEnum=EnumerableEx.Generate(1,currentNum=>(currentNum currentNum,previousNum=>{Thread.Sleep(250);返回previousNum+1;});
//这将使用一个缓存每个值的枚举来装饰慢速枚举。
var cachedEnum=slowEnum.MemoizeAll();
//打印数字
foreach(cachedEnum中的var num.Repeat(2))
{
控制台写入线(num);
}
[/EDIT]

你好,多线程大师

我创建了ThreadSafeCachedEnumerable类,目的是在重用长时间运行的查询时提高性能。其想法是从IEnumerable中获取一个枚举数,并在每次调用MoveNext()时向缓存中添加项。以下是我当前的实现:

/// <summary>
/// Wraps an IEnumerable&lt;T&gt; and provides a thread-safe means of caching the values."/>
/// </summary>
/// <typeparam name="T"></typeparam>
class ThreadSafeCachedEnumerable<T> : IEnumerable<T>
{
    // An enumerator from the original IEnumerable<T>
    private IEnumerator<T> enumerator;

    // The items we have already cached (from this.enumerator)
    private IList<T> cachedItems = new List<T>();

    public ThreadSafeCachedEnumerable(IEnumerable<T> enumerable)
    {
        this.enumerator = enumerable.GetEnumerator();
    }

    #region IEnumerable<T> Members

    public IEnumerator<T> GetEnumerator()
    {
        // The index into the sequence
        int currentIndex = 0;

        // We will break with yield break 
        while (true)
        {
            // The currentIndex will never be decremented,
            // so we can check without locking first
            if (currentIndex < this.cachedItems.Count)
            {
                var current = this.cachedItems[currentIndex];
                currentIndex += 1;
                yield return current;
            }
            else
            {
                // If !(currentIndex < this.cachedItems.Count),
                // we need to synchronize access to this.enumerator
                lock (enumerator)
                {
                    // See if we have more cached items ...
                    if (currentIndex < this.cachedItems.Count)
                    {
                        var current = this.cachedItems[currentIndex];
                        currentIndex += 1;
                        yield return current;
                    }
                    else
                    {
                        // ... otherwise, we'll need to get the next item from this.enumerator.MoveNext()
                        if (this.enumerator.MoveNext())
                        {
                            // capture the current item and cache it, then increment the currentIndex
                            var current = this.enumerator.Current;
                            this.cachedItems.Add(current);
                            currentIndex += 1;
                            yield return current;
                        }
                        else
                        {
                            // We reached the end of the enumerator - we're done
                            yield break;
                        }
                    }
                }
            }
        }
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    #endregion
}
//
///包装IEnumerableT并提供线程安全的方法来缓存值。“/>
/// 
/// 
类ThreadSafeCachedEnumerable:IEnumerable
{
//来自原始IEnumerable的枚举数
私有IEnumerator枚举器;
//我们已经缓存的项(来自此.enumerator)
private IList cachedItems=新列表();
public ThreadSafeCachedEnumerable(IEnumerable enumerable)
{
this.enumerator=enumerable.GetEnumerator();
}
#区域可数成员
公共IEnumerator GetEnumerator()
{
//将索引放入序列中
int currentIndex=0;
//我们将以产量突破来打破僵局
while(true)
{
//currentIndex永远不会递减,
//所以我们可以先检查而不锁定
if(currentIndex

当缓存中没有更多的项时,我只是简单地“锁定(this.enumerator)”,以防另一个线程正要添加另一项(我认为从两个线程调用this.enumerator上的MoveNext()是个坏主意)

在检索以前缓存的项目时,性能非常好,但在第一次获取多个项目时(由于不断锁定),性能开始下降。有没有提高性能的建议


谢谢!

一些建议:

  • 现在普遍接受的做法是不让容器类负责锁定。例如,调用缓存枚举器的人可能还希望在枚举时阻止向容器添加新条目,这意味着锁定将发生两次。因此,最好将此责任推迟到cal莱尔
  • 您的缓存依赖于枚举数始终按顺序返回项目,这是不能保证的。最好使用
    字典
    哈希集
    。同样,在调用之间可能会删除项目,从而使缓存无效
  • 通常不建议在公开访问的对象上建立锁。这包括包装枚举器。例外情况是可以想象的,例如,当您绝对确定您绝对确定您是唯一持有对正在枚举的容器类的引用的实例时。这在很大程度上也是无效的我把我的反对意见写在#2下面

  • 在.NET中锁定通常非常快(如果没有争用)。分析是否将锁定确定为性能问题的根源?在基础枚举器上调用
    MoveNext
    需要多长时间

    此外,当前的代码不是线程安全的。您不能在一个线程上安全地调用
    this.cachedItems[currentIndex]
    (在
    if(currentIndex)中
    
    /// <summary>
    /// Wraps an IEnumerable&lt;T&gt; and provides a thread-safe means of caching the values."/>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class ThreadSafeCachedEnumerable<T> : IEnumerable<T>
    {
        // An enumerator from the original IEnumerable<T>
        private IEnumerator<T> enumerator;
    
        // The items we have already cached (from this.enumerator)
        private IList<T> cachedItems = new List<T>();
    
        public ThreadSafeCachedEnumerable(IEnumerable<T> enumerable)
        {
            this.enumerator = enumerable.GetEnumerator();
        }
    
        #region IEnumerable<T> Members
    
        public IEnumerator<T> GetEnumerator()
        {
            // The index into the sequence
            int currentIndex = 0;
    
            // We will break with yield break 
            while (true)
            {
                // The currentIndex will never be decremented,
                // so we can check without locking first
                if (currentIndex < this.cachedItems.Count)
                {
                    var current = this.cachedItems[currentIndex];
                    currentIndex += 1;
                    yield return current;
                }
                else
                {
                    // If !(currentIndex < this.cachedItems.Count),
                    // we need to synchronize access to this.enumerator
                    lock (enumerator)
                    {
                        // See if we have more cached items ...
                        if (currentIndex < this.cachedItems.Count)
                        {
                            var current = this.cachedItems[currentIndex];
                            currentIndex += 1;
                            yield return current;
                        }
                        else
                        {
                            // ... otherwise, we'll need to get the next item from this.enumerator.MoveNext()
                            if (this.enumerator.MoveNext())
                            {
                                // capture the current item and cache it, then increment the currentIndex
                                var current = this.enumerator.Current;
                                this.cachedItems.Add(current);
                                currentIndex += 1;
                                yield return current;
                            }
                            else
                            {
                                // We reached the end of the enumerator - we're done
                                yield break;
                            }
                        }
                    }
                }
            }
        }
    
        #endregion
    
        #region IEnumerable Members
    
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    
        #endregion
    }