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

推荐订阅源

U
Unit 42
N
News and Events Feed by Topic
S
Schneier on Security
G
GRAHAM CLULEY
Scott Helme
Scott Helme
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
CERT Recently Published Vulnerability Notes
T
The Exploit Database - CXSecurity.com
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
Cisco Talos Blog
Cisco Talos Blog
P
Privacy & Cybersecurity Law Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
Blog — PlanetScale
Blog — PlanetScale
Project Zero
Project Zero
MyScale Blog
MyScale Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
The Last Watchdog
The Last Watchdog
Vercel News
Vercel News
The Cloudflare Blog
C
Check Point Blog
Help Net Security
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
NISL@THU
NISL@THU
S
Security @ Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
SegmentFault 最新的问题
MongoDB | Blog
MongoDB | Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threatpost
AWS News Blog
AWS News Blog
Cloudbric
Cloudbric
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Vulnerabilities – Threatpost
S
Secure Thoughts

博客园 - 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一些说得的很严重的事情,大家还得自己考证一下。