CPU 如何从内存中检索多字节
how does CPU retrieve multibyte from memory
您好,我只是汇编编程的新手。我很困惑 CPU 如何从内存中检索多字节(例如 32 位机器的 32 位)。假设我们有一个整数 i 在内存中占用 4 个字节(起始地址为 0x100)
所以当我们使用IA32汇编编程时,我们只要这样写:
movl 8(%esp), %eax
其中 esp 是当前堆栈指针。 8 只是从堆栈指针地址到变量 i 的偏移量
所以当 ia32 指令执行时,cpu 只检索 0x100 处的字节,那么 0x101、0x102、0x103 处的其余字节呢? CPU 如何一次检索全部 32 位?
已编辑:新问题
我认为我对字长的理解是根本错误的。但我仍然很困惑,但是 32 位机器如何检索 8 字节 64 位的长整数,也许使用 movq
但是再次访问一个 256 字节的对象呢? CPU 只发出 4 次 movq 吗?
cpu如何预先知道需要发出多少次mov命令来检索大尺寸对象?
通常 CPUs 可以从内存中加载多个字节,因为它们被设计成这样做并且它们的 ISA 支持它。
例如,他们的寄存器、内部总线、缓存设计和内存子系统就是这样设计的。物理上,一个能够加载 64 位值的处理器 可能 在不同的地方有 64 条并行线来围绕 CPU 移动 64 位(8 字节)——但其他设计也是可能的,例如一次传输两个字节的较小的 16 位总线,或者甚至是一次传输一个位的位串行点对点连接。相同CPU的不同部分可能使用不同的设计和不同的物理宽度。例如,从 DRAM 读取 N 位可以实现为从 C 芯片并行读取 M 位,结果在内存控制器处合并,因此芯片需要支持比内核到内存路径的其他部分更小的并行度。
ISA 固有支持的宽度可能不同于硬件实现的自然宽度。例如,当英特尔添加 AVX ISA 扩展时,这是第一个支持 256 位(16 字节)加载和存储的扩展,底层硬件最初将其实现为一对 128 位操作。后来 CPU 架构 (Haswell) 最终将其实现为完整的 256 位宽度操作。即使在今天,成本较低的 x86 芯片也可能将大型 load/store 操作拆分为更小的单元。
归根结底,这些都是CPU的内部细节。您可以依赖的是记录的行为,例如可以原子加载的值的大小,或者对于记录它的 CPUs,加载类型值需要多长时间。它是如何实现的内部更像是一个电气engineering/CPU设计问题,有很多方法可以做到这一点。
how does 32 bits machine retrieve long integer which is 8 bytes 64 bit
如果您在整数寄存器中执行此操作,编译器必须使用多条指令,因为架构不提供一次加载两个 32 位寄存器的指令。所以 CPU 只看到两个单独的加载指令。
考虑这些函数,compiled by gcc7.3 -O3 -m32
for 32-bit x86,在堆栈上传递参数,并在 edx:eax
中返回 64 位整数(EDX 中的高半部分,EAX 中的低半部分)。即 i386 系统 V ABI。
int64_t foo(int64_t a) {
return a + 2;
}
movl 4(%esp), %eax
movl 8(%esp), %edx
addl , %eax
adcl [=10=], %edx # add-with-carry
ret
int64_t bar(int64_t a, int64_t b) {
return a + b;
}
movl 12(%esp), %eax # low half of b
addl 4(%esp), %eax # add low half of a
movl 16(%esp), %edx
adcl 8(%esp), %edx # carry-in from low-half add
ret
CPU 本身提供了程序员/编译器在处理大于寄存器的数据时可以使用的指令。 CPU只支持属于指令集的宽度,不支持任意宽度。这就是我们拥有软件的原因。
在 x86 上,编译器可以选择将 movq
用于 XMM 或 MMX 寄存器,并使用 paddq
,特别是如果这是可以存储 64 -bit 结果在内存中的某处,而不是在整数寄存器中需要它。但这只能达到您可以使用矢量寄存器执行的操作的限制,并且它们仅支持最大 64 位宽的元素。没有128位加法指令。
how does cpu know in advance that how many time it need to issue a mov command to retrieve the large size of object?
CPU 只需要按程序顺序执行每条指令恰好一次。 (或者在内部做任何它想做的事,给人一种这样做的 错觉 )。
x86 CPU 必须知道如何将任何可能的 x86 指令解码为正确的内部操作。如果 CPU 一次只能加载 128 位,它必须在内部将像 vmovups (%edi), %ymm0
这样的 256 位向量加载解码为多个加载操作(就像 AMD 所做的那样)。参见 David Kanter's write-up on the Bulldozer microarchitecture。
或者它可以将其解码为特殊的加载操作,在加载端口(如 Sandybridge)中需要两个周期,因此 256 位 loads/stores 不会花费额外的前端带宽,只会花费额外的时间在加载/存储端口。
或者如果它从 L1d 缓存到执行单元的内部数据路径足够宽(Haswell 及更高版本),它可以解码为单个简单的加载 uop,由缓存/加载端口内部处理,非常像 mov (%edi), %eax
,或者特别是 vmovd (%edi), %xmm0
(将 32 位零扩展加载到向量寄存器中)。
256 bytes 是 32 个 qwords;当前的 x86 CPUs 无法在一次操作中加载这么多。
256 bits 是 4 个 qwords,或一个 AVX ymm
寄存器。现代英特尔 CPUs(Haswell 及更高版本)具有那么宽的内部数据路径,并且确实可以一次将 256 位从缓存传输到矢量加载执行单元,将 vmovups ymm0, [rdi]
作为单个 uop 执行。 请参阅 How can cache be that fast? 以了解有关来自缓存的宽负载如何为 L1d 缓存提供极高吞吐量/带宽的更多详细信息。
您好,我只是汇编编程的新手。我很困惑 CPU 如何从内存中检索多字节(例如 32 位机器的 32 位)。假设我们有一个整数 i 在内存中占用 4 个字节(起始地址为 0x100) 所以当我们使用IA32汇编编程时,我们只要这样写:
movl 8(%esp), %eax
其中 esp 是当前堆栈指针。 8 只是从堆栈指针地址到变量 i 的偏移量 所以当 ia32 指令执行时,cpu 只检索 0x100 处的字节,那么 0x101、0x102、0x103 处的其余字节呢? CPU 如何一次检索全部 32 位?
已编辑:新问题 我认为我对字长的理解是根本错误的。但我仍然很困惑,但是 32 位机器如何检索 8 字节 64 位的长整数,也许使用 movq 但是再次访问一个 256 字节的对象呢? CPU 只发出 4 次 movq 吗? cpu如何预先知道需要发出多少次mov命令来检索大尺寸对象?
通常 CPUs 可以从内存中加载多个字节,因为它们被设计成这样做并且它们的 ISA 支持它。
例如,他们的寄存器、内部总线、缓存设计和内存子系统就是这样设计的。物理上,一个能够加载 64 位值的处理器 可能 在不同的地方有 64 条并行线来围绕 CPU 移动 64 位(8 字节)——但其他设计也是可能的,例如一次传输两个字节的较小的 16 位总线,或者甚至是一次传输一个位的位串行点对点连接。相同CPU的不同部分可能使用不同的设计和不同的物理宽度。例如,从 DRAM 读取 N 位可以实现为从 C 芯片并行读取 M 位,结果在内存控制器处合并,因此芯片需要支持比内核到内存路径的其他部分更小的并行度。
ISA 固有支持的宽度可能不同于硬件实现的自然宽度。例如,当英特尔添加 AVX ISA 扩展时,这是第一个支持 256 位(16 字节)加载和存储的扩展,底层硬件最初将其实现为一对 128 位操作。后来 CPU 架构 (Haswell) 最终将其实现为完整的 256 位宽度操作。即使在今天,成本较低的 x86 芯片也可能将大型 load/store 操作拆分为更小的单元。
归根结底,这些都是CPU的内部细节。您可以依赖的是记录的行为,例如可以原子加载的值的大小,或者对于记录它的 CPUs,加载类型值需要多长时间。它是如何实现的内部更像是一个电气engineering/CPU设计问题,有很多方法可以做到这一点。
how does 32 bits machine retrieve long integer which is 8 bytes 64 bit
如果您在整数寄存器中执行此操作,编译器必须使用多条指令,因为架构不提供一次加载两个 32 位寄存器的指令。所以 CPU 只看到两个单独的加载指令。
考虑这些函数,compiled by gcc7.3 -O3 -m32
for 32-bit x86,在堆栈上传递参数,并在 edx:eax
中返回 64 位整数(EDX 中的高半部分,EAX 中的低半部分)。即 i386 系统 V ABI。
int64_t foo(int64_t a) {
return a + 2;
}
movl 4(%esp), %eax
movl 8(%esp), %edx
addl , %eax
adcl [=10=], %edx # add-with-carry
ret
int64_t bar(int64_t a, int64_t b) {
return a + b;
}
movl 12(%esp), %eax # low half of b
addl 4(%esp), %eax # add low half of a
movl 16(%esp), %edx
adcl 8(%esp), %edx # carry-in from low-half add
ret
CPU 本身提供了程序员/编译器在处理大于寄存器的数据时可以使用的指令。 CPU只支持属于指令集的宽度,不支持任意宽度。这就是我们拥有软件的原因。
在 x86 上,编译器可以选择将 movq
用于 XMM 或 MMX 寄存器,并使用 paddq
,特别是如果这是可以存储 64 -bit 结果在内存中的某处,而不是在整数寄存器中需要它。但这只能达到您可以使用矢量寄存器执行的操作的限制,并且它们仅支持最大 64 位宽的元素。没有128位加法指令。
how does cpu know in advance that how many time it need to issue a mov command to retrieve the large size of object?
CPU 只需要按程序顺序执行每条指令恰好一次。 (或者在内部做任何它想做的事,给人一种这样做的 错觉 )。
x86 CPU 必须知道如何将任何可能的 x86 指令解码为正确的内部操作。如果 CPU 一次只能加载 128 位,它必须在内部将像 vmovups (%edi), %ymm0
这样的 256 位向量加载解码为多个加载操作(就像 AMD 所做的那样)。参见 David Kanter's write-up on the Bulldozer microarchitecture。
或者它可以将其解码为特殊的加载操作,在加载端口(如 Sandybridge)中需要两个周期,因此 256 位 loads/stores 不会花费额外的前端带宽,只会花费额外的时间在加载/存储端口。
或者如果它从 L1d 缓存到执行单元的内部数据路径足够宽(Haswell 及更高版本),它可以解码为单个简单的加载 uop,由缓存/加载端口内部处理,非常像 mov (%edi), %eax
,或者特别是 vmovd (%edi), %xmm0
(将 32 位零扩展加载到向量寄存器中)。
256 bytes 是 32 个 qwords;当前的 x86 CPUs 无法在一次操作中加载这么多。
256 bits 是 4 个 qwords,或一个 AVX ymm
寄存器。现代英特尔 CPUs(Haswell 及更高版本)具有那么宽的内部数据路径,并且确实可以一次将 256 位从缓存传输到矢量加载执行单元,将 vmovups ymm0, [rdi]
作为单个 uop 执行。 请参阅 How can cache be that fast? 以了解有关来自缓存的宽负载如何为 L1d 缓存提供极高吞吐量/带宽的更多详细信息。