谁改变了目标?瞬息万变的 CPU 世界

作者:Leigh Davies 和 Ryan Shrout

内容简介

不管您使用何种平台,开发游戏都有如在流沙上构建游戏:在您开始设计游戏与游戏发布之间,您赖以开展开发工作的环境已然发生改变。这对于游戏设计而言可能只是一个简单的改变,在您采用全新图形技术以加快迁移到全新硬件平台的同时,安装硬件的电脑也在不断地演进。也就是说,当您在工作室忙着针对当前的硬件编写游戏代码时,硬件行业已大幅提升了处理器性能,向您客户的电脑中添加了新特性。

直到 2005 年,主处理器将前进目标锁定在提高单线程性能,使每一代处理器都较上一代处理器能够提供更高的工作频率和更高的指令级并行性。开发人员认为,随着新处理器的发布,他们将能够更快地进行开发,代码和应用也将相应地扩展。开发人员设计代码通常基于如下假设:新处理发布时处于最高端的电脑要比编写原始代码所用的开发系统运行速度更快。

然而,最近几年处理器架构和设计的转变已经将这条一次性旧规则变成复杂得多的场景。随着英特尔® 酷睿™ i7 处理器的推出,游戏开发人员现在能够利用的计算线程数是之前的多至 8 倍。处理器速度和特性不再以线性方式增加。诸如睿频加速等新技术现在能够对系统性能产生重大影响,使得以下这点变得至关重要:针对任意应用编写的代码不仅要能从一代处理器扩展到另一代处理器,而且还要能在以后可靠适应未来架构。

时间表创新

处理器性能的根本性转变源自处理器架构领域的众多技术创新。甚至是在过去的三年,英特尔“tick-tock”模型至少经历了两轮改进。“tick”方面,推出了硅核微缩制程技术,支持设计人员在相同物理硅核空间内容纳更多的晶体管。同时,“tock”方面,推出了性能和特性均得到提升的全新微架构。

在“tick”年代,制程技术已经从 2005 年推出的 65 纳米代产品演进到 2007 年的 45 纳米代,并将在 2009 年末演进到 32 纳米代。在“tock”年代,原先的英特尔® 奔腾® 处理器微架构演变为 2006 年的英特尔® 酷睿™ 微架构,并导致 2008 年英特尔® 酷睿™ i7 处理器和全新微架构的问世。图 1 显示了这一发展进程。



图 1. 英特尔微架构进展的 “tick-tock”模型。


为了与图 1 中显示的其它架构保持一致,即将推出的 Sandy Bridge 微架构将于 2010 年上市,将与前一代处理器大不相同。尽管针对一种架构编写和优化的代码能够在后续架构上运行,但通过采用新处理器开发人员将能够不断提升性能和避免可能的性能缺陷,这是因为发布新处理器是为了确保开发人员编写的代码能够枚举运行代码的硬件并适应该硬件。


基于多线程进行编程

多年来,硬件和软件开发周期在很大程度上相互独立。编译器和其它低级应用的程序员需要对底层硬件了如指掌才能实现最佳的平台性能。相比之下,游戏依赖编译器来针对硬件优化游戏代码,而编码人员则负责找到这一工作的最佳算法。如今,更为一般的计算任务(如游戏开发)也需要同等硬件专业知识。而且个中缘由不难理解。

一般而言,开发一款游戏需要一到三年的时间,编写任意新的内核引擎技术时必须确保其能够在市场上持续更长的时间,以便它能够被应用于多款游戏之中。对于大型游戏引擎,从启动到发运的整个开发时间长达五年并不罕见。英特尔的快速开发 tick-tock 模型意味着一般的游戏引擎将需要跨越多种不同的处理器架构。多核处理器(而不仅仅是主频较高的处理器)的推出意味着开发人员需要编写可扩展至利用该硬件的代码。随着人们越来越重视通过增加内核数量来提高处理器性能,硬件不再能够轻松加速现有代码,除非设计人员特意基于多线程开发软件。以图 2 中的英特尔® 酷睿™ i7 处理器为例,要充分利用该处理器,游戏将需要通过层次化高级缓存来利用 8 线程通信。


图 2.英特尔® 酷睿™ i7 处理器。

今天做出的编程决策将对基于未来架构的游戏性能产生重大影响。因此,尽管处理器的速度在不断加快,性能在不断提升,但是开发人员必须确保他们充分利用了他们可支配的计算性能。


