单处理器vs.多处理器
在现今特定的建构技术下,将单处理器的效能提升至理论上最高值既不简单,也缺乏有效率的做法。更快的频率、更大的通道、与更大的高速缓存都是业界尝试过有效的技术,但相随而来的是芯片面积与散热的成本。要榨出最后10%的效能,投资报酬率实在不高。有时候我们别无选择,只好提高频率,并将电源与冷却子系统升级;但如果能将工作分配给多颗处理器,整体效能会提升,且处理器组件的设计也可以更简单、更有效率。
虽然这是当今PC、服务器、与工作站的作法,但也适用于嵌入式系统单芯片(SoC)设计。事实上,现在许多嵌入式SoC设计都运用多个处理器,但是在针对特定应用或利用“松散耦合”(loosely coupled)的方式。即使到今日,能与软件无间配合的多处理器SoC设计还是十分有限。但随着MIPS32 1004K Coherent Processing System (CPS)等SoC设计组件的诞生,系统架构设计师终于得以选择在单一操作系统下的芯片内建对称多任务处理功能(Symmetric Multiprocessing, SMP),本文将详述此新技术的功能与限制。
平行处理参考范例
运用平行处理器需要平行软件支持,软件工程师会对“平行程序设计”模式有所疑虑,因为不是所有现存的程序代码都针对平行处理平台所编写。但平行处理软件有好几种范例可以参考,软件设计师也已经相当熟悉其中一些范例,即使他们不见得认为自己很熟悉。
数据平行算法
数据平行算法是解决单一基本运算所产生出问题的方法之一,透过分割数据群组,并使用一个以上的处理器,最理想的状况是有充足的中央处理器(CPU)。大型输入档案或数据数组是典型所谓的庞大数据组,但在嵌入式系统中,这可能意味着高I/O效能与服务带宽。在部分SoC架构中,多个输入数据源,例如网络接口端口,每个数据源都要以同样的方式处理,可以用统计的方法指派给多个处理器,执行相同的驱动程序/路由器程序代码,自然形成数据平行处理。
要以多个处理器来处理单一数据数组或单一输入串流,经常可使用“个个击破”(divide and conquer)式数据平行处理算法,例如图一的简单范例。这些算法对单处理器经常不尽理想,但它们运用延展性,善用运算带宽,弥补了效率不彰的问题。它们“以量取胜”。这些算法是平行运算中最具延展性的,但将可运作的循序程序改写成数据平行处理算法可能简单、也可能很困难、甚至不可能,这要视程序相依特性等因素而定。
如果应用中的绝大部分运算都由少数冗长但有规律的运算loop组成,希望提高现有应用效能的系统设计师最可能直接使用数据平行处理算法。
随着PC、工作站、与服务器用多核心“x86”处理器芯片的兴起,业界开始研究与投资在新一波的链接库与工作套件上,试图支持并更轻易地在为数不多的处理器上运用平行处理算法。这包括很多开放程序代码的设计,可针对MIPS等嵌入式架构改写。支持数据平行处理C/C++与FORTRAN的gcc OpenMP正在成为标准GNU编译程序系列的一部份。
控制平行处理程序设计
另外一种算法可称为控制平行处理程序设计(control-parallel programming),将程序根据工作而非输入加以分割。我们可将数据平行处理算法比喻为一家汽车工厂里,100位工人每一位个别负责制造一辆汽车。而控制平行处理程序则相当于一家工厂里只有一条生产线,但有100个分站,每个分站都有一位工人,执行不同的工作,占组装工作的1/100。图2说明简单的2站建构。生产线的作法通常效率较高,但能将组装能分割成多少工作分配搞不同工人还是有所限制。该限制对程序代码很重要,因为可能被延展到数千个处理器,但这对消费电子应用所采用的有限平行SoC架构通常不是问题。
即使不考虑到平行处理,软件工程师通常也将程序分为几个阶段,以简化程序设计团队的程序撰写、除错、与维护,也能降低指令内存与高速缓存的负担。在很多状况下,问题的控制平行分解已经提升到OS可见(OS-visible)工作的层次。类似UNIX系统的单一“cc”指令会循序呼叫C语言预处理器、编译程序、组译器、与链接程序。在SMP多处理器上,有些步骤可以同时执行,下一个程序将上一阶段的输出作为输入,使用档案,甚至可以用软件管道(pipe),其一直都是类似UNIX操作系统,像是Linux的功能之一。
当尚未分解成独立执行的工作时,必须采用软件工程的作法,让操作系统与基础硬件了解应用的阶段,并在“所有权”交给下一个阶段时,清楚地转换数据处理。和资料平行处理分解时不同的是不需要重新思考或重新设计组成阶段。可透过档案、插座、或管道沟通的程序,粗略地分解工作。如果要更精细地掌控,通常会使用名为pthreads的POSIX线程API,各种操作系统都支持该作法,包括Linux、Microsoft Windows、与许多实时操作系统。
要做的事越多,同时处理能力也变得更重要
如图3所示,虽然非当初设计目标,复杂、模块化、多任务的嵌入式软件系统经常展现“意外的”并行性。系统的整体任务可能包括执行多个工作,每一个工作都有不同责任,针对独特的输入组合做出回应。如果没有分时操作系统,这些工作必须各自在独立的处理器上执行。在分时单处理器上,它们交替地运用时间段落执行。如果以多处理器配合SMP操作系统,它们可以同时在所有可用的处理器上执行。
分布式处理
另一种形式的平行处理也变得很常见,虽然有时候它甚至不被视为“平行”,这就是分布式计算,网络主从式架构是最常见的型态。主从式程序设计基本上是分解控制流,程序工作不自行执行所有运算,而是将工作要求传送给系统中一或多个各有专职的工作。虽然主从式程序设计使用跨LAN与WAN的情形最为普遍,SMP SoC内工作之间的沟通也使用同样的方式。我们可使用未修改的主从式二进制文件(binaries),透过芯片内建或null “loopback”网络接口以TCP/IP沟通;或使用当地通信协议,传送内存中的数据缓冲器,效率还会更高。
就实务而言,上述任何一种技术都可以独立或配合使用,针对特定应用发挥以SMP为基础平台的优势。我们可建构分布式SMP服务器数据平行处理数组,每个服务器都建构控制流程管道。但要使这种设计发挥效率,就必须有很大的工作负荷与数据组。
系统软件支持是关键
在SoC系统中,可将工作以静态实体分解,分配给处理器,达到平行处理的目的(如一个处理器核心对应一个输入端口),我们可用硬件将工作分给处理器,这能减少软件的额外工作与大小,但却缺乏运作弹性。
如果嵌入式应用可用静态的方式分解为客户端和服务器,并利用芯片内部链接沟通,结合该系统所需的软件就只剩下传递信息在处理器之间建构共通的通讯协议的程序代码。该信息传递通信协议可提供某种程度的抽象化(abstraction),让一或多个处理器执行相同的基础应用程序代码,但就任何特定组态而言,处理器之间的负载平衡和硬件分割一样,都是静态的。如果要追求更有弹性的平行系统程序设计,就必须以软件将工作分配给共享资源的多处理器系统。
SMP系统的弹性与适应性
顾名思义,SMP操作系统对系统采用“对称”的观点。所有处理器都看到同样的内存、同样的输入/输出装置、与同样的整体操作系统状态。因此将程序从一个处理器转移到另一个变得非常简单与高效率,如图4的简单范例所示,也减轻了负载平衡的难度。不需额外程序设计或系统管理,一组程序可在单一系统上分时多任务,在SMP系统可用的CPU上同时执行。SMP排程程序,如Linux的排程程序,将开关处理器,使它们平衡地进展。
如果Linux应用当作多个程序执行,不需修改就能利用SMP平行处理的优势。在大多数的状况下,都不需重新编译;例外是动态连接到non-thread-safe链接库的二进制文件。
SMP Linux环境提供多种工具,让系统设计师调整可用处理器分享工作的方式。工作的优先级高低可调整,也能限制为在特定的处理器上执行。如果有适当的核心支持,它们可要求使用不同的实时排程方式。
类似UNIX的操作系统向来提供应用程序某种程度的工作排程掌控,即使是单处理器的分时系统。在Linux中,除了传统的nice shell指令与系统呼叫以外,还有更复杂的机制,可操纵工作优先级、工作组、或特定系统用户,如果有必要怀疑OS决定的话。
此外,在多处理器配置下,每一个Linux工作都有指定哪些处理器能为工作排程的参数。默认值为系统中所有处理器,但就像优先级一样,工作可透过taskset shell指令,或明确的系统呼叫,来选择特定的CPU。
支援SMP
SMP系统思维要求所有处理器都能看到同样地址的所有内存。对简单、低效能的处理器而言,做到这一点并不困难。我们只要将所有处理器的取得指令与加载/储存流量都放在同一个记忆与I/O总线上即可。但随着处理器数目增加,这种简单模式很快就失效了,因为总线很快就变成效能瓶颈。即使在单处理器系统中,因为高效能嵌入式核心对指令与数据带宽的要求,主存储器与处理器间都必须使用高速缓存。
现在每个处理器都有独立高速缓存的系统已不再被自动视为SMP。如果内存特定地址的最新数值只存在一个处理器的高速缓存中,就有基本 而且危险的不对称情况。这时候必须加入高速缓存同步协议,恢复系统的对称。在非常简单的系统中,所有处理器都连接到同一个总线上,只要让所有高速缓存控制器监控总线,了解那个高速缓存上面有特定记忆地址的最后数值。在较先进的系统上,例如MIPS32 1004K CPS,处理器以点对点的方式,透过交换系统而非总线连接内存。1004K同步管理员(coherence manager)会针对内存交易全面实行纪律,并产生必要的介入信号,就可以维持多个1004K处理器核心之间的高速缓存同步。因此1004K处理器对内存有对称的观点,Linux等SMP操作系统可不受限制地转移工作并动态平衡处理器负荷。
在嵌入式Soc中,整体运算时间相当部分会花在岔断服务上。这意味着需要控制优异的负载平衡与调整效能,包括可在哪里执行程序工作,也包括哪里可以执行岔断服务。Linux操作系统有“IRQ affinity”的控制接口,让用户与程序指定哪些处理器可为特定的岔断服务。要使用这种功能,接口必须透过下面的系统硬件,指定岔断给处理器。1004K整体岔断控制器(global interrupt controller)就能为1004K CPS提供这种功能。
快取同步基础架构不仅在对称多处理系统的处理器,也在处理器与输入/输出DMA通道都能产生成效。虽然MIPS32等RISC架构有支持以软件为基础I/O同步的功能,这需要在每一个I/O DMA作业前后都让CPU处理DMA缓冲器。这种处理对I/O密集的应用会造成可测量的效能影响。在1004K CPS中,因为透过I/O同步单元连接输入/输出DMA与内存,可掌控DMA流量,并与同步加载/储存流程整合,减低软件负担。
付出代价-并收到成效
1004K同步管理员会维持处理器、输入/输出、与内存之间流量的秩序,但这样做会增加处理器存取内存的时间周期。通常导致在管道延误(stall)时,这会导致损失额外处理器周期,因为要等指令或必要的数据填入高速缓存。但1004K平台使用MIPS32 34K核心系列率先引进的MIPS多线程架构,让单一核心同时执行数个指令串流。
1004K CPS的每一个核心都能透过虚拟处理组件(VPE)支持两个硬件线程,对操作系统而言,VPE看起来就像是一个CPU。这两个虚拟处理器分享相同的高速缓存与功能单元,并在管在线交错执行。如果一个VPE在等内存填写高速缓存而造成延误,另一个VPE能继续执行,保持管线忙碌。1004K处理器的多线程能解决原本会因为同步内存子系统的延迟的风险。
因为对软件而言,1004K处理器的VPE看里来就像真的处理器,包括有独立的岔断输入,我们可以利用原本管理多个核心的SMP操作系统逻辑,来管理组成的VPE。在最高的系统管理层级,所有VPE都在动作的双核心1004K系统看起来就像是一套4-way SMP系统。针对利用SMP能力而撰写会配置的软件自然能利用多线程功能,反之亦然。
由于对系统资源保持对称,相较于在独立核心上执行两个线程,两个线程竞争单处理器管线的效能会比较低,由多线程CPU构成的同步丛集相当普遍,所以效能低落的问题在服务器系统上已存在多年。而1004K的SMP Linux核心可优化负载平衡,如果针对要优化耗电量,排程程序可以一次将工作加载一个核心的虚拟处理器,其他虚拟处理器可处于低耗电状态。如果要优化效能,可以将工作先分配给不同的核心,只在所有核心都有工作执行时,才将工作交给多个VPE。
结论
我们可以多种方式利用芯片内建多处理功能,提高SoC的效能。工作静态分解(根据输入数据或处理功能)的效率可能很高,但也非常缺乏弹性。SMP平台与软件通常只需要稍微修改应用程序代码,甚至完全不需修改,可以提供弹性极高的高效能运算平台,且比单处理器快得多。多线程和SMP平行处理可以发挥相辅相成的效果,充分运用各处理器的管线资源。MIPS32 1004K Coherent Processing System将MIPS多线程与同步SMP整合到单一IP单元,提供高延展性、高密度的嵌入式运算功能。