摩尔定律的末日-软硬件性能提升的历史和未来(1) - 抓虾

来源:百度文库 编辑:神马文学网 时间:2024/05/01 05:54:43

之一,正文

免费大餐不久就将结束。对此,你有何打算,做好下一步准备了么?

 

对主要的处理器厂商以及架构,包括Intel、AMD和Sparc、PowerPC[译注1]来说,改善CPU性能的传统方法,如提升时钟速度和指令吞吐量,基本已走到尽头,现在开始向超线程和多核架构靠拢。而且这两个特性(特别是多核)已经在部分芯片实现,如PowerPC和Sparc IV;Intel和AMD也将在2005年内赶上。2004年In-Stat/MDR秋季处理器论坛[译注2]的主题就是多核设备,很多公司都展示了改进和新研发的多核处理器。不过,要将2004年称为多核年,显然还不够理直气壮。

 

多核将引领软件研发发生基础性变化,特别对接下来几年里那些面向一般应用、运行在PC和低端服务器上的应用软件(在今天已经销售出去的软件里占有很大比例)而言。在这篇文章里,我想就多核为何突然对软件产生重要影响,以及并发巨变如何影响我们和我们未来编写软件方式的问题展开讨论。

 

我可以这么说:免费大餐已经结束一两年了,但我们现在才开始意识到这个问题。

 

免费的性能大餐业界存在一个有趣的现象:“安迪送,比尔取。”[译注3]无论处理器性能提升多少,软件都有办法迅速吞噬。CPU速度十倍于前,软件就有十倍于前的活要干(或者肆无忌惮猛增软件的工作量,导致性能下降)。在过去几十年里,由于CPU、内存和硬盘特别是CPU厂商强力推进主流系统向更新更快的方向发展,大多数软件不做版本升级,甚至原封不动,就可轻松而持续地享受处理器性能提升的成果。尽管时钟速度不是衡量系统性能的唯一和最好的标尺,但其重要意义不容忽视。我们见证了CPU的发展历史:从500MHz到1GHz,然后再到2GHz,不断提高。今天,主流计算机已经进入3GHz时代。

 

不过,有一个很关键的问题:这种提升模式什么时候会走到尽头?尽管莫尔定律预言了历史上的指数式增长,但我们很清楚指数式增长不可能永远维持,因为硬件毕竟受物理极限约束;光速是不可能更快的[译注4]。所以增长必然放缓,最后停滞。顺便说明一点,莫尔定律的主要描述对象是晶体管集成密度,但在一些相关的领域,如时钟速度方面,也出现了类似的指数式增长;甚至在别的领域有更快的增长速度,例如著名的数据存储量爆炸。不过这些重要趋势需要另一篇文章来分析了。

 

如果你是一个软件开发人员,那么你可能一直在免费享受桌面计算机性能提升的大餐。某些操作会成为应用程序性能的瓶颈?“你过虑了”,我们对这样的回答耳熟能详,“未来处理器将更为强劲,而现在的应用程序速度倒是日益被非CPU吞吐量和内存速度因素扼杀,比如I/O、网络和数据库等等。”真的是这样么?

 

要在过去,这的确没错。但在以后,就完全不对了。

 

我有两个消息要告诉大家。第一个是好消息,处理器性能仍然会不断提高。第二个则是坏消息,至少在短时间内,处理器性能的提升,不再能像以往那样让现在的应用程序继续免费获益。

 

过去30年里,CPU设计者主要从三个方面提高CPU性能,头两个就是从线性执行流程上考虑的:

 

1、时钟速度

 

2、执行优化

 

3、缓存

 

提升时钟速度将增大单位时间的时钟周期数。让CPU跑得更快,就意味着能让同样工作或多或少更快完成。

 

优化指令执行,可以在每个时钟周期内完成更多工作。目前的CPU中,一些指令被不同程度地做了优化,如管线、分支预测、同一时钟周期内执行更多指令,甚至指令流再排序支持乱序执行等[译注5]。引入这些技术的目的是让指令流更好、更快执行,降低延迟时间,挖掘每一时钟周期内芯片的工作潜能。

 

