繁体   English   中英

元素大小会影响C#集合的性能吗?

[英]element size influencing C# collection performance?

在完成改善一段代码性能的任务后,我遇到了以下现象。 我在泛型队列中有大量的引用类型集合,并且要逐一删除和处理元素,然后将它们添加到另一个泛型集合中。

似乎元素越大,将元素添加到集合中所花费的时间就越多。

为了将问题缩小到代码的相关部分,我编写了一个测试(省略对元素的处理,仅执行插入操作):

    class Small 
    {
        public Small()
        {
            this.s001 = "001";
            this.s002 = "002";
        }
        string s001;
        string s002;
    }


    class Large
    {
        public Large()
        {
            this.s001 = "001";
            this.s002 = "002";
            ...
            this.s050 = "050";

        }
        string s001;
        string s002;
        ...
        string s050;
    }

    static void Main(string[] args)
    {
        const int N = 1000000;
        var storage = new List<object>(N);
        for (int i = 0; i < N; ++i)
        {
            //storage.Add(new Small());
            storage.Add(new Large());
        }

        List<object> outCollection = new List<object>();
        Stopwatch sw = new Stopwatch();

        sw.Start();
        for (int i = N-1; i > 0; --i)
        {          
            outCollection.Add(storage[i];);
        }
        sw.Stop();

        Console.WriteLine(sw.ElapsedMilliseconds);
    }

在使用Small类的测试机上,运行大约需要25-30毫秒,而使用Large类则需要40-45毫秒。 我知道outCollection必须不时地增长以便能够存储所有项目,因此存在一些动态内存分配。 但是给定初始收集大小,差异甚至更加明显:“小型”对象为11-12毫秒,大型对象为35-38毫秒。

我有点惊讶,因为它们是引用类型,所以我期望这些集合仅对Small / Large实例的引用起作用。 我已经阅读了Eric Lippert的相关文章 ,并且知道不应将引用视为指针。 同时,AFAIK当前将它们实现为指针,并且它们的大小以及集合的性能应独立于元素大小。

我决定在这里提出一个问题,希望有人可以解释或帮助我了解这里发生的事情。 除了性能提高外,我真的很好奇幕后发生的事情。

更新:尽管必须承认我不是使用探查器的专家,但使用诊断工具对数据进行分析并没有多大帮助。 今天晚些时候,我将收集更多数据以查找瓶颈所在。

GC上的压力当然很高,尤其是对于Large实例。 但是一旦创建了实例并将其存储在storage集合中,并且程序进入循环,就不会再触发任何集合,并且内存使用也不会显着增加( outCollction已经预先分配)。

当然,大部分CPU时间都花费在内存分配(JIT_New)上,大约占62%,唯一的其他重要条目是函数名称包含样本专有样本包含样本%专有样本%模块名称System.Collections.Generic.List`1 [ System .__ Canon]。添加约7%。

对于100万个项目,预分配的outCollection大小为800万个字节(与storage大小相同); 人们可能会怀疑64位地址存储在集合中。

可能是我没有正确使用工具,或者没有正确解释结果的经验,但是探查器并没有帮助我更深入地了解原因。 如果循环未触发集合,并且仅在两个预分配的集合之间复制指针,那么项目大小如何引起任何差异? 在这两种情况下,高速缓存命中率/未命中率应该大致相同,因为在这两种情况下,循环都是在“地址”列表上进行迭代。

感谢到目前为止的所有帮助,我将收集更多数据,如果发现任何问题,请在此处进行更新。

怀疑以上至少一项操作(也许是类型检查)将需要取消引用。 然后,许多Small可能并排放置在堆上,因此共享高速缓存行这一事实可能会造成一定程度的差异(当然,与Large相比,它们中的更多可以共享一条高速缓存行)。

此外,您还可以按照分配它们的相反顺序访问它们,以最大程度地获得这种好处。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM