惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Announcements
Recent Announcements
IT之家
IT之家
Google DeepMind News
Google DeepMind News
罗磊的独立博客
爱范儿
爱范儿
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
U
Unit 42
MongoDB | Blog
MongoDB | Blog
S
SegmentFault 最新的问题
B
Blog
博客园 - 叶小钗
月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

博客园 - Jeffrey Zhao

两则.NET高级技术人员的招聘信息 上周末Jscex项目介绍的幻灯片 Jscex使用BSD授权协议正式发布 Jscex项目现状:UglifyJS解析器及AOT编译器 我们不是牛人,所以还是老老实实跟着兴趣走吧 分清“语言/规范”以及“平台/实现”,以及跨平台.NET开发 “花钱”购买App Hub Membership终于成功了 第三届nBazaar技术交流会开始报名 Silverlight与微软技术(下):微软技术与技术学习 Silverlight与微软技术(上):微软抛弃Silverlight了么? 浅谈这次ASP.NET的Padding Oracle Attack相关内容 综述:编程语言的发展趋势及未来方向 盛大创新院赞助第二届.NET技术交流会开始报名了! NDC 2010视频下载:看看其他微软平台程序员们都在做什么 关于using及foreach的一点看法,及其他 盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载 盛大创新院赞助首届.NET技术交流会即将召开 浅谈Java 7的闭包与Lambda表达式之优劣 通知:正式迁移至新博客
单链表与List<T>究竟哪个遍历速度快?
Jeffrey Zhao · 2010-07-02 · via 博客园 - Jeffrey Zhao

firelong雄文又起,不过说实话,可能是这篇文章写的太简单了,其中的理由和结论都听得不是很明白。当然有一段话的意思很清楚(原话):“C#事件的背后是一个委托链表(单链表),单链表的遍历调用性能远低于数组链表(List<T>)”。这句话让我比较纳闷,因为从我的直觉来说,两种做法之间即使性能有差距,也不该是“远高于”啊。不过我提出这个疑问之后,firelong回应到(还是原话)“间接指针移动,和i++哪个快慢很难辨析吗?”于是我想,还是做个试验吧。试验代码很简单:

public class Node
{
    public Node Next;
    public int Value;
}

public class Item
{
    public int Value;
}

class Program
{
    static Node GetSingleList(int length)
    {
        Node root = null;
        for (int i = 0; i < length; i++)
        {
            root = new Node { Next = root, Value = 0 };
        }

        return root;
    }

    static List<Item> GetList(int length)
    {
        return Enumerable.Range(0, length)
            .Select(_ => new Item { Value = 0 }).ToList();
    }

    static void Main(string[] args)
    {
        int length = 10000;
        int iteration = 100000;
        int count = 0;

        var root = GetSingleList(length);
        var watch1 = Stopwatch.StartNew();
        for (int t = 0; t < iteration; t++)
        {
            var node = root;
            while (node != null)
            {
                count += node.Value;
                node = node.Next;
            }
        }
        Console.WriteLine("{0} (Node List)", watch1.Elapsed);

        GC.Collect();

        var list = GetList(length);
        var watch2 = Stopwatch.StartNew();
        for (int t = 0; t < iteration; t++)
        {
            for (int i = 0; i < list.Count; i++)
            {
                count += list[i].Value;
            }
        }
        Console.WriteLine("{0} (List<Item>)", watch2.Elapsed);
    }
}

使用Release模式编译,并且保证VS不会Attach Debugger之后,执行几遍结果如下:

00:00:02.0731861 (Node List)
00:00:02.4602990 (List<Item>)

00:00:02.3176291 (Node List)
00:00:02.2912638 (List<Item>)

00:00:02.1539642 (Node List)
00:00:02.4635390 (List<Item>)

我的直觉是这样的:如果使用List<T>来遍历,除了i++操作以外,还需要计算偏移量,根据List内部的数组来找到下一个对象的地址,再根据这个地址去访问下一个对象,而单向链表的遍历做的事情会少一些,只要一个接一个的访问就行了。从结果上看,总体说来差别不大,并没有出现firelong所说的“单向链表遍历性能远低于List<T>”的情况出现。而且事实上,这点性能真的有关系吗?这里累计遍历了10亿个元素,才产生了零点几秒的差距,而对于一个事件来说,您会为它添加多少个Handler,又会调用多少次呢?

我一直不愿意多谈性能方面的问题,因为我实在没有什么可谈的,该谈的都谈过了。而且,我在这方面也没吃过什么苦头,即使遇到一些小问题,也是因为代码写的效率不高,简单优化以后就没有问题了。不过firelong的新文章谈的是设计,例如觉得C#——应该说是.NET的事件机制很糟糕,yield功能没有什么用,让C#语法变的很臃肿等等。我喜欢语言,我很喜欢谈语言设计,所以这些方面倒有可以讨论的地方。只是最近事情有些多,以后我会写的,您可以关注我的新博客

这篇文章写的比较匆忙,也没有什么太可取的内容,便先简单试试看这趟水有多深吧。

补充说明三点:

有朋友提出,数组里的对象在内存里的分布是连续的,单向链表不连续,因此考虑到如果换页,缓存等关系,基于数组的效率会比较高。我的看法是:如果您遍历的是int[],那么每个int值的在内存里自然是连续的,但是这里访问的是Item[]这样的引用元素的数组,连续分布的只是对象的地址,而要获得最终对象,还得再根据地址去访问某个内存,它就不能保证连续性了。同样道理,有朋友说,真实情况下Node这样的对象不是连续访问的,我认为这个差别也不会偏袒向其中任何一方。也有朋友认为,不管怎么说数组里的地址是连续的,局部性还是更好。不过我认为,对于单向链表来说,如访问Node的Value时,Next也会一并加载到缓存里去,同一个对象的字段是紧挨着的也是.NET出于局部性的考虑。

还有朋友指出,数组访问它不会傻傻得i++再去访问下标,它会优化。这没错,如果您用Item[]来代替List<Item>就会发现性能的确有提高(但同样相差不大)。但是,firelong同学说的是List<T>,它不是数组,而是基于数组的容器。由于List<T>是可变的,因此JIT是否真会对其进行优化还是个未知数,我倾向于理解为“不是”。不过现在,我只是通过小实验来看看究竟相差如何。

也有朋友说,Delegate不一定就是用单向链表或是List<T>保存的啊。是的,已经有朋友在firelong的原帖提出了。不过我针对的只是firelong关于“单项链表和List<T>”之间的遍历性能比较。我一直很奇怪,firelong从上一篇就开始说“性能相差很大”,“远低于”,“致命影响”之类的很严重的词汇,但是真的严重到什么程度却始终不给出任何说法。因此我现在也只是开个头,想说明firelong一些说得的很严重的事情,大家还得自己考证一下。