以x86体系为代表的CPU已经占有了桌面和服务器处理器的绝大部分份额,而且这个趋势还在不断增强。CPU具有兼容性强、易编程、应用资源丰富、价格低廉的优势,但是在某些领域,CPU存在天然的缺陷,以FPGA、GPU为代表的硬件可以克服CPU的缺陷,因此也拥有自己的市场。
1.1 图解各类型芯片
从设计软件进行计算任务的软件工程人员的角度,可以将芯片分为CPU、GPU、FPGA和ASIC等类型。
对处理器芯片的特性和应用,理论上是软件人员具有最大发言权。但每一类芯片的使用和理解都不是一件简单的事情,以CPU为例:即使从事CPU环境的编程设计多年,也很难谈得上深入理解了CPU的设计思想。能深入各种芯片编程的软件人员更是凤毛麟角,更别谈进行分析和比较。另外一个问题是软件和硬件设计已分离多年,软件设计人员,很难深入理解芯片的设计思路,即使操作系统的设计人员也一样。而芯片的设计厂商由于利益相关,往往只宣扬各自的优点,回避缺陷,在测试对比中选择有利的测试条件,产生对己有利的测试数据。测试数据的真真假假,更加混淆了技术人员的视听。
在对各种芯片比较和研究的过程中,我们认为不应该沉湎于具体芯片的架构和设计思路,而应该关注芯片的实际应用。有两个原因支持我们的思路。一个原因是芯片的架构非常繁复,熟悉各种芯片几乎是不可能的任务。另一个更重要的原因是技术的价值在于应用。不管何种芯片设计或者架构,最终决定芯片价值的是实际的应用。从应用的角度出发,应按照易用性和经济性两个维度考察芯片。
易用性指用芯片进行编程的难度以及相关编程资源的获取难度。这个指标技术人员虽然不怎么关心,但其实对芯片发展有重大,甚至是绝对的重要性。例如在FPGA的编程实践中,相关的编程资源非常难以获得,即使获得也往往是代价巨大。比如常见的JPEG图片,相关的FPGA编解码库往往需要付出数万美元的成本,这和CPU领域大量的开源库完全不能相提并论。价格的昂贵还带来了测试和验证的繁杂。提供库资源的厂商往往需要旷日持久的沟通和谈判以及签署协议才能进行验证工作,这在很多研发项目运作中几乎是不可承受的。
经济性指提供相同性能情况下的芯片成本。芯片往往型号众多,比如FPGA芯片,既有上千美元甚至几千美元售价的高端型号,也有几美元计价的低端型号。脱离芯片成本谈论性能没有意义。需要指出的是,成本是综合的运营成本,而非单独的芯片购买成本。比如某款芯片如果性能等于CPU十倍,那么它不仅仅是顶替了十颗CPU,而是顶替了十台服务器的采购成本以及十台服务器的运营成本,考虑到实际的运营成本往往大于采购成本,后者可能更具有重要性。
1.2 芯片的分类
对常用的处理器芯片进行分类,有一个明显的特点:CPU&GPU需要软件支持,而FPGA&ASIC则是软硬件一体的架构,软件就是硬件。这个特点是处理器芯片中最重要的一个特征。
上图可以从两个角度来说明:从ASIC->CPU的方向,沿着这个方向芯片的易用性越来越强,CPU&GPU的编程需要编译系统的支持,编译系统的作用是把高级软件语言翻译成机器可以识别的指令(也叫机器语言)。高级语言带来了极大的便利性和易用性,因此用CPU&GPU实现同等功能的软件开发周期要远低于FPGA&ASIC芯片。沿着CPU->ASIC的方向,芯片中晶体管的效率越来越高。因为FPGA&ASIC等芯片实现的算法直接用晶体管门电路实现,比起指令系统,算法直接建筑在物理结构之上,没有中间层次,因此晶体管的效率最高。
本质上软件的操作对象是指令,而CPU&GPU则扮演高速执行指令的角色。指令的存在将程序执行变成了软件和硬件两部分,指令的存在也决定了各种处理器芯片的一些完全不同的特点以及各自的优劣势。
FPGA&ASIC等芯片的功能是固定的,它们实现的算法直接用门电路实现,因此FPGA&ASIC编程就是用门电路实现算法的过程,软件完成意味着门电路的组织形式已经确定了,从这个意义上,FPGA&ASIC的软件就是硬件,软件就决定了硬件的组织形式。软硬件一体化的特点决定了FPGA&ASIC设计中极端重要的资源利用率特征。利用率指用门电路实现算法的过程中,算法对处理器芯片所拥有的门电路资源的占用情况。如果算法比较庞大,可能出现门电路资源不够用或者虽然电路资源够用,但实际布线困难无法进行的情况。
存在指令系统的处理器芯片CPU&GPU不存在利用率的情况。它们执行指令的过程是不断从存储器读入指令,然后由执行器执行。由于存储器相对于每条指令所占用的空间几乎是无限的,即使算法再庞大也不存在存储器空间不够,无法把算法读入的情况。而且计算机系统还可以外挂硬盘等扩展存储,通过把暂时不执行的算法切换到硬盘保存更增加了指令存储的空间。
处理器芯片各自长期发展的过程中,形成了一些使用和市场上鲜明的特点。CPU&GPU领域存在大量的开源软件和应用软件,任何新的技术首先会用CPU实现算法,因此CPU编程的资源丰富而且容易获得,开发成本低而开发周期,而FPGA&ASIC编程需要的资源通常很难获得,这些资源往往以IP(intellectual property)的方式授予和收费,授予的周期往往很长而且需要签署法律协议,而费用也很昂贵。导致FPGA&ASIC的开发成本高而且周期很长。
1.3 CPU架构和编程设计
无论是x86体系为代表的繁杂指令系统(CISC)CPU还是精简指令系统(RISC)CPU,其核心都是执行一套指令系统。x86体系的CPU不断更新换代,不断提升主频,采用更先进的工艺和新架构,目的就是为了更高性能地执行x86指令。因为X86系列的CPU应用广泛,已经成为事实上的标准,本文所指的CPU特指X86系列的CPU。
从CPU内部结构观察,大致可分为控制器和执行器,再加上存储管理部件MMU以及总线接口部件。控制器不断从存储器取出指令,进行指令译码,执行器从译码完成的指令队列中取出译码指令执行。各个功能部件既能独立工作,又能与其他部件配合工作,下图给出了CPU各个部件之间的指令操作流水图。
指令系统是计算机系统发展中的巨大进步。借助指令系统,高级语言的出现成为可能,大大方便了计算机的应用。但是事情的另一面是使用指令系统后,所有的计算任务都要翻译为指令,执行一个简单的计算任务可能就需要多条指令完成。从晶体管的角度来看,简单的计算任务可能就需要众多的晶体管共同参与。为提升性能,采用指令系统的CPU,其性能设计出发点是增强指令执行的效率。
以前CPU的架构设计一直围绕如何增强指令执行的效率,为此采取的措施是不断提升主频、加多流水线(奔腾首次应用了双路流水,而现在的CPU往往拥有20以上的流水数目
)以及增加CPU的cache提升取指令的效率(早期奔腾芯片拥有几十K的缓存,而至强E5的三级缓存超过10MB,甚至可达到30MB)。近几年,CPU的架构更加重视多核的应用,期望通过多核实现更高的性能。
CPU设计出发点是增强指令的运行性能,因此CPU的核心功能强大,占用的晶体管资源庞大,具有很高的运行效率,因此CPU的多核不可能做到非常多。目前顶级的X86 CPU具有十多个核心,而GPU已经达到几千个核心。
对编程设计来说,如果线程完全独立的执行计算任务,线程间数据不存在共享和竞争关系,那么并行效率可以达到线性效果。不过现实中的编程,有很大一类是单任务的并行化,即将一个繁杂的任务通过多核并行执行来加速,那么就面临两个困难:一个是将任务并行化之后面临多线程之间的切换代价。因为CPU核心功能强大,因此操作系统切换线程时需要CPU内部大量的状态寄存器置位,所以线程之间切换是代价很大的操作(实测中,线程切换大概需要几十微秒),如果计算任务的执行时间小于这个数字,那么多线程执行对性能提升可能并无收益,甚至可能效率反而下降。
另一个问题是任务执行中数据的依赖关系。如果计算任务中某部分必须利用前面部分的计算结果,即存在数据依赖性,那么就必须等前面部分计算完成才能执行后面的计算,而不可能并行计算。数据依赖是计算中经常遇到的场景,编程设计需要调整代码结构尽量减少相关性提升并行性。