前言代码写了这么多,你知道a=1+2的代码是如何被CPU执行的吗?软件用了这么多,你知道32位和64位软件有区别吗?32位操作系统可以在64位计算机上运行吗?64位操作系统可以在32位计算机上运行吗?若否,原因为何??CPU见多了,大家都知道CPU通常分为32位和64位,你知道64位相对于32位CPU有哪些优势吗?64位CPU的计算性能一定比32位CPU高很多吗?知道了就不要慌,然后一步一步,一层层的攻破这些问题。文本图灵机的工作方式如果想了解程序执行的原理,可以从“图灵机”说起。组件是什么,程序是如何执行的。图灵机长什么样?从下图可以看到图灵机的实际外观:图片来源来自:http://www.kristergustafsson.me/turing-machine/图灵机的基本组成如下:(1)有一种“纸带”,纸带由连续的格子组成,每个格子都可以写字符,纸带就像内存,纸带上格子的字符就像内存中的数据或程序;(2)有一个“读写头”,读写头可以读取纸带上任意格子的字符,也可以将字符写入纸带的格子中;(3)读写头上有一些部件,如存储单元、控制单元和运算单元:存储单元用于存储数据;控制单元用于识别字符是数据还是指令,控制程序的流程等。操作单元用于执行操作指令;知道了图灵机的组成之后,我们以简单的数学运算1+2为例,看看它是如何执行这行代码的。首先用读写头将“1、2、+”三个字符写入纸带上的三个格子中,然后读写头先停在1个字符对应的格子上;然后,读写头将Enter1读入存储设备,这个存储设备称为图灵机的状态;然后读写头向右移动一格,用同样的方法将2读入图灵机的状态,所以现在图灵机的状态存储了两个连续的数,1和2;将读写头再向右移动一格,就会遇到+号。读写头读到+号后,会把+号传给“控制单元”,控制单元发现是一个+号不是数字,所以不存入状态,因为+号是运算符指令,作用是添加当前状态,通知“计算单元”开始工作。运算单元收到状态中添加值的通知后,会读取并计算状态中的1和2,然后将计算出的结果3存储到状态中;最后,运算单元将结果返回给控制单元,控制单元将结果传给读写头,读写头右移,将结果3写入纸带的格子中;通过上面图灵机计算1+2的过程可以发现,图灵机的主要作用是读取纸带的格子中的内容,交给控制单元来识别是否是字符是一个数字或操作符指令。如果它是一个数字,它将存储在图灵机的状态中。如果是算子,会通知算子单元读取状态中的值。进行计算,最后将计算结果返回给读写头,读写头将结果写入纸带的格子中。事实上,图灵机看似简单的工作方式,与我们今天的计算机基本相同。接下来,让我们来看看当今计算机的组成和工作方式。冯·诺依曼模型1945年,冯·诺依曼等计算机科学家提出了一份关于计算机具体实现的报告,该报告沿用了图灵机的设计,还提出用电子元件构造计算机,并同意用二进制进行计算而Storage也将计算机的基本结构定义为五个部分,即中央处理器(CPU)、存储器、输入设备、输出设备和总线。这五个部分也被称为冯诺依曼模型。接下来,我们来看一下这五个部分的具体作用。1、内存我们的程序和数据都是存放在内存中的,存储区域是线性的。数据存储的单位是二进制位(bit),即0或1。最小的存储单位是字节(byte),1字节等于8位。内存的地址从0开始编号,然后自增排列。最后一个地址是内存的总字节数-1。这个结构在我们的程序中就像一个数组,所以在内存中读写任何数据的速度都是一样的。的。2、中央处理器中央处理器就是我们常说的CPU。32位和64位CPU的主要区别在于一次可以计算多少字节的数据:32位CPU一次可以计算4个字节;64位CPU一次可以计算8个字节;这里的32位和64位通常指的是CPU的位宽。CPU之所以这样设计,是为了能够计算出更大的数值。如果是8位CPU,一次只能计算1字节0~255范围内的值,所以不可能一次完成10000*500的计算。因此,为了一次计算大数,CPU需要支持多个字节一起计算,所以CPU位宽越大,可以计算的值就越大。例如32位CPU能计算的最大整数是4294967295。CPU内部还有一些部件,如寄存器、控制单元、逻辑运算单元等。其中,控制单元负责控制CPU的工作,逻辑运算单元负责计算,而寄存器又可以分为很多种,每个寄存器的作用也不尽相同。CPU中寄存器的主要作用是存放计算数据。你可能想知道为什么有内存还要寄存器?原因很简单,因为内存离CPU太远了,而寄存器在CPU里面,紧挨着控制单元和逻辑运算单元,自然计算速度会很快。通用寄存器类型:通用寄存器用于存放需要计算的数据,比如需要相加的两个数据。程序计数器用于存储CPU将执行下一条指令的“内存地址”。请注意,不会存储下一条要执行的指令。此时指令还在内存中,程序计数器只存储下一条指令的地址。指令寄存器用于存放程序计数器指向的指令,即指令本身。在执行指令之前,指令存储在这里。3.总线总线用于CPU与内存等设备之间的通信。总线可分为三种:地址总线,用于指定CPU将要操作的内存地址;数据总线,用于读写内存数据;控制总线用于发送和接收信号,例如中断、设备复位和其他信号。CPU收到信号后自然响应。这时,它还需要控制总线;当CPU要读写内存数据时,一般需要经过两条总线:一是通过“地址总线”指定内存的地址;然后通过“数据总线”传输数据;4、输入输出设备输入设备向计算机输入数据,计算机计算后将数据输出给输出设备。这期间,如果输入设备是键盘,需要在按键按下时与CPU进行交互,这时就需要控制总线。线路位宽和CPU位宽数据是如何通过地址总线传输的?其实就是通过工作电压,低电压表示0,高电压表示1。如果构造一个高、低、高等信号,其实就是101个二进制数据,即十进制表示5。如果只有一行,则表示一次只能传输1位数据,即0或1。那么传输101的数据。需要3次才能完成转账,效率很低。这种逐位的传输方式称为串行,下一位必须等待前一位传输完成后才能传输。当然,如果你想一次传输更多的数据,只需要加线,然后就可以并行传输数据了。为了避免低效的串行传输,线的位宽最好能一次访问所有的内存地址。CPU要操作的内存地址需要地址总线。如果只有一根地址总线,每次只能表示“0或1”,所以CPU一次只能操作2个内存地址;如果要让CPU去操作4G的内存,就需要32条地址总线,因为2^32=4G。知道了线路位宽的含义,我们再来看看CPU位宽。CPU的位宽不能小于线路的位宽。比如32位的CPU控制40位宽的地址总线和数据总线,工作起来会非常复杂和麻烦,所以32位的CPU最好搭配32位宽的线。匹配,因为一个32位的CPU最多只能操作32位宽的地址总线和数据总线。如果用32位的CPU来计算两个64位的数,需要把两个64位的数分成2个低位32位数和2个高位32位数来计算,先加两个低位32位数,计算进位,然后将两个高位32位数相加,最后加上进位,即可计算结果。可以发现32位的CPU无法计算出一次将两个64位的数相加的结果。.对于64位CPU来说,两个64位数相加的结果是可以一次性计算出来的,因为64位CPU一次可以读入64位数,而64位内部的逻辑运算单元CPU也支持64位数字的计算。但并不代表64位CPU的性能就比32位CPU高很多。很少有应用程序需要计算超过32位的数字,因此如果计算量不超过32位的数字,则32位和64位CPU没有区别。是的,64位的优势只有在计算32位以上的数时才能体现出来。另外,一个32位的CPU最多只能运行4GB的内存,即使你装了8GB的内存条也没用。64位CPU寻址范围大,理论上最大寻址空间为2^64。程序执行的基本过程在前面。我们知道程序在图灵机上的执行过程。接下来我们看看程序在冯·诺依曼模型上是如何执行的。程序其实就是一条指令,所以程序的运行过程就是一步步执行每一条指令,CPU负责执行指令。CPU执行程序的过程是这样的:首先CPU读取“程序计数器”的值,也就是指令的内存地址,然后CPU的“控制单元”操作“地址总线”"指定要访问的内存地址,然后通知内存设备准备数据。数据准备好后,命令数据通过“数据总线”传输给CPU。CPU从内存中接收到数据后,将命令数据存入“命令寄存器”。第二步,CPU对“指令寄存器”中的指令进行分析,确定指令的类型和参数。如果是计算型指令,则将指令交给“逻辑运算单元”进行运算;如果是存储型指令,则交给“控制单元”执行;第三步,CPU执行完指令后,“程序计数器”的值加1,表示指向下一条指令。这个自增的大小是由CPU的位宽决定的。例如对于32位CPU,指令为4个字节,需要4个内存地址存储,所以“程序计数器”的值会自增4;简单概括就是,当一个程序执行时,CPU会根据程序计数器中的内存地址,将要执行的指令从内存中读取到指令寄存器中执行,然后根据指令的长度递增到顺序读取下一条指令。CPU从程序计数器中读取指令,执行它们,然后继续执行下一条指令。这个过程会一直循环下去,直到程序执行结束。这种不断循环的过程称为CPU的指令周期。a=1+2执行具体过程了解了基本的程序执行过程后,接??下来以a=1+2为例进一步分析程序在冯·诺依曼模型中的执行过程。CPU不知道字符串a=1+2。这些字符串只是为了让我们的程序员理解。如果这个程序能够运行,就需要将整个程序翻译成汇编语言程序。这个过程称为编译成汇编代码。对于汇编代码,我们还需要使用汇编器将其翻译成机器码。这些机器码是由0和1组成的机器语言,每段机器码就是一条计算机指令。这才是CPU真正能够理解的。我们来看看a=1+2在32位CPU上的执行过程。程序编译时,编译器通过分析代码发现1和2是数据,所以程序运行时,内存中会有一块专门的区域存放这些数据,这块区域就是“数据段”。如下图数据1和2的区域位置:数据1存放在0x100;数据2存储在0x104;注意,数据和指令存放在不同的区域,存放指令区的地方叫做“文本域”。编译器会将a=1+2翻译成4条指令并将它们存储在文本字段中。如图,这4条指令存放在0x200~0x20c区域:0x200的内容是加载指令将地址0x100的数据1加载到寄存器R0中;0x204的内容是load指令加载地址为0x104的数据20x208的内容是add指令将寄存器R0和R1中的数据相加,结果存入寄存器R2;0x20c的内容是存储指令,将寄存器R2中的数据存储回数据段中的地址0x108,这个地址也是变量a内存中的地址;编译完成后,真正执行程序时,会将程序计数器设置到地址0x200,然后依次执行这4条指令。在上面的例子中,由于是在32位CPU上执行的,一条指令占用32位,所以你会发现每条指令之间有4个字节。数据的大小取决于您在程序中指定的变量类型。比如int类型的数据占4个字节,char类型的数据占1个字节。1.指令在上面的例子中,我针对图中指令的内容编写了简单的汇编代码。有对应的机器码,CPU通过解析机器码就知道了指令的内容。不同的CPU有不同的指令集,对应不同的汇编语言和不同的机器码。接下来选择最简单的MIPS指令集,看看机器码是如何生成的,这样就能理解二进制机器码的具体含义。MIPS的指令是一个32位的整数,高6位代表操作码,表示这条指令是一条什么样的指令,其余26位的内容对于不同的指令类型是不同的。主要有R、I、J三种类型,下面我们来详细了解一下这三种类型的含义:R指令,用于算术和逻辑运算,读写数据的寄存器地址。如果是逻辑位移操作,后面有位移操作的“位移量”,最后一个“功能码”是在前面的操作码不够用的时候,对操作码进行扩展,代表对应的具体指令;I指令,用在数据传输、条件分支等。对于这类指令,没有位移和操作码,也没有第三个寄存器。而是将这三部分直接组合成一个地址值或一个常量;J指令,用于跳转,高6位其余26位为跳转后的地址;接下来,我们将前面例子中的指令:“add指令将寄存器R0和R1的数据相加,结果放入R3”翻译成机器码。加法运算add指令属于R指令类型:add对应的MIPS指令中的opcode为000000,末尾的函数码为100000,这些值是固定的,查看MIPS指令集手册;rs表示第一个寄存器R0的编号,即00000;rt表示第二个寄存器R1的编号,即00001;rd表示目标的临时寄存器R2的编号,即00010;因为不是位移运算,所以位移为00000上面的数加起来就是一条32位的MIPS加法指令,用16进制表示的机器码就是0x00011020。编译器编译程序时,会构造指令。这个过程称为指令的编码。当CPU执行程序时,它会解析指令。这个过程称为指令译码。大多数现代CPU使用流水线来执行指令。所谓流水线就是把一个任务拆分成多个小任务,所以一条指令通常分为4个stage,称为4-stagepipeline,如下图所示:4个stage的具体含义:CPU读取通过程序计数器获取内存地址对应的指令,这部分称为Fetch(获取指令);CPU对指令进行解码,这部分称为Decode(指令译码);CPU执行指令,这部分称为Execution(执行指令);CPU将计算结果存回寄存器或将寄存器的值存入内存,这部分称为Store(数据回写);以上四个阶段,我们称之为指令周期(InstrutionCycle),CPU的工作是一个又一个周期,周而复始。其实不同的阶段其实是由计算机中不同的部件来完成的:在取指令阶段,我们的指令是存放在内存中的。实际上,通过程序计数器和指令寄存器取指令的过程是由控制器完成的。操作;指令的译码过程也由控制器完成;指令执行的过程,无论是算术运算、逻辑运算、数据传输,还是条件分支运算,都是由算术逻辑单元进行运算,即由运算符处理。但如果是简单的无条件地址跳转,则直接在控制器中完成,无需使用运算器。2、指令的种类指令从功能上可以分为5类:数据传输指令,比如store/load是寄存器和内存之间进行数据传输的指令,mov是将数据从一个内存地址移动到另一个内存地址地址说明;运算类指令,如加减乘除、位运算、比较等,最多只能处理两个寄存器中的数据;跳转型指令,通过修改程序计数器的值来跳转执行指令的过程,如编程中常见的if-else、swtich-case、函数调用等。信号型指令,如中断发生时的指令陷阱;空闲类型的指令,如指令nop,执行后CPU会空闲一个周期;3.指令的执行速度。CPU的硬件参数会有参数GHz,比如1GHz的CPU指的是1G的时钟频率,也就是说1秒会产生1G次的脉冲信号,每个高到-一个脉冲信号的低电平跳变为一个周期,称为一个时钟周期。对于CPU来说,在一个时钟周期内,CPU只能完成一个最基本的动作。时钟频率越高,时钟周期越短,工作速度越快。一条指令能否在一个时钟周期内执行?答案不一定。大多数指令不能在一个时钟周期内完成,通常需要几个时钟周期。不同的指令需要不同的时钟周期。加法和乘法都对应一条CPU指令,但乘法比加法需要更多的时钟周期。如何让程序运行得更快?程序执行时,消耗的CPU时间越少,说明程序运行速度快。对于程序的CPU执行时间,我们可以拆解成CPU时钟周期数(CPUCycles)和时钟周期时间(ClockCycleTime)。时钟周期时间就是我们前面提到的CPU的主频。主频越高,CPU工作得越快。比如我电脑的CPU是2.4GHz的四核IntelCorei5,这里的2.4GHz就是电脑的Mainfrequency,时钟周期时间是1/2.4G。想要CPU跑得更快,自然要缩短时钟周期时间,也就是提高CPU频率,但今天不是那一天,摩尔定律早就失效了,今天CPU频率翻倍很难.另外,换更好的CPU是我们软件工程师无法控制的。我们应该关注另一个倍增因素——CPU时钟周期数。如果可以减少程序所需的CPU时钟周期数,同样如此。可以提高程序的性能。对于CPU时钟周期数,我们可以进一步拆解为:“指令数×每条指令的平均时钟周期数(CyclesPerInstruction,简称CPI)”,因此CPU执行时间的公式为程序可以改成如下:因此,要想程序运行得更快,可以优化这三个:指令数表示执行程序需要多少条指令,需要哪些指令。这个级别基本上是编译器优化的。毕竟同样的代码,在不同的编译器中,编译出的计算机指令会有不同的表现形式。每条指令的平均时钟周期数CPI表示一条指令需要多少个时钟周期。大多数现代CPU使用流水线技术(Pipline),使一条指令所需的CPU时钟周期数尽可能少;时钟周期时间表示计算机的主频取决于计算机硬件。一些CPU支持超频技术。开启超频意味着CPU的内部时钟调整得更快,所以CPU工作得更快,但这是有代价的。CPU运行速度越快,散热压力就越大,CPU很容易死机。很多厂商为了跑分而跑分,基本都是从这三个方面入手,尤其是超频。最后,我们来回答一下开头的问题。(1)64位CPU相比32位CPU有什么优势?64位CPU的计算性能一定比32位CPU高很多吗?64位CPU相对于32位CPU的优势主要体现在两个方面:64位CPU可以计算32位以上的数,32位CPU如果要计算32位以上的数,需要分多步计算,效率没有那么高,但是大部分应用很少计算这么大的数,所以只有计算数大的时候才能体现64位CPU的优势,否则,32位CPU的计算性能相差不大。64位CPU可以寻址更大的内存空间。32位CPU的最大寻址地址是4G。即使加8G内存,也只能寻址4G。64位CPU最大寻址地址为2^64,远远超过32位CPU最大寻址地址的2^32。(2)你知道32位和64位软件的区别吗?32位操作系统可以在64位计算机上运行吗?64位操作系统可以在32位计算机上运行吗?若否,原因为何?64位和32位软件其实代表的是指令是64位还是32位:如果在64位机器上执行32位指令,需要兼容机制来实现兼容运行。但是如果64位的指令在32位的机器上执行,就会比较困难,因为32位的寄存器不能存放64位的指令;操作系统其实就是一个程序,我们也会看到操作系统会分为32位操作系统,64位操作系统,它的代表意义就是程序在操作系统中的指令条数,比如作为64位操作系统,指令是64位的,所以不能安装在32位机器上。简而言之,硬件中的64位和32位是指CPU的位宽,软件中的64位和32位是指指令的位宽。