英特尔® 架构 – 持续演进

为了将此问题与实际情况联系起来,让我们从英特尔® 奔腾® 4 处理器(图 3)开始,了解一下最新英特尔® 架构的演进。


图 3. 英特尔® 奔腾® 4 处理器微架构的特性。

2005 年,英特尔® 奔腾® 4 处理器架构是市场上最高端的英特尔消费类处理器。该处理器采用 4200 万到 1.24 亿个晶体管(晶体管的数量视具体型号而定),芯片面积约 122 mm2。该架构支持每个时钟周期发送 3 个指令,而且随后几年其总管线级数将深达31 级。该处理器集成有英特尔 SIMD 流指令扩展 2(英特尔® SSE2)指令,并采用 64 位单指令多数据 (SIMD) 单元。因此,执行一个 128 位 SIMD 指令需要两个周期。在该架构的生命周期即将结束时,英特尔开始提供对 64 位处理能力、英特尔® SSE3 指令和同步多线程 (SMT) – 英特尔® 超线程技术(英特尔® HT 技术)的支持。低级别程序员优化倾向于关注改进分支预测行为,或利用英特尔® SSE 来并行处理数据算法热点。



图 4. 英特尔® 酷睿™ 微架构的特性。


图 4 显示了英特尔® 酷睿™ 微架构。该微架构是高性能多核处理器家族的基础。与英特尔® 奔腾® 4 处理器相比,英特尔® 酷睿™ 处理器的晶体管数增加了多至 7 倍。双核处理器的晶体管数为 5.82 亿,四核处理器的晶体管数更是高达 8.2 亿。英特尔® 酷睿™ 处理器的一大进步之处在于迁移到 14 级管线,减少了分支预测失败对性能的影响,并将每个时钟周期可发送的指令数增加到 4 个。此外,英特尔® 酷睿™ 微架构还是首款在单个芯片的两个内核之间具有 1 个共享二级高速缓存的桌面型英特尔® 机构。实际上,四核处理器甚至具有 2 个需要通过速度较慢的前端总线进行通信的共享二级高速缓存。要高效利用芯片上的晶体管,游戏开发人员需要扩展到多至 4 个线程的处理能力。功能并行性常见于 2 到 3 个主要游戏线程,以及其它轻量级操作系统 (OS) 和中间件线程(如声音)中。

由于内核更多和管线更加高效,英特尔® 酷睿™ 微架构还使 SIMD 单元的性能加倍,这是因为 128 位的计算任务在单个处理器时钟周期内即可完成。“tick”带来了 45 纳米技术和英特尔® SSE4 指令集,将混洗单元 (shuffle unit) 的速度提升了 3 倍,从而提高了 SIMD 设置速度,为优化浮点密集型算法提供了更多机会。由于混洗单元的性能得到大幅提升以及大量指令可用于数据重新排序,之前不适合进行快速 SOA(数组结构)处理的数据结构如今也可以利用 SIMD 进行优化。



图 5。以英特尔® 酷睿™ i7 处理器为代表的下一代英特尔® 酷睿™ 微架构。

2008 年,最新英特尔处理器微架构与英特尔® 酷睿™ i7 处理器(图 5)一起推出。该微架构首次采用了原生、单片英特尔四核处理器,并且使用了比英特尔® 酷睿™ 双核处理器更少的晶体管(7.31 亿对比 8.20 亿)。在奔腾 4 上最后一次露面的英特尔® HT 技术在四核处理器上再次现身。借助该技术,英特尔® 酷睿™ i7 处理器能够支持多至 8 个同步线程和乱序引擎中的更多资源,从而支持更多线程数。尽管英特尔® 酷睿™ 微架构的晶体管数有所减少,但是其芯片面积却增加到 263 mm2,这在很大程度上得益于其包含三层高速缓存的更为复杂的架构设计。英特尔® 酷睿™ 微架构的管线仅扩展了两级,但是其解码器中的微融合和宏融合能力都得到了增强。从英特尔® 酷睿™ 微架构到英特尔® 酷睿™ i7 处理器微架构的架构变化与从英特尔® 奔腾® 处理器微架构到英特尔® 酷睿™ 微架构的架构变化一样显著。所不同的是,前两者之间的差别不及后两者之间的差别明显。


