¿Cómo resuelve la CPU el problema de la asunción de riesgos?

¡Acostúmbrate a escribir juntos! Este es el día 13 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

¿Qué riesgos debemos tomar para aumentar el rendimiento de la CPU a través del diseño de canalización?

Las tres grandes aventuras que el diseño de tuberías debe resolver:

  • Peligro estructural
  • Riesgo de datos
  • peligro de control

En el diseño de la tubería de la CPU, se encontrarán varios "peligros", por lo que la siguiente instrucción de la tubería no puede ejecutarse normalmente. Pero aún tenga la oportunidad de aumentar la tasa de rendimiento de instrucción a través de "preferencia" y "riesgo". Una CPU segmentada es una opción activa y arriesgada. Se espera que tomar riesgos genere mayores ganancias, por lo que esta no es una respuesta desesperada y, naturalmente, no es una crisis.

Para los problemas que pueden causar varias aventuras, en realidad están preparados con soluciones.

aventura estructural

En esencia, es un problema de competencia de recursos a nivel de hardware, es decir, un problema a nivel de circuito de hardware.

La CPU ejecuta dos etapas diferentes de instrucciones de computadora en el mismo ciclo de reloj al mismo tiempo. Pero estas dos etapas diferentes pueden usar el mismo circuito de hardware.

El ejemplo más típico es el acceso a datos de memoria.

  • En el mismo ciclo de reloj, dos instrucciones diferentes acceden al mismo recurso (diagrama esquemático de una tubería de 5 etapas)

Cuando la primera instrucción se ejecuta en el acceso a la memoria (MEM), la cuarta instrucción de la canalización realiza la operación de búsqueda de instrucciones (Fetch). Tanto el acceso a la memoria como la búsqueda de instrucciones son necesarios para leer los datos de la memoria. La memoria tiene solo un decodificador de dirección como entrada de dirección, por lo que solo se puede leer un dato en un ciclo de reloj, y los datos de memoria de lectura de la primera instrucción y el código de instrucción de lectura de la cuarta instrucción no se pueden ejecutar al mismo tiempo. tiempo.

El conflicto de recursos similar más común es la "tecla de bloqueo" del teclado de membrana. El teclado de membrana no tiene una línea independiente detrás de cada tecla, pero varias teclas comparten una línea. Si se presionan al mismo tiempo dos botones que comparten la misma línea, las señales de los dos botones no se pueden transmitir. Los usuarios intensivos de teclados deben comprar un teclado mecánico o un teclado capacitivo. Debido a que las teclas tienen líneas de transmisión independientes, "todas las teclas no tienen perforaciones", escriben muchos artículos, escriben programas o juegan juegos, pero no presionarán la tecla pero no tendrá efecto.

“全键无冲”本质就是增加资源。同样可用在CPU结构冒险。 对访问内存数据和取指令的冲突,把我们的内存分成两部分,各有各的地址译码器。这两部分分别是存放指令的程序内存和存放数据的数据内存。

这样把内存拆成两部分的解决方案,在计算机体系结构里叫作哈佛架构(Harvard Architecture)。 冯·诺依曼体系结构,又叫作普林斯顿架构(Princeton Architecture)。

如今的CPU仍是冯·诺依曼体系结构,并未将内存拆成程序内存、数据内存。 因为那样拆分,对程序指令和数据需要的内存空间,就无法根据实际应用去动态分配。虽然解决了资源冲突,但也失去灵活性。 现代CPU架构,借鉴了哈佛架构,在高速缓存层面拆分成指令缓存和数据缓存 不过,借鉴了哈佛结构的思路,现代的CPU虽然没有在内存层面进行对应的拆分,却在CPU内部的高速缓存部分进行了区分,把高速缓存分成了指令缓存(Instruction Cache)和数据缓存(Data Cache)两部分。

内存的访问速度远比CPU的速度要慢,所以现代的CPU并不会直接读取主内存。它会从主内存把指令和数据加载到高速缓存中,这样后续的访问都是访问高速缓存。而指令缓存和数据缓存的拆分,使得我们的CPU在进行数据访问和取指令的时候,不会再发生资源冲突的问题了。

结构冒险是一个硬件层面的问题,我们可以靠增加硬件资源的方式来解决。然而还有很多冒险问题,是程序逻辑层面的事儿。其中,最常见的就是数据冒险。

数据冒险:三种不同的依赖关系

同时在执行的多个指令之间,有数据依赖。

这些数据依赖,可分成三类:

  • 先写后读(Read After Write,RAW)
  • 先读后写(Write After Read,WAR)
  • 写后再写(Write After Write,WAW)

先写后读(Read After Write)

C语言代码编译出来的汇编指令。

int main() {
  int a = 1;
  int b = 2;
  a = a + 2;
  b = a + 3;
}
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
  int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
  int b = 2;
   b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
  a = a + 2;
  12:   83 45 fc 02             add    DWORD PTR [rbp-0x4],0x2
  b = a + 3;
  16:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  19:   83 c0 03                add    eax,0x3
  1c:   89 45 f8                mov    DWORD PTR [rbp-0x8],eax
}
  1f:   5d                      pop    rbp
  20:   c3                      ret  
