从理论上说,一个64位处理器可以对多达16 EB(艾字节)的物理内存(4 GB的平方)寻址。相比之下,32位处理器只能对最多4 GB内存寻址。对于有些应用程序来说,只能对4 GB内存寻址是一种限制,例如轻易就会超过4 GB的数据库。因此,升级至64位寻址就能实现对更大数据集的操作。 X86处理器的64位指令集扩展有AMD64、EMT64,x86-64(简称x64),这些指令集扩展不仅增加了处理器能寻址的内存,而且还通过消除或减轻两大问题提升了性能。 解决的第一个问题是基于栈的调用约定。此约定导致代码不得不用大量的存储和加载指令将参数传入函数。在32位代码中,当函数被调用时,其所有参数都需要存储在栈上。该函数首先要将从栈中弹出的参数加载到寄存器。在64位代码中,参数保存在寄存器中,避免了所有的加载和存储操作。 这一点可在代码清单1-4中看出,代码清单1-4为同样的代码基于64位x86指令集的编译结果。 代码清单1-4 将某个地址的变量+1的64位x86汇编代码 addl $1, (%rdi) //将地址<code>%rdi</code>的值+1 ret //从例程中返回 在这个例子中,我们使用的指令减少到两条,而不是代码清单1-3中的3条。这两条指令是将寄存器%rdi指向的值+1的增量指令,以及返回指令。 由64位寄存器解决的第二个问题,是将通用寄存器从32位代码的6个增加到64位代码的14个。增加寄存器数量减少了寄存器溢出与载入的次数。 由于这两个改变,很容易将变更为64位代码视为性能提升。但这样想并不完全正确。寄存器数量改变与调用约定改变只是恰巧与过渡到64位同时发生,但这两者也可引入到32位x86处理器。但变更为64位是一个重新评估架构以及作出这些根本性改进的良机。 64位地址空间实际上带来的变化是性能损失。指针从4字节的结构改为8字节的结构。类Unix操作系统中,长整型变量也从4字节变为8字节。当变量大小增加,应用程序的内存占用也相应增加,因此导致性能下降。例如,可以考虑代码清单1-5所示的C数据结构。 代码清单1-5 包含整数的一个指针数组的数据结构 struct s { int *ptr[8]; }; 为32位编译时,此结构占据8 * 4字节 = 32字节,因此每一个64字节的缓存线可以包含两个结构。为64位地址编译时,指针大小翻倍,因此此结构需要64字节,一个结构就占据了一个缓存线。 设想一下,如果32位版本应用程序中包含一个这种结构的数组,当从内存中提取一个此结构时,也会同时提取下一个。在64位版本的相同代码中,则一次只能提取一个结构。从另一个角度来看,对于相同的计算,64位版本需要从内存提取的数据高达32位版本的两倍。对于某些应用程序,内存需求量的增加可能会导致应用程序性能明显下降。然而,在x86处理器上,得益于其他改进,绝大多数应用程序最终会获得性能增益。有些编译器可生成使用EMT64指令集扩展和ABI的二进制文件,但这类二进制文件将应用程序限制在32位地址空间中,这使得应用程序既能从指令集改进中获得高性能,又不会因内存需求量增加而损失性能。 让我们将这一情况与SPARC处理器略加对比。指针大小和长整型数据大小增加同样会造成SPARC处理器的性能损失,但SPARC的32位代码调用约定是通过寄存器传递值,且已有大量寄存器可用。因此,为SPARC处理器编译的代码通常会因内存需求量而导致性能小幅下降。