处理器执行指令,指令是所有计算的基本构建块,执行如加法、从内存提取数据或将数据存储回内存等任务。指令在寄存器上进行操作,寄存器保存变量的当前值和其他机器状态。考虑代码清单1-1所示的代码段,其功能为递增指针ptr指向的整型变量。 代码清单1-1 将某个地址的变量+1的代码 void func( int * ptr ) { ( *ptr )++; } 代码清单1-2显示了此代码段编译为SPARC汇编代码的结果。 代码清单1-2 将某个地址的变量+1的SPARC汇编代码 ld [%o0], %o5 //将地址%o0的值加载到寄存器%o5 add %o5, 1, %o5 //将寄存器%o5的值加1 st %o5, [%o0] //将寄存器%o5的值存回地址%o0 retl //从例程返回 SPARC代码 通过寄存器%o0传递指针ptr。加载指令将该地址的值载入寄存器%o5。接着增加寄存器%o5的值,接着存储指令将寄存器%o5中的新整数值存储到%o0所指向的内存位置,最后返回指令退出例程。 代码清单1-3显示了这一源代码基于32位x86处理器的编译结果,x86的汇编代码确实与SPARC汇编代码有所不同。第一个不同之处在于32位模式的x86有一个基于栈的调用约定。这意味着传递给函数的所有参数都将存储到栈,而之后函数要做的第一件事就是取回存储在栈的参数。因此,代码做的第一件事就是从栈中加载指针的值(在本例中,就是从地址%esp+4获取值,然后将值放入寄存器%eax。 代码清单1-3 将某个地址的值+1的32位x86汇编代码 movl 4(%esp), %eax //将地址%esp+4中的值加载到%eax addl $1, (%eax) //将地址%eax中的值+1 ret //从例程返回 接下来,我们遇到了x86和SPARC汇编语言的第二个区别。SPARC是精简指令集计算机(RISC),也就是说SPARC只有少量的简单指令,且所有操作都必须由这些简单的构建块构成。x86则是复杂指令集计算机(CISC),因此其指令能完成更复杂的操作。x86指令集有一个指令是将某个内存地址中的值与一个增量相加。在本例中,寄存器%eax中存放一个地址,指令将该地址中的值加1。执行这条CISC指令,即能达到SPARC 3条RISC指令的执行结果。 为了进行计算,这两个代码段都使用了两个寄存器。SPARC的代码中使用了寄存器%o0和%o5,而x86代码则使用了%esp和%eax。然而,两个代码段使用寄存器的目的不同。x86代码将寄存器%esp用作栈指针,指向保存函数调用参数的内存区。与之不同,SPARC代码则用寄存器传递函数参数。传递参数的方法称为调用约定,是平台应用程序二进制接口(ABI)的一部分。该规范说明应该怎样编写程序才能使其在某个特定平台上正确运行。 这两个代码段都使用一个寄存器保存要访问的内存地址。SPARC的代码中使用%o0,x86代码中使用%eax。两个代码段的另一区别是SPARC代码中用寄存器%o0保存变量的值,因此SPARC的代码不得不使用3条指令加载值,加1,然后将结果存回内存。相比之下,x86代码只用了一条指令。 两个处理器之间的又一个不同之处是可用寄存器的数量。SPARC实际上有32个通用寄存器,而x86处理器只有8个通用寄存器。由于某些通用寄存器具有特殊功能,最终SPARC处理器有约24个寄存器可供应用程序使用,而32位x86处理器只有6个可用于应用程序。然而,由于x86处理器采用CISC指令集,所以并不需要使用寄存器来保存暂时需要的值,在本例中,变量在内存中的当前值甚至并未加载到寄存器。因此,尽管x86处理器寄存器数量少得多,仍然可以编写代码,因此寄存器不是问题。 但不可否认,寄存器数量多确实有优势。如寄存器数量不足,寄存器就不得不在清空前先将其内容存储到内存,使用后再将原内容从内存重新载入。这称为寄存器溢出与载入,需要两个额外的指令,并需使用缓存空间。 x86的64位指令集扩展中引入的两个性能优势就是寄存器数量增加以及调用约定大为改进。