在这里,有必要对指令再排序作个简单说明。我刚才提到的部分指令优化手段,其实已远非普通意义上的优化。这些优化可能改变程序原意,造成程序不响应程序员的正常要求。这可是个大问题。CUP设计师都是心智健全且经过严格训练的好同志,正常情况下,他们连苍蝇都不愿伤害,自然也无意破坏你的程序。而在最近几年里,尽管知道指令重组有破坏程序语义的风险,但为了提升每个时钟周期内的工作效率,他们已经习惯于积极开展这类有风险的优化工作。难道海德先生[译注6]复活了?当然不是。这种积极性清楚表明,芯片设计师承受了交付速度更快CPU的巨大压力;在这种压力下,为了让软件跑得更快,他们不得不冒改变程序意思,甚至应用崩溃的风险。拿两个有名的例子来说——写操作再排序和读操作再排序[译注7]。允许处理器对写操作再排序是非常令人吃惊的,让大多数程序员意外,一般来说这个特性必须关闭,因为在写操作被处理器武断地再排序条件下,程序员很难保证程序正确执行。读操作再排序也有明显的问题,但大多数情况下这个特性是开启的;因为相对来说它更容易把握一些,而且人们对性能的要求,让操作系统和操作环境设计师只能选择让程序员在一定程度吃点苦头,毕竟,这比直接放弃性能优化机会的罪责小一些。

 

第三个是增大与RAM分离的片内高速缓存。RAM一直比CPU慢很多,因此让数据近可能靠近处理器就很重要——当然那就是片内了[译注8]。片内缓存持续飚升了很多年,现在的主流芯片商出售的CPU都带有2M甚至更高的二级缓存。值得一提的是,今后,三种提升CPU性能的传统手段里,增加缓存将硕果仅存。我会在后面更详细说明缓存的重要性。

 

我写这么多的意思是什么呢?

 

最重要的是我们必须认识到,传统性能提升方法与并发没有直接关系。过去任何方法带来的速度提升,无论是顺序(非并行的单线程或单进程)、还是并发执行的程序,都能直接受益。这点很重要,我们目前大量的程序都是单线程的,而且在未来仍然有重要的存在价值。

 

当然,适当时候,我们重新编译程序,可以利用CPU的新指令(如MMX、SSE[译注9])和新特性提升系统性能。但总的来说,即使放弃使用新指令和新特性,不做任何更改,老程序在新CPU也会跑得更快,让人心花怒放。

 

曾经的世界是这般美好,可如今,她就要变了颜色。

 

为什么我们今天没有10GHz芯片

 

其实,CPU性能提升在两年前就开始碰壁,但大多数人到了最近才有所觉察。我这里有份来自Intel的数据(当然你可以从其他厂商得到类似数据):

 

 

图中反映了Intel芯片的时钟速度和晶体管集成规模演变历史。晶体管集成数至少就目前而言仍在继续上升,但时钟速度的情况就不同了。我们从图中可以看到,大概在2003年初,一路高歌猛进的CPU时钟速度突然急刹车。受制于一些物理学问题,如散热(发热量太大且难以驱散)、功耗(太高)以及泄漏问题[译注10]等,时钟速度的提升已经越来越难。

 

你目前在工作站上用的CPU时钟速度是多少?10GHz么? 2001年8月Intel芯片就达到2GHz,按照2003年前的CPU发展趋势推算,到2005年初,我们就能拥有第一块10GHz的Pentium芯片。但实际上没办到。而且情况好像越来越糟——我们根本就不知道到底在什么时候这样的芯片可以出现。

 

那么放低期望,4GHz又如何呢?目前我们已到3.4GHz——那么4GHz已经不远了吧?唉,好像4GHz也遥不可及。可能你知道,Intel首先于2004年中将4GHz芯片的发布时间推迟到2005年,而到了2004年秋季,则彻底取消了4GHz计划[译注11]。在本文写作的同时,Intel宣布计划到2005年早期,实现到3.73GHz(即图中的右上最高处)的微量提升。所以,至少就目前来说,时钟速度的竞赛实际上结束了,Intel和其他大多数处理器厂商将把旺盛的精力投入到多核等方向去。

 

也许,我们某天在主流PC里能装上4Ghz的CPU,但2005年别想。Intel实验室里的确已经有运行在更高速度的芯片——不过代价是惊人的,比如庞大数量的冷却装置。你想不久在你的办公室里就有这样的冷却设备,坐飞机的时候,就把它们放在你膝盖上?别做梦了!

 