英特尔® 酷睿™ i7 架构的重要特性


图 6. 英特尔® 酷睿™ i7 处理器设计的内核和非内核图表。


英特尔® 酷睿™ i7 处理器架构专为模块化而设计,允许每个处理器具有不同的内核数。英特尔® 酷睿™ i7 处理器的每枚内核均完全相同,与前代架构相比能够更好地进行分支预测,在处理非对齐负载和高速缓存分离时具有更高的内存性能,以及具有更高的存储容量。每枚普通内核均采用两个寄存器集来支持 SMT。这两个寄存器集共享二级和一级数据高速缓存和乱序执行单元。每枚内核在操作系统看来都是两个逻辑处理器。

英特尔® 酷睿™ i7 处理器微架构还为处理器设计带来了非内核部分。非内核包括其它处理器特性,如内存控制器、电源和时钟控制器。其工作频率不同于普通内核。该架构支持将不同数目的内核连接至单个非内核设计,并支持非内核根据其目标市场而有所差异,例如添加额外的 QPI 链接以支持多路服务器和工作站。

要从英特尔® 酷睿™ i7 处理器中获得最大的性能优势,开发人员需要积极利用多线程。针对四个或更少线程编写的游戏引擎将仅能够利用可用处理能力的一部分,其相较于之前的四核处理器的性能提升可能也有限。开发人员不能仅满足于功能并行性和将物理智能与人工智能 (AI) 分开,而是要迁移到其各种功能分布在多个线程中的数据级并行性。对于任意图形密集型游戏而言,一个重要的要求将是确保渲染引擎本身能够在多个线程中扩展以及游戏和视频卡不会成为限制使用处理器和图形处理单元 (GPU) 的瓶颈。DirectX* 11 中对于多线程渲染上下文的支持有望从游戏编程领域中获得巨大优势。



图 7. 英特尔® 酷睿™ i7 处理器上的内存结构。

图 7 显示了英特尔® 酷睿™ i7 处理器上的内存结构,与之前的英特尔台式机设计大不相同。每枚内核在四个周期内提供 32 K 一级高速缓存 – 速度快得令人难以置信。与英特尔® 酷睿™ 处理器家族相比,256 K 二级高速缓存要小很多,尽管其在 10 周期访问时间 (10-cycle access time) 方面更快,同时时钟速率更高。为了弥补二级高速缓存偏小的缺陷,英特尔® 酷睿™ i7 处理器在非内核中采用了大得多的三级高速缓存,并且支持在所有普通内核中共享三级高速缓存。

全新的三级高速缓存支持 35 到 40 个周期的访问时间,具体视内核和非内核时钟速度间的可变比而定。三级高速缓存支持非独占共享高速缓存设计,能够映射一级和二级高速缓存中的数据。乍一看,非独占式高速缓存设计似乎不如使用芯片上的全部可用高速缓存来得高效,但是非独占式设计的内存延迟性能更高。如果一枚内核所需的数据在其自身的本地一级和二级高速缓存中找不到,它只需在访问主内存之前询问非独占式三级高速缓存,而非向其它内核探寻高速缓存结果。三级高速缓存连接到全新的集成内存控制器,后者则连接到能够比酷睿 2 处理器上的前一代前端总线提供更高带宽的系统中的 DRAM。对于多路系统,内存控制器能够通过 quickpath 互联而非前端总线与其它处理器通信。

与前一代高端台式机系统相比,主内存在带宽(3 倍)和延迟(0.5 倍)方面的性能均有所提升。决定是否运行八线程计算能力时,具备足够的内存带宽以避免无法满足处理器内核需求非常重要。


英特尔® SIMD 流指令集

英特尔® 酷睿™ i7 处理器还为英特尔处理器上的各种 SIMD 指令集支持带来了一个扩展(称作 SSE4.2)。新指令旨在加速特定算法,同时它们自身在游戏方面也有有限的优势(具体视您是否使用能从这些新指令中获益的算法而定)。通常而言,对 SSE 的支持对于充分利用电脑非常重要。仅仅上一版本 SSE 的支持范围如此之广这一点就常常让游戏开发人员感到惊异。英特尔® SSE 指令集初次推出是在 1999 年,紧接着英特尔® SSE2 在 2000 年推出(图 8)。认为大多数游戏开发人员正在选择 2000 年之后构建的硬件的看法似乎很合理,因此开发人员至少基本上能够保证对英特尔® SSE2 的支持。选择至少双核配置作为最低规格将能够保证在英特尔® 平台和 AMD 平台上支持英特尔® SSE3。