复制代码
  • 内存地址为12的机器码,把0x2添加到 rbp-0x4 对应内存地址
  • 内存地址为16的机器码,又要从rbp-0x4内存地址,把数据写入eax寄存器。

所以,需要保证内存地址为16的指令读取rbp-0x4的值前,内存地址12的指令写入到rbp-0x4的操作必须完成。 这就是先写后读所面临的数据依赖。这顺序保证不了,程序就是错的!

这种先写后读的依赖关系称为数据依赖,Data Dependency。

先读后写(Write After Read)

这次我们先计算 a = b + a,然后再计算 b = a + b。

int main() {
  int a = 1;
  int b = 2;
  a = b + a;
  b = a + b;
}
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
   int b = 2;
   b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
   a = b + a;
  12:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  15:   01 45 fc                add    DWORD PTR [rbp-0x4],eax
   b = a + b;
  18:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  1b:   01 45 f8                add    DWORD PTR [rbp-0x8],eax
}
  1e:   5d                      pop    rbp
  1f:   c3                      ret       
复制代码

内存地址为15的汇编指令里,要把 eax 寄存器值读出,加到 rbp-0x4 的内存地址里。 在内存地址为18的汇编指令里,再写入更新 eax 寄存器里面。

如果在内存地址18的eax的写入先完成了,在内存地址为15的代码里面取出 eax 才发生,程序计算就错。这里,我们同样要保障对于eax的先读后写的操作顺序。

这个先读后写的依赖,一般被叫作反依赖,Anti-Dependency。

写后再写(Write After Write)

先设置变量 a = 1,再设置变量 a = 2。

int main() {
  int a = 1;
  a = 2;
}
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
  int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
  a = 2;
   b:   c7 45 fc 02 00 00 00    mov    DWORD PTR [rbp-0x4],0x2
}
复制代码

内存地址4所在的指令和内存地址b所在的指令,都是将对应的数据写入到 rbp-0x4 的内存地址里面。 如果内存地址b的指令在内存地址4的指令之后写入。那么这些指令完成之后,rbp-0x4 里的数据就是错误的。这就会导致后续需要使用这个内存地址里的数据指令,没有办法拿到正确的值。 所以,也需要保障内存地址4的指令的写入,在内存地址b的指令的写入之前完成。

这个写后再写的依赖,叫输出依赖,Output Dependency。

流水线停顿

除了读之后再进行读,对同一寄存器或内存地址的操作,都有明确强制顺序。而这个顺序操作的要求,也为使用流水线带来挑战。 因为流水线架构的核心,就是在前一个指令还没有结束时,后面的指令就要开始执行。

所以,需要有解决这些数据冒险的办法。 最简单也是最笨的就是流水线停顿(Pipeline Stall),或流水线冒泡(Pipeline Bubbling)。

Si se encuentra que las instrucciones ejecutadas más tarde tienen una dependencia a nivel de datos de las instrucciones ejecutadas anteriormente, "espere de nuevo". Cuando se decodifica la instrucción, se obtendrán el registro y la dirección de memoria a los que debe acceder la instrucción correspondiente, y luego se puede juzgar si la instrucción desencadenará un riesgo de datos. se activará, puede decidir detener toda la canalización durante uno o más ciclos. La señal del reloj cambiará automáticamente entre 0 y 1 continuamente. Por lo tanto, de hecho, no hay una pausa real, y cada paso de operación de la canalización debe hacer algo. Por lo tanto, en realidad no está deteniendo la tubería, sino insertando una operación NOP antes de ejecutar los pasos de la operación posterior, es decir, ejecutar una operación que solo se encarga de capturar peces. Esta instrucción insertada es como una burbuja de aire en una tubería de agua (Pipeline). Cuando el flujo de agua pasa, en realidad no envía el agua al siguiente paso, sino que genera una burbuja de aire sin nada, de ahí el nombre Pipeline Bubble.

Resumir

  • Los problemas de aventura estructural se pueden resolver agregando recursos.

La arquitectura de CPU moderna también es una solución de estructura híbrida basada en la arquitectura de Harvard bajo la arquitectura de von Neumann. Aunque la memoria no está dividida por función, está dividida en caché de instrucciones y caché de datos a nivel de caché.Desde el nivel de hardware, la competencia por los mismos recursos bajo el mismo reloj ya no ocurrirá.

  • Los problemas de riesgo, es decir, los atascos de tuberías, también se pueden resolver "esperando", es decir, insertando operaciones NOP.

Sin embargo, las soluciones como las paradas de canalización sacrifican el rendimiento de la CPU. Porque, de hecho, en el peor de los casos, nuestra CPU canalizada degenerará en una CPU de ciclo de instrucción única.

referencia

  • Capítulos 4.5 a 4.7 de Composición y diseño de computadoras: interfaces de hardware/software

Supongo que te gusta

Origin juejin.im/post/7086678891359633439
Recomendado
Clasificación