莫尔定律与新一代处理器“没有免费的午餐。”——摘自R. A. Heinlein的小说《The Moon Is a Harsh Mistress》

 

莫尔定律玩完了?这个问题很有趣,严格地讲,还不能这么说。尽管和所有的指数式增长方式一样,莫尔定律总有一天会走到尽头,但最近这些年,还没有这样的危险。芯片工程师在榨取时钟周期内剩余价值时的确碰了壁,不过晶体管集成量仍在暴涨,所以从这个角度说,CPU近期仍将遵循莫尔定律,系统吞吐量继续提高。

 

关键的变化,即本文的中心,是今后几代处理器性能提升所走的道路将完全不同。同时,大多数现在的应用软件将不再可能不作大规模重构,就能像过去那样从处理器免费获益。

 

接下来数年里,新型芯片的性能提升将主要从三个方面入手,其中仅有一个沿袭是过去的:

 

1、超线程

 

2、多核

 

3、缓存

 

超线程,是指在单个CPU内,并行两个或多个线程。超线程CPU已经发布了,支持并行执行一些指令。不过这种CPU还是存在短板,虽然给它增加了部分硬件如寄存器,但它和绝大多数普通CPU一样,但缓存、整数和浮点运算器仍然是唯一的。有资料表明,写得较好的多线程应用,在超线程CPU上能获得5%-15%的性能提升;假设趋于理想状态,即多线程程序写得好到极点,那么性能可以提高40%。不错了,不过还是做不到成倍提升,而且对单线程应用毫无帮助。

 

多核,主要是指在一块芯片上运行两个或多个处理器。部分芯片如Sparc和PowerPC目前已经推出了多核版本。Intel和AMD也计划在2005年内初步实现,具体时间取决于它们的系统集成水平,功能则是一样的。AMD初期在性能设计可能更具优势,如更好的支持功能同片内集成,而Intel基本上就打算将两颗Xeon胶合在一块芯片上了事。所以刚开始的时候,这种双核芯片与一个真正的双CPU系统在性能几乎没有差别,仅仅在价格上前者更为便宜,毕竟它的主板上不需要两个插槽和额外胶合件;另外,即便理想状态,这种架构也无法达到双倍速度,且无益于单线程应用,而只有写得较好的多线程应用能得到好处。

 

最后一个是片内缓存,还能像预期那样在近期继续上升。三个方法中,仅有这个可以让现有应用全面受益。片内缓存有令人难以置信的重要性和对大多数现有应用的超高价值,原因很简单,那就是空间就是速度。CPU和主存交互的代价是巨大的,如果能避免,那就尽量不要和它打交道。在目前的系统里,从主存获取数据所花时间,通常是从缓存获得数据的10到50倍。很让人吃惊吧,因为很多人都以为内存已经足够快。其实这不过是与硬盘和网络相比,而不是运行在更高速度的片内缓存。应用程序的工作与缓存间的适配程度,和我们是荣辱与共的。很多年来,不重构程序,仅仅提高缓存大小就拯救了现有应用,给它们带来新生。软件操纵的数据和为新增功能而加入的代码越来越多,性能敏感的操作必须继续与缓存适配。套用经济大萧条时期老人常念叨的一句话:“缓存为王。”

 

顺带说件发生在我的编译器小组的趣事,算是“空间就是速度”的一个佐证。32位和64位编译器将同样的代码分别编译成32位和64位程序。64位CPU有多得多的寄存器和其他代码优化特性,因此运行其上的64位编译器先天的获得极大性能提升。这当然很好。而数据的情况又如何呢?换到64位平台上,内存中绝大部分数据的大小并未发生变化,唯一例外的就是指针,指针占用了两倍于以前的空间。因此,我们的编译器和绝大多数32位应用相比,挥舞指针就费力得多。现在的指针耗用8个而不是4个字节,空间净增加,结果我们发现64位编译器的工作集[译注12]大小显著增加。工作集增大导致性能下降,差不多抵消了更快的CPU和更多寄存器带来的性能优势。就在我写这篇文章的时候,64和32位编译器正以同样的速度运行,尽管程序代码完全一样而且64位处理器先天能力更强。这就是“空间就是速度”。

 