图 8. 英特尔® SIMD 流指令演进。

除了手工编写的优化之外,每种新架构的目标之一便是支持编译器自动生成类型更加灵活的优化。借助支持更好的非对齐负载和更加灵活的 SSE 负载指令的性能改进,编译器能够优化之前利用编译时可用的上下文信息无法安全优化的代码。因此我们始终建议您利用编译器标签来测试性能,以便有针对性地测试特定硬件。有些编译器还能够在提供后台兼容性以及通往目标硬件的快速路径的同时生成多个代码路径。


线程性能考虑事项

对于游戏开发线程化而言,一个重要的方面便是各种线程与高速缓存之间的交互。如前所述,英特尔® 奔腾® 4 处理器、英特尔® 酷睿™ 处理器家族和英特尔® 酷睿™ i7 处理器的高速缓存配置各不相同。(请参见图 9)。



图 9:不同微架构上的高速缓存布局。

英特尔® 奔腾® 4 处理器必须通过前端总线与各线程通信,因而需要至少 400-500 个周期的延迟;而英特尔® 酷睿™ 处理器家族能够通过共享二级高速缓存与各对内核、前端总线以及四核设计上的多对内核通信,延迟仅为 20 个周期。在英特尔® 酷睿™ i7 处理器中使用共享三级高速缓存意味着无需访问总线就能与其它内核同步,除非正在使用的是多路系统。由于高速缓存大小的增长不及内核数量的增长快,开发人员需要考虑如何充分利用可用的有限资源。诸如高速缓存锁定等技术将内部循环限制到更小的数据集和数据流库 (streaming stores) 中,有助于避免高速缓存行污染,从而减少了高速缓存抖动,最大程度地减少了对通过最后一级共享高速缓存进行通信的需求。


伪共享

对于高速缓存,一个潜在的重要缺陷是伪共享。伪共享发生于两个线程访问属于同一个高速缓存行的数据时。


图 10. 伪共享示例。

图 10 显示了一个伪共享示例,其中开发人员创建了一个全局对象来存储调试数据。创建的全部四个整数均轻松融入单个高速缓存行。然而,多个线程能够访问结构的不同部分。当有任意线程(如提交新绘制调用的主渲染线程)更新其相关变量时,访问或更新该对象中信息的所有其它线程的整个高速缓存行都会遭到污染。由此,需要在所有高速缓存中重新同步该高速缓存行,而这会导致性能下降。如果这两个线程通过英特尔® 酷睿™ 处理器或英特尔® 奔腾® 4 处理器的前端总线进行通信,则用户在更新简单的调试代码时就会遭遇长达 400-500 个周期的延迟。布置变量时必须格外小心,以确保变量布局考虑了典型使用模式。诸如英特尔® VTune™ 性能分析器等工具可用于查找诸如伪共享等高速缓存问题。

基本任务队列

要说明高速缓存设计如何对算法的性能产生重大影响,我们可以使用一个基本任务管理器,将一组任务分配给若干已经完成其先前工作现在可用于完成其它工作的工作线程。该方法是一种通用方法,通过将工作负载分解成若干可并行执行的独立任务,为应用赋予了可扩展线程化特性。然而,如图 11 所示,采用这种方法会导致若干问题。首先,两个线程在从队列中提取信息时必须访问相同的数据结构。

图 11a. 简单任务队列。 图 11b. 分配给线程的任务。

根据程序同步化数据的方式,延迟可以是数个周期(在共享高速缓存上执行原子操作)、数百个周期(使用前端总线)乃至数千个周期(使用互斥体或需要上下文开关的任意其它操作时)之间的任意值。从基本任务队列中提取小任务所需的开销非常大,甚至会超过从并行化处理这些任务中获得的优势。


图 11c. 添加到队列的程序任务。 图 11d. 在不同内核上执行的新任务。

