一个字节是8位,为什么有些单词会比一个字节大?

3
如果一个变量是布尔值(boolean)或者字符(char)(在C/C++中),或者字节(byte)(在像Java这样的一些语言中 - 等等,Java运行在虚拟机上),并且CPU使用的字长大于1字节,那么空间是否会被浪费?我听说当变量存储在内存中时,它可以更紧凑地存储,例如1个字节,即使一个字长为4个字节。有人能解释一下这是如何发生的吗?这似乎与事实相反,因为寄存器比RAM更受限制。

7
有点像在说所有纸张都应该只有一行。 - Hot Licks
我想我明白了。所以就好像最好让寄存器尽可能地大,因为即使数据只占用了一半的空间,寄存器也无法分成两个大小为一半的寄存器。 - Celeritas
实际上,一些设计已经将大寄存器分成两个较小的寄存器(或反之亦然),具体取决于使用哪些指令。多年来已经使用了各种技巧。 - Hot Licks
1
寄存器分割可以追溯到4004年,那是第一款真正的CPU。当然,那时它将一个“大”的8位寄存器分成两个4位寄存器。 - MSalters
那么我的观点依然成立。寄存器中应该只使用最少量的位,以便进行分隔。 - Celeritas
显示剩余4条评论
7个回答

8
尽管寄存器具备存储数字的能力,但它们并不完全等同于“空间”。 举个例子:你手里有一堆名片,想要按字母顺序排列。在此过程中,你会将盒子里的名片从一个地方移动到另一个地方。虽然在移动名片到新位置时你手里拿着一张名片,但是你的手并不像盒子那样提供储存名片的空间。手中的位置太珍贵了,不能称之为“空间”。 继续使用名片类比,假设名片的大小不同。你的手可以握住一张大名片、两张中等大小的名片或四张小名片。然而,在排序名片时,同时取多张名片很少对你有利,特别是当名片被均匀洗牌时:多张名片有多个目标位置,因此如果同时抓取多张名片,则需要进行更复杂或不必要的操作。 同样,CPU的寄存器可能包含多个字节,但如果需要执行计算,则通常无法告诉CPU使用哪些字节:寄存器作为不可分割的单元参与操作。这就是为什么整个字用于寄存器中的数据,即使只有一个字节也足够的原因。

6

一般来说,是指适合于单个寄存器的数据量。

如果将寄存器变窄,比如说8位,那么将需要多个周期才能将两个32位整数相加。

我认为您还希望RAM地址空间也适合于一个字。


1
我相信,“word”这个术语在典型的CPU寄存器为16位时得到了修正,现在“word”只是指16位。 - Gabe
@Gabe 当我学习计算机体系结构时,一个“字”用于表示32位。 - Sam I am says Reinstate Monica
为什么需要2个时钟周期?一个整数在一个寄存器中,另一个整数在另一个寄存器中。我意识到两个整数相加的整个操作需要比1个时钟周期更长时间,因为可能需要读取内存,但是一旦正确的值在寄存器中,唯一需要完成的就是相加,这只需要1个时钟周期,无论寄存器的大小如何,对吗? - Celeritas
@Celeritas 那是一些并行处理。 - Sam I am says Reinstate Monica
1
@Gabe和SamIam--“单词”是由架构师定义的。我见过12、16、32、36、48和64位的单词。 - Hot Licks
显示剩余8条评论

2
对于每个指令集,都有这些种类的定义。例如,一个字节是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 <--- carry bits/numbers
 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倍。

1

这与总线宽度有关。处理器的每个时钟周期都会将整个处理器向前移动一定的总线宽度,因此也许不可能针对每种操作数类型优化寄存器宽度。


0

你可以使用架构的存储字节指令(通常会将寄存器的低位存储在内存位置)将一个字节大小的值从一个字长寄存器存储到内存中。


0

很多早期计算机将数据存储在更小的增量中,一般是4位或6位数字。当数据路径狭窄且运算是串行进行时,这种方法相对较好。而且在存储方面也非常高效(当时存储资源很宝贵)。

现在存储资源已不再那么宝贵,通过扩大数据通道,计算机可以实现更好的吞吐量。


0

需要考虑的一件事:

现代(约1985)RISC处理器设计方案考虑到,在处理器芯片上放置相当数量的相对宽的寄存器相对便宜,而与“控制逻辑”的“芯片占用”成本相比则相对较低。

虽然有理由质疑这种论点的全部真实性,但它确实有一些真实的元素。虽然一个寄存器比同样数量的RAM昂贵多次(可能是1000或更多),但它仍然相对便宜。要是让寄存器变窄,并且需要更多周期来完成相同的操作则是虚假的经济手段。因此,设计师们试图在一个周期内完成尽可能多的工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接