缓存能,但超线程和多核CPU对现在的绝大多数应用,几乎不会有任何影响。

 

综上所述,硬件的变化到底会给软件开发方式带来怎样的影响呢?你可能已经有了初步答案了。让我们更深入研究,明白其厉害所在。

 

对软件来说,这意味一次巨变上世纪90年代初,我们开始学着理解对象。在主流软件开发领域里,从结构化到面向对象编程是过去20甚至可以说30年来最重要的变革。这期间也发生了其他一些变化,例如近来诞生的的确让人着迷的WebServices,但我们中绝大多数人在职业生涯里从未有过见识像面向对象那样基础而深刻改变软件开发方式的机会。

 

现在,机会来了。

 

也从现在开始,性能大餐就不再免费了。虽然托缓存增大的福,我们还能在半路上捡到普通的应用性能提升丸,但如果你希望你的应用程序在新的处理器里能继续获得爆炸性的性能提升,那就需要你好好编写并发程序了(通常是多线程的)。说比做容易啊,也不是所有问题都天生可以通过并行解决,而且并发编程的难度也是很大的。

 

肯定有人嚷嚷开了:“并发?并不是什么新鲜玩意嘛!人们不就已经在写这样的程序了么。”是的,小部分程序员的确写过。

 

别忘了,至少从上世纪60年代晚期的Simula开始,人们就在写面向对象程序。但到了90年代,面向对象才成功发动革命并夺取统治地位。为什么呢?工业是受现实需求驱动的,为了解决越来越大的问题,必须编写越来越大的系统,这样的系统也需要越来越强劲的CPU和存储设备支持,正好硬件系统也逐步提供了这样的支持。面向对象编程擅长抽象和依赖管理,所以成为了开发经济、可靠和可重用的大型软件的必备利器。

 

并发编程差不多也有同样漫长的历史可以追溯,很早的时候,我们就开始编写协程、管程[译注13]以及其他与并发相关的东西。近10年来,我们也发现有越来越多的程序员在编写并发应用(有多线程的,也有多进程的)系统。但是发生整体转向性的巨变,目前还不具备条件,需假以时日。现有大量的单线程应用,仍然有巨大的存在价值,这点我会在后面说明。

 

说点题外话,当前“下一次软件开发革命”这样的词语多如牛毛,总让大家眼花缭乱,其实这是商家宣传自己新技术所作的广告,不要理睬它。新技术通常都很吸引人,有时候也很有用,但软件开发方式的重大变革必然来源于在真正得到爆发式广泛应用前就存在并经过多年缓慢成长、先进而稳定的技术。这个过程是绕不掉的。变革所依赖的基础技术必须足够成熟(包括有固定的厂商和工具支持);通常,这个成熟稳定过程至少要花费7年的时间,新技术在广泛应用时才不会有潜在的性能悬崖和陷阱。所以,像面向对象这样的软件开发变革,也必须在各项技术经过多年甚至几十年磨砺后才能发生。即便在好莱坞,绝大多数的一夜成名,也仍然是多年努力后发生重大突破的表面象征。

 

并发将是软件开发史上的又一个重大变革。很多专家仍然在这个变革是否比面向对象还大的问题上争论不休。这样的争论最好还是留给学问家吧。技术工作者最感兴趣的是和面向对象一样,编程方式的变化程度、编程技术的复杂性和学习曲线问题。

 

并发之正反二面并发技术(特别是多线程)在主流软件里大多应用在两个方面。第一类是天然就彼此独立的、逻辑上分离的控制流程,比如在我设计的数据库复制服务器里,每个复制Session都放在各自的线程里,彼此完全独立的,不会工作于同一条数据记录上。第二类不像第一类那么常见。为了系统提升性能,像利用多CPU平台的能力,挖掘应用程序其他部分的潜能等,我们也会编写并发代码。在我的数据库复制服务器里,多个独立的线程在多CPU平台上就工作得很好。

 

然而,并发编程也是要付出代价的。一些很明显的问题相对来说无关紧要,比如锁定。对资源的锁定降低了系统的性能,但如果你能找到办法最小化甚至消除资源共享,让操作真正并行,从而明智得当地使用锁,那么从并发执行得到的收益,要远大于在同步上蒙受的损失。

 

更重要的问题,大概就是并非所有应用都适用并行。这点我会在后面说明。

 