这一队列设计的另一个问题是线程与在线程之上处理的数据之间不存在关联。需要相同数据的任务可能在不同的线程上在执行,从而增加了内存带宽,并可能导致与上述问题相似的共享问题。当开发人员在将程序生成的任务(图 11b 和 11c)放回到队列之前没有将执行此任务的线程与先前执行父任务的线程进行关联时,也会产生问题。如果开发人员足够幸运,执行任务的会碰巧是同一个内核和线程,该任务需要的许多数据仍然留在前一个任务的高速缓存中;否则,将需要再一次重新同步化该任务,所需延迟如上所述(图 11d)。显而易见,随着可用线程的数目越来越多,此类系统的复杂性会显著增加,高速缓存命中率会不断下降。


高速缓存感知任务队列

目前倍受推崇的一个方法是为每个线程构建单独的任务队列(图 12),从而限制线程之间的同步点,实施类似于英特尔® 线程构建模块中所用的任务窃取方法。该方法不会将程序生成的任务放在单个队列的末尾,而是按照先入先出的顺序将新任务放到其父任务所在的相同队列的前面。现在开发人员能够充分利用高速缓存,因为父任务很可能已经利用线程要完成任务所需的大多数数据对高速缓存进行了热身 (warm)。

单独队列还意味着任务可根据其共享的数据进行分组。


图 12a. 按线程 图 12b. 线程独立从队列中提取数据。

图 12c. 程序任务的 LIFO。 图 12d. 使用热高速缓存的任务。

仅当特定线程队列为空时才需要从其它队列中提取任务。尽管这种方法无法最高效地利用高速缓存,但是与让高速缓存闲置相比,这种方法的弊端可能会更小,且不失为一种更平衡的方法。



图 12e. 队列为空时的线程任务窃取。

除了智能任务队列之外,正在进行并行化处理的算法本身也可能需要感知底层硬件。智能利用任务分配和对任务执行时间进行分组能够大幅提高高速缓存利用率,从而大幅提高性能。最好的优化工具永远是程序员的大脑。


英特尔® 超线程技术有助于提高性能

英特尔® HT 技术与英特尔® 奔腾® 4 处理器一同推出,支持以较低的晶体管成本在单核处理器上添加一个计算线程(图 13)。理论上,这是一种提高性能的高能效方法,因为两个线程将共享相同的执行内核和资源。在许多情况下,内核会出现“泡沫”,此时单个线程由于依赖性无法在每个时钟周期执行 4 个指令(最大指令数);这些泡沫可以利用其它线程的指令和任务来填充,从而更早完成任务(与没有填充这些泡沫的情况相比)。从中可以推断,尽管每个指令的总体内核时钟计时单元 (overall core clock ticks) 有所改善,但是由于处理器要处理每个线程的数据和指令,因而单独线程的性能反而略有下降。通常情况下,这种性能下降可以忽略不计。但是,当一个关键性能线程与一个执行低优先级任务的线程并行执行时,这种性能下降偶尔会造成问题,因为 SMT 将无法区别两种线程从而平等对待两种线程。具体的线程执行性能取决于操作系统。较新的操作系统会改进多个线程的分布方式,最大限度地提高硬件性能。



图 13. 英特尔® 超线程技术消除了进程线程中的“泡沫”,改进了每个指令的内核时钟计时单元。

通过消除众多限制其在较早架构上的效率的瓶颈,SMT 支持在英特尔® 酷睿™ i7 处理器上的实施已的得到改进。由于内存带宽增加了三分之一,因而处理器遭遇带宽受限、多个线程塞满数据的几率降低了许多。启用英特尔® HT 技术时资源不再在启动时进行分配,英特尔® 奔腾® 4 处理器就是这样;英特尔® 酷睿™ i7 处理器逻辑专为实现完全的动态性而设计。当处理器注意到用户正在运行单个线程时,应用会访问所有计算资源;只有当软件和操作系统在该物理内核上运行超过一个线程时,这些计算资源才会在一个以上的线程之间共享。与前代处理器相比,线程可以利用的实际资源数也有所增加(图 14)。



图 14. 英特尔® 酷睿™ i7 处理器微架构上的同步多线程改进。



图 15. 针对 SMT 进行编程可避免性能惩罚。


还有一些简单的应用编码方法能够在逻辑和物理内核中任务分配不均的情况下,支持 SMT 提升性能或至少防止潜在问题(图 15)。第一个解决方法是将任务分解成多个通常能够在逻辑处理器间切换的粒度更细的任务。这种方法能够有效降低程序最终在同一个物理内核上运行两个最大的任务的几率。

