对于每个指令集,都有这些种类的定义。例如,一个字节是8位或9位或其他某个大小。一个单词是由该架构定义的意思。
8086/8088将一个字节定义为8位,一个单词定义为16位。你有16位寄存器,但你也可以将它们用作8位半寄存器,ax是一个16位寄存器,ah是ax的上半部分,al是下半部分。虽然不典型,但这就是他们的做法。后来,80x86 EAX成为32位寄存器,其中ax是下半部分,以此类推。由于一个单词被定义为16位,因此EAX被定义为双字或dword。后来出现了64位寄存器……在这个架构中,64位被称为四倍字。
ARM将一个字节定义为8位,半字定义为16位,单词定义为32位。其他人也这样做。
这是硬件层面的事情。然后你会涉及到编程语言,程序员们可以自由更改定义,也可以利用语言中的typedefs或其他东西来创建或更改其他定义,并且可以将它们任意设置大小。语言的大小不一定要与硬件匹配,有时这样做是一个好主意,但不必如此,这取决于实现特定目标的编译器和/或后端的人员。
关于浪费...
x86架构使用可变字长指令。这意味着有一些8位指令,一些16位指令,24位,32位等等。您可能想要从一个寄存器移动到另一个寄存器,而该指令可能只需要一个字节,但如果您想将16位值移动到寄存器中,则可能需要一个字节来表示我要将立即数移动到寄存器中,然后需要两个字节来定义立即数,总共三个字节的指令。该指令集是在内存宽度为8位时发明的,对于8位宽度的内存系统,这是有意义的。现在我们使用32位和64位宽度的内存系统,这非常痛苦。尽管如此,指令集具有8位寄存器ah、al、bh、bl等,并且可以对8位寄存器执行8位操作。因此,将布尔值或其他大小的数据设置为1字节大小以节省一些空间可能是有意义的。无论如何,您已经在将内存分成不同大小并且没有对齐,这样做也无妨。
ARM架构中,传统的ARM指令始终是32位宽度,不多也不少。虽然寄存器比x86多,但它们没有被分成半个或字节大小的寄存器。你没有8位操作,例如比较等。一切都是32位的,因为它是寄存器对寄存器进行的(有一些小的立即数,是的)。如果在高级语言中有一个变量永远不会小于-5或大于+20,你可能想要在高级语言中使用有符号字节来节省一些空间。有时你会发现需要使用额外的指令来符号扩展或屏蔽数据,以模拟使用32位寄存器的8位操作。节省3个字节的代价是使用4个、8个或更多的字节。一个32位整数比一个有符号字节更便宜。
存在对齐问题,因为x86允许非对齐访问(例如从/到地址0x4进行32位读/写,并使用32位数据总线),这将耗费额外的周期。ARM和其他处理器不允许这种情况发生。但是反过来说,即使通过高速缓存进行字节写入,也会花费读-修改-写操作的时间,因为高速缓存很可能是32位宽度的RAM。要更改一个字节,您必须读取32位,修改8位,然后再写回32位,这将消耗时钟周期。如果您使用了32位变量而不是8位变量,即使该变量永远不会超出-5到+20的范围,您也会浪费更多的时钟周期。
至于您关于为什么具有8位寄存器的系统比32位系统需要更多周期来添加32位数字的问题,您已经知道答案,因为您很可能在小学就学会了用铅笔和纸张进行加法运算。
如果我想在只允许使用3位寄存器的世界中将十进制数123和789相加,我可以在单个周期内完成这个加法。
110 <
123
+789
====
912
将其翻译为中文:
把它看作是你的32位寄存器系统。现在对于8位寄存器系统,世界上只允许一次输入一个数字:
一个周期3 + 9,带有进位0
10
3
+ 9
====
2
"Carry out"是1,我们必须执行该操作以获取"carry out",以便在下一个操作中与下一组寄存器一起使用作为进位输入,在下一个周期中进行2 + 8计算,并带有我们的进位输入为1。
11
2
+ 8
====
1
"carry out"也是一种1 + 7的三周期,带有进位值为1。
01
1
+ 7
====
9
“如果需要的话,进位为0...”
“数字系统无关紧要(十进制、二进制等),它们都是一样的。”
现在,如果你想问的问题是例如,我有2和3,想把它们加在一起,使用一个64位处理器中的两个64位寄存器来执行只需要几个位列的操作似乎是一个巨大的浪费。同样,为了保存这些数据到/从内存中,等等。对于布尔运算或ALU运算(加法、或运算等),寄存器的宽度并不重要,因为它被设计为该宽度,在流水线平均一个时钟周期内完成。它执行的是64位加法,无论如何都会这么做。是的,这是很多浪费的逻辑资源。如上所述,您可以选择使用更少的逻辑但更多的时钟周期,或者更多的逻辑但更少的时钟周期。更多的时钟周期解决方案可能还涉及更多的内存周期,甚至会耗费更多的时钟周期。更宽的变量可能会浪费更多的RAM,但使用更宽的内存系统可以使用更少的时钟周期。这是一个权衡。现在,逻辑最高达数十亿赫兹,内存非常缓慢,但通过使总线更宽和其他并行技巧,您可以使其看起来更快。如果您节省逻辑和RAM上的钱,以换取时钟周期,您可能无法实时观看YouTube视频,或者以足够的像素查看字体和图像,甚至无法浏览网页,因为绘制这些经常使用数学函数压缩的字体和图像需要很长时间,用户无法忍受。
我建议您查看Microchip PIC指令集。
http://en.wikipedia.org/wiki/Microchip_PIC
在上述页面中列出了12位指令集表。想想你写过的最后一个程序,并使用该指令集实现该程序。更好的是,使用pic指令集添加三个简单数字。它只有一个寄存器,要进行数学运算,您必须获取一个操作数并将其放入w寄存器中,使用f寄存器进行数学运算,如果您不想弄乱f寄存器的内容,那么您就将结果留在w寄存器中。现在,添加另一个f寄存器,将结果添加到w中,然后添加第三个寄存器,将结果添加到w中,然后将w保存在第四个f寄存器中。d = a+b+c。当然,在arm、mips或其他处理器上,如果您只需要a、b、c进行一次操作,那么您必须进行三次读取,然后才能进行加法运算,但如果其中一个或多个操作数是其他操作的结果,并且不需要存储到ram中,因为我们有更多的寄存器等等,您开始看到规模经济性。6502(除非您了解零页)和其他指令集针对的是较少的逻辑、较少的ram,代价是时钟周期。对于旧设计(包括x86),这是因为逻辑制造和构建昂贵(相对而言),内存同样昂贵。
您可以通过牺牲时钟周期来极度简化处理器,甚至只需一条指令。
http://en.wikipedia.org/wiki/Single_instruction_set_computer
opencores.org有一些单指令或少数指令的处理器。您提到了Java,它的虚拟机以时钟周期为代价(以可移植性为回报)运行,如果您要将其构建成硬件,请参见opencores上的zpu,这是一个基于堆栈的处理器示例(不是Java,只是另一个基于堆栈的解决方案的示例)。当然,堆栈式解决方案并非由Java发明,Pascal最初是基于堆栈的伪代码,然后您在目标上实现该伪代码。small-c生成基于堆栈的程序,您为每个目标实现该程序等等。基于堆栈的解决方案非常便携,但以时钟周期为代价。
你的问题的简短回答是,不要过于关注微观层面,退后一步看整个画面。我们在处理器中使用越来越大的寄存器的原因是为了速度,大多数情况下,在64位处理器上进行ALU操作时,并没有使用所有这些地址位或数据位,这是非常真实的。但是对于那些需要使用更宽的寄存器、总线和内存的时候,这些更宽的寄存器、总线和内存会带来巨大的性能差异,足以弥补其他地方的浪费。而且这些浪费成本相当便宜,处理器、内存、磁盘空间的成本,如果需要提供相同的用户体验或接近的用户体验,8位多千兆赫系统的成本不会比64位多千兆赫系统便宜8倍。