应该说,最大的问题,就是并发编程本身的难度了。程序员必须将脑子里的编程模型转化为可靠的程序,这比实现顺序执行的传统程序难得多。

 

任何学习过并发的人都认为自己已经理解并发,早早结束寻找他们认为不可能但实际潜在的竞争冲突和他们其实仍没闹明白的问题。如果开发人员认真学习和思考并发编程,就会发现通过合理组织的内部测试能发现大多数的竞争冲突问题,这个时候,无论是在知识水平还是心情愉悦度上,他们都能达到一个新的高度。不过,除了经过理解为什么和怎么进行真正压力测试的行家测试过的、已经正式发布的软件,都会存在部分在普通测试中无法捕获的潜伏并发问题。这些问题只有在真正的多处理器系统上才会暴露出来,因为在这样的环境里,多个线程不是在单处理器上切换,而是真正的并发运行,大量新问题就会涌现。而偏偏又有很多人自以为已经真正理解如何编写并发程序,真是让人忐忑不安啊。我见过不少项目组,他们的程序在很多用户那里即便施以极端苛刻的压力测试,都能出色工作,但某天一个用户部署了真正的多处理器机器后,深层次的竞争冲突甚至程序崩溃问题马上出现。CPU发展到今天,重构你的应用,让它们多线程运行在真正的多核计算机上,的确像逼迫初学游泳的人一下子跳入深水——直达终点,似乎有点残忍,但只有真正并行的环境,才非常容易暴露出你的问题。而且,即使你组织了一个能真正编写可靠并行代码的团队,也不能说就不会出现问题。例如,并发代码运行可能非常安全,但(在多核的机子上)却不比在单核的机子上跑得快。其典型原因就是线程未被合理分离,共享了单一资源,造成程序执行顺序化。这类问题是相当微妙而复杂的。

 

结构化程序员学习面向对象(什么是对象?什么是虚函数?我如何使用继承?知道“是什么”和“怎么办”外,还得问一句“如何保证理论上正确的设计在实践中的正确性”)是一个飞跃,同样的,顺序思维的程序员学习并发(什么是竞争冲突?什么是死锁?它是怎么出现的,我如何避免它?什么样的构造在我看来是并行的但实际上顺序化了程序?在“是什么”和“怎么办”外,还要回答同样的问题“如何保证理论上正确的设计在实践中的正确性”)也是一个飞跃。

 

现在的大量程序员并没有真正理解并发,就像15年前大量程序员没有真正理解对象一样。但并发编程模式是可以学习的,尤其是我们要坚持基于消息和锁的编程;一旦真正理解了并发,就会发现它并不比面向对象难多少,很容易觉得那是自然而然的。我们需要为我们自己和我们的团队做好训练投资和时间的准备。

 

有必要说明一点,我在上面故意将并发编程模式限定在消息和锁基础上。其实也有在语言级就直接支持的无锁编程,比如Java5和很流行的C++编译器。但对于程序员来说,无锁比有锁并发编程难得多。大多数情况下,只需要系统和库编写者理解无锁编程就行了,虽然事实上每个人都可以从无锁系统和库获益。老实说,即便有锁编程,也有点碰运气的味道呢。

 

对我们来说这到底意味着什么好了,回到正题,将问题归纳一下。

 

1、我们已经讨论清楚的、最重要结论是:如果应用程序想充分利用CPU吞吐增加量,那它们就必然日益需要并发,这种形势逐渐明朗,并将在接下来的数年里深入发展。Intel已经扬言未来他们会推出集成100颗内核的芯片,那么单线程应用最多就只能利用这种芯片1/100的潜在生产力。“哦,性能没那么重要吧,计算机总是跑得越来越快”的论调已经变得天真而可疑,甚至在未来不久将完全错误。

 

目前,并不是所有的应用都需要(或者更准确的说,只有应用中重要的作业才需要)并行。像编译这类的问题,是必须要考虑并行的,而其他则不一定。请看这个有趣的例子:一个女人需要九个月才能生产一个小孩,并不代表九个女人能花一个月生出一个孩子。你以前可能接触过类似的推导,但又没感觉这个问题意犹未尽呢?如果有人再和你就此讨论,我向你推荐一个刁钻的问题:从这个命题你能断定“女人-小孩”是一个非并行问题么?通常,人们会下意识地认为它天然就不是一个并行问题,但实际上并不完全是这样。如果目的是生一个小孩,它的确是一个非并行问题;但如果是生产多个小孩,那么它就是一个标准的并行问题了!所以说,目标不同,结论就大相径庭。在考虑你的软件是否和如何使用并行时,千万别忘了面向目标原则。

 