另一个问题发生于开发人员出于调度的目的尝试自旋线程的情况下。在逻辑处理器上进行线程自旋时使用的执行资源能够被内核上的其它线程更好地利用。解决方案是利用 SMT 感知方法(如 EnterCriticalSectionSpinwait),使实际线程在复查之前沉睡 25 个周期或当线程能够实际运行而非处于睡眠状态时利用事件来发送信号。

当开发人员利用“零睡眠 (sleep zero)”生成一个优先于应用中主要线程的后台任务且线程数少于系统上的逻辑处理器数时会发生相似的效果。“优先任务”并不会阻止后台任务的执行,因为操作系统认为它拥有一个完全空闲的处理器。从根本上讲,自旋被忽略,且本应偶尔运行的单个线程不断反复运行,从而导致共享同一个物理内核的所有线程性能下降。这种情况下需要更加明确的同步(如 Windows 事件),以确保线程仅在合适的时间运行一次。


处理器拓扑缺陷

对于针对这些不同的高速缓存结构和英特尔® HT 技术场景进行编程和规划而言,一种似乎更为直接的方法是找到用户系统上特定处理器的拓扑,然后定制出专门针对具体硬件的算法。问题在于如何利用对 CPUID(由 CPU Identification 派生而来)规格的了解来精确确定处理器拓扑。

随着其它叶 (leaf) 的添加,CPUID 的用法已发生了变化。过去经常用来计算可用处理器数目的一种错误方法是利用 CPUID 叶 4。这种方法在之前的架构上偶尔起效,因为它能够提供物理包中最大可寻址 ID 数,在早期硬件上这个数字始终与处理器的实际数目相同。然而,这种假设对于英特尔® 酷睿™ i7 处理器不再适用。



图 16. 测试具体处理器的拓扑非常复杂。

有多种方法可根据您的优势使用 CPUID。然而,这些方法非常复杂,且需要经历一个漫长、包含多个步骤的过程(图 16)。在英特尔网站上您可以找到多篇包含 CPUID 检测算法具体示例的优秀白皮书。掌握了 CPUID 信息后,您不仅能够判断用户的处理器包含多少枚内核,而且还能够判断哪些内核共享了哪些高速缓存。这使针对特定架构定制算法成为可能,尽管在此过程中如果走了捷径会很容易导致应用的性能和可靠性下降。


线程相似性

提到在游戏引擎中使用线程相似性,更多的问题出现了。拓扑枚举生成的掩码随操作系统、操作系统版本(x64 对比 x86)甚至服务包的变化而变化。这使设置线程相似性成为编程中的一项艰难任务。内核在英特尔® 奔腾® 4 处理器上 CPUID 枚举中出现的顺序与在英特尔® 酷睿™ i7 处理器上完全不同,且不同操作系统的英特尔® 酷睿™ i7 处理器会显示相反的顺序。如果没有通过仔细的枚举和算法设计来考虑这些不同,则代码相似性导致的问题将比其能够防止的问题还要多。

开发人员还必须考虑游戏和操作系统的整个软件环境。应用中的中间件能够创建自己的线程,并可能不提供 API 以支持线程利用用户代码进行有效调度。显卡驱动程序还会生成自己的线程,这些线程按照操作系统分配给处理器内核,从而导致为游戏线程的相似性进行准确编码的工作遭到污染。鉴于开发过程中存在大量的潜在问题,在应用中设置相似性基本上是在短期利益上押注。

如果使用的是硬编码的线程相似性和不正确的 CPUID 信息,应用性能将会实际下降两倍。在最坏的情况下,应用会由于在之前架构上所做的假设而无法在较新的硬件上运行。对于游戏,我的建议是避免使用线程相似性,因为它提供的短期解决方案会在事情不顺利时造成长期问题 – 其弊端远远超过了其带来的收益。相反,我建议您使用诸如 SetIdealProcessor 等处理器暗示而非硬绑定 (hard binding)。

如果开发人员执意采用线程相似性,则应当构建一种安全特性以支持用户在未来的处理器架构和硬件对性能或稳定性造成重大影响时禁用相似性,或者更好的做法是仍然允许选择,确保应用能够发挥出与默认配置中没有设置相似性时一样好的性能。如果软件需要设置相似性才能运行,则讨厌的错误可能即将在硬件和软件环境的组合上发生。


操作系统调度

每个版本的 Microsoft Windows* 操作系统在线程调度方面均略有不同;即使是 Windows 7 也在 Windows Vista* 的基础上有所改进。调度程序是一种基于优先级的轮询系统,专为公平对待当前运行在系统之上的所有任务而设计。实际上,Windows 为线程分配的执行时间(在清除之前)非常长 – 大约为 20-30 毫秒左右 – 除非有事件触发了自发开关 (voluntary switch)。对于游戏而言,这是非常长的一段时间,可能会持续超过一个帧。考虑到在操作系统调度方面吸取的经验教训,正确调度自己的线程以确保任务执行时长合适对于游戏非常重要。线程同步不正确会导致微小 (minor) 的后台线程或低优先级线程运行时长超过预期,从而对游戏的关键线程造成重大影响。遗憾的是,过于频繁的同步也不是一件好事,因为线程之间的变化将会造成数千个周期的延迟。共享数据的线程之间发生过于频繁的上下文切换会导致性能下降。要避免上下文切换造成的性能惩罚,针对可能不久会再次利用的资源执行短期自旋锁定是非常有益的。当然,如果等待时间过长也会造成性能下降,因此密切监控线程锁定非常重要。


图 17. 线程自我监控支持轻松定位性能瓶颈。

图 17 显示了如何枚举程序所用的应用和中间件已经创建的线程。Vista 引入了一些新的 API 附加特性,这些特性能够在线程枚举完毕后告知开发人员在特定内核上花费了多长时间以及特定线程运行了多长时间等信息。将这个考虑在内,为开发人员甚或最终用户构建一个屏幕诊断线程性能下降的工具将非常简单。


前景

考虑到多线程、微架构和游戏的未来,两种有趣的技术会浮现在您的脑海。称作 Sandy Bridge 架构的下一个“tock”将推出英特尔® 高级矢量扩展指令集(英特尔® AVX)。如今,随着英特尔® SSE4 扩展到 128 位指令,英特尔® AVX 将能够提供256 位指令,向下兼容当前的 128 位 SSE 指令。正如支持 64 位寄存器的当前处理器能够以一半的寄存器空间运行 32 位代码一样,支持 AVX 的处理器将能够以一半的寄存器支持英特尔® SSE 指令。矢量将能够运行八位而非架构当前能够处理的四位,从而提高了低级别数据级并行性的可能性,并改进了游戏性能。Sandy Bridge 还将提供更多的内核数和更高的内存性能,能够以基于任务的细粒度级别实现更高的线程化。



图 17. Larrabee 将为显卡市场带来处理器类型架构。

多核 Larrabee 架构(图 17)是另一款即将推出的相关技术。尽管这种技术被规定为一种 GPU 技术,但是该设计从根本上将当前多核处理器的性能提升至全新水平。尽管此架构已大大增加了内核的数量,但是本文中提到的一切线程性能、编程模型和相似性仍将适用于该架构。规划未来的开发人员应当考虑如何扩展到英特尔® 酷睿™ i7 处理器甚至 Sandy Bridge 以上的配置以及了解 Larrabee 架构在高速缓存和线程间通信等方面的知识。


结论

台式机处理器正在以飞快的速度向前发展。尽管性能增强特性为开发人员改进逼真性和保真度提供了巨大的机会,但是它们同时也造成了需要不同编程模式的新问题。开发人员不再能够想当然地认为他们当前编写的代码在下一年的硬件上能够更好地运行。相反,他们需要考虑即将出现的硬件架构,编写出能够轻松适应不断变化的处理器环境(尤其是可用线程数目方面)的代码。

硬件不断发展的一个负面影响是性能测试的重要性不断提高。针对确定的工作负载使用合适的工具和开发结构化性能测试套件对于确保应用在尽可能广泛的硬件上发挥最佳性能至关重要。在游戏设计周期内提早在各种硬件上进行测试是确保设计决策能够提供最高长期性能解决方案的最好方法。本文中提到的许多问题可利用诸如英特尔® VTune™ 性能分析器等工具来确定。英特尔® VTune™ 性能分析器能够监控特定线程中的高速缓存行为和指令吞吐率。其它此类工具还有专为测量线程并发性而设计的英特尔® 线程调节器。

如欲获取本文中提到的处理器和其它英特尔® 处理器的优化指南,请访问 http://www.intel.com/products/processor/manuals/
有关编译器优化的更完整信息,请参阅优化通知