2、可能不那么明显的结论是:CPU将很可能日益成为应用程序性能的瓶颈。当然,不是所有应用都会这样,那么目前还未明显受制于CPU能力的应用在未来虽然可能受到CPU影响,CPU也不会一夜之间成为它们的镣铐。但“I/O、网络和数据库瓶颈”似乎快走到谷底,因为在这些领域,条件仍然在迅速得到改善(如GB硬盘、WiFi网络等等);而与此形成对比的是,CPU性能提升技术已走到峰点。请注意,我们的CPU目前在3GHz徘徊。所以,除了指望缓存在未来继续扩大(这可真是一个大好消息),现在的单线程应用不太可能跑得更快。其他方面的性能提升手段,虽然未来还可能继续发挥作用,不过已经无法和过去相提并论了。芯片设计师正在拼命寻找新办法提高管线利用率,降低数据加载延迟,但在这些领域,长在低枝的果子早已被摘光。而应用程序新需求的增加不但不稍事休息,反而神经质地加速猛冲。我们只好逼迫程序做更多的事情,而程序则只有威逼CPU,压垮它,除非程序能并发执行。

 

应对如此巨变,我们现在有两条路可以走。一是面向并发重构应用,二是勤俭持家,小心规划代码,让它吃更少的食,干更多的活。这就引出了第三个有趣的话题。

 

3、提升程序效率、优化其性能将越来越重要,而不是相反。已经高度重视性能优化的语言将获得新生,其他的语言赶紧奋起直追,朝着效率和优化努力吧。面对长期增长的需求,希望面向性能努力的语言和系统能为我们分忧。

 

4、最后一点,编程语言和系统将不得不尽快做好面向并发的准备。Java语言从一开始就支持并发编程,虽然还存在不少问题以致不得不发布多个后续版本提升并发程序的正确性和效率。C++长期以来被用于编写大型多线程系统,但它却没有对并发的标准支持(ISO C++标准甚至有意未提及线程[译注14] ),因此,并发目前只能在一些不可移植的特定平台和库基础上实现(而且实现还不够完善,比如静态变量只能初始化一次,这就要求编译器自动加锁,但很多C++的实现里并不生成锁)。另外,目前存在多种并行编程标准,比如pthreads和OpenMP[译注15] ,其中一些支持隐式并行,另一些显式支持。让编译器分析单线程程序并自动生成并行代码的隐式并行方式当然美妙而优雅,不过这类自动转化工具的结果代码质量还无法与人工编写的显式并行代码媲美。目前的并发编程主要以锁为基础,这种方式也很难把握,常常要赌几分运气。总之,我们迫切需要一个比目前语言提供的更高抽象层次的、统一的并发编程模式。关于这点,以后我还有更多的话要说。

 

总结如果你以前对并发未加注意,那么现在是时候了,仔细分析应用的设计,挑出现在和不久就可能过于依赖CPU能力的操作,研究这些部分如何从并发得益。你和你的团队,现在也该深入学习和了解并发编程的要求、不足、风格和专业概念了。

 

少部分应用天然适用于并行,但大多数不是的。即便你知道程序受制于CPU的位置,可能也很难找到将这部分操作并行化的办法。所有这些问题,要求我们赶紧对并行多加思考和研究。隐式并行编译器能帮点小忙,但不能指望太多,它不可能比得上尽你所能将顺序化程序转化为显式并行和多线程版本后的效果的。

 

感谢仍未停止的缓存扩大和管线少量优化,免费饭菜在今后还能有一点,不过从今天开始,餐馆无偿提供的只有小菜和饭后小点心了。菜谱上仍然有优质可口的鱼片,但现在要享受它就得付费——设计精细化、代码更复杂,而且要加倍测试。对于多数应用来说,这是个好消息,尽管要辛勤耕耘,但回报是丰厚的,因为并发可以让应用继续从处理器能力暴增中充分受益。