Experimento grupal 4 Control de la predicción de la aventura y las ramas

Prefacio

Blog anterior: The Pipeline Adventure of Group Experiment 3 Mips Adventure

Tocado durante medio mes. . . Cuckoo está aquí para escribir el experimento de grupo de cálculo 4

Este experimento es muy simple, pero un poco difícil.

Es más probable que este experimento se haya calculado mal o se haya realizado debido a los resultados inesperados del simulador, por lo que hoy registraré y analizaré el experimento en detalle.

Nota: asegúrese de leer la parte [Método de cálculo del período de bloqueo] de este artículo, ¡esta parte es la conclusión! Es fácil entender el cálculo del período en el experimento leyéndolo.

Contenido del experimento

Siga los pasos experimentales y las instrucciones a continuación para completar las operaciones relevantes para registrar las capturas de pantalla del proceso experimental:

  1. Primero, proporcione un fragmento de código para la multiplicación de matrices, optimícelo activando la función BTB y observe los detalles de la canalización para explicar el papel de BTB en ella;
  2. En segundo lugar, diseñe un fragmento de código que lo invalide incluso si BTB está activado.
  3. En tercer lugar, utilizando el método de desenrollado de bucle, observe el fenómeno de que se reduce el número de veces que el flujo se detiene debido a las ramas y compare el número de veces que el flujo se detiene debido a las ramas cuando se utiliza la estructura BTB.

(Opcional: escribe el código de multiplicación de matrices en lenguaje C en el sistema x86, usa perf para observar el número de errores de predicción de rama y analiza si el número coincide con tu conocimiento. Luego, escribe el código utilizado en la segunda parte para que falle la predicción de rama. Verifique que x86 pueda predecir correctamente e intente explicar)

conocimiento de fondo

Cuando nos encontramos con una declaración de salto, a menudo necesitamos esperar hasta la etapa MEM para determinar si esta instrucción salta (a través de la optimización del hardware, el retardo de la rama se puede acortar en gran medida y la ejecución de la rama se puede avanzar a la etapa de ID, de modo que pueda be El costo de los errores de predicción de rama se reduce a una sola instrucción)

Esta demora para garantizar que las instrucciones correctas se obtengan previamente se denomina peligro de control (peligro de rama). Para reducir la pérdida de rendimiento causada por los peligros de control, generalmente se utilizan técnicas de predicción de ramificaciones .

Tecnología de predicción de ramas

La tecnología de predicción de rama incluye la predicción de rama estática en tiempo de compilación y la predicción de rama dinámica en tiempo de ejecución. Aquí, nos enfocamos en introducir la tecnología BTB (Branch Target Buffer) en la predicción dinámica de ramas.

BTB es el búfer de destino de la bifurcación. Coloca la instrucción de bifurcación (dirección de instrucción correspondiente) en un búfer y la guarda. Cuando se encuentre la misma instrucción (decisión de salto) la próxima vez, se ejecutará igual que la última vez. Salto (bifurcación o no rama) predicción.

Un diagrama esquemático de una estructura BTB factible es el siguiente:
Inserte la descripción de la imagen aquí
Después de que se adopta el BTB, las operaciones relevantes realizadas en cada etapa de la tubería son las siguientes:
Inserte la descripción de la imagen aquí
Tenga en cuenta que para completar o eliminar el BTB, se requieren 2 ciclos.

Esta frase es muy importante para nuestro cálculo posterior del número de ciclos de bloqueo del código. ! ! El establecimiento y error de la rama BTB tomará 2 ciclos.

Método de cálculo del período de bloqueo⭐

Creo que muchos zapatos para niños, como yo, están muy confundidos sobre la cantidad de ciclos bloqueados cuando se ejecuta el código de bucle por primera vez.

Inserte la descripción de la imagen aquí
Entonces, el número de ciclos de bloqueo que resumí se da directamente a continuación. Tome el marco de código de bucle de este experimento como ejemplo (es decir, beq se coloca al final del bucle para determinar si es necesario volver a la etiqueta del bucle)

No abra BTB

Sin iniciar BTB, mips predice que nunca saltará de forma predeterminada . En un bucle de n veces, habrá n-1 Branch Taken Stall, porque a excepción de la última vez para salir del bucle, se predijo correctamente. Otras veces, beq es siempre La predicción es incorrecta. Y cada puesto de rama tomada bloquea un ciclo

Entonces, el número de ciclos bloqueados = (n-1) * 1 = n-1 ciclos

Encienda BTB

Cuando se enciende BTB, en un ciclo de n veces, habrá 1 puestos tomados de rama y 1 puestos de predicción errónea de rama

Debido a que el BTB no se ha establecido por primera vez en el ciclo, los puestos de sucursales tomados ocurrieron una vez

Además, el beq al salir del bucle por última vez, porque previamente predijimos que el BTB saltaría, por lo que la última vez que hubo un error de predicción, el BTB debe borrarse y se produjo una pérdida de predicción errónea de rama.

Independientemente de las pérdidas de predicción errónea de rama y las pérdidas de predicción errónea de rama, se necesitan 2 ciclos para rectificar el BTB, por lo que el número final de ciclos bloqueados = 2

parte 1 multiplicación y optimización de matrices

En esta etapa, primero damos un ejemplo de multiplicación de matrices, y luego configuramos la canalización para que se ejecute directamente sin la función BTB (configurar-> habilitar búfer de destino de rama), observar los resultados y registrar;

Luego, encienda la función BTB y ejecútela nuevamente para observar los resultados experimentales. Compare los resultados de los dos experimentos para observar si BTB funciona. Si funciona, observe los detalles de la ejecución de la tubería y explique por qué BTB funciona.

El código para la multiplicación de matrices es el siguiente:

	.data
str:  .asciiz "the data of matrix 3:\n"
mx1:   .space 512
mx2:   .space 512
mx3:   .space 512

			.text
initial:     daddi r22,r0,mx1	#这个initial模块是给三个矩阵赋初值
             daddi r23,r0,mx2
			daddi r21,r0,mx3
input:		daddi r9,r0,64
			daddi r8,r0,0
loop1:		dsll r11,r8,3
			dadd r10,r11,r22
			dadd r11,r11,r23
			daddi r12,r0,2
			daddi r13,r0,3
			sd r12,0(r10)
			sd r13,0(r11)

			daddi r8,r8,1
			slt r10,r8,r9
			bne r10,r0,loop1

# i=r17, j=r18, k=r19

mul:			daddi r16,r0,8
			daddi r17,r0,0
loop2:		daddi r18,r0,0	#这个循环是执行for(int i = 0, i < 8; i++)的内容
loop3:		daddi r19,r0,0	#这个循环是执行for(int j = 0, j < 8; j++)的内容
			daddi r20,r0,0	#r20存储在计算result[i][j]过程中每个乘法结果的叠加值
loop4:		dsll r8,r17,6		#这个循环的执行计算每个result[i][j]
			dsll r9,r19,3
			dadd r8,r8,r9
			dadd r8,r8,r22
			ld r10,0(r8)		#取mx1[i][k]的值
			dsll r8,r19,6
			dsll r9,r18,3
			dadd r8,r8,r9
			dadd r8,r8,r23
			ld r11,0(r8)		#取mx2[k][j]的值
			dmul r13,r10,r11	#mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	#中间结果累加

			daddi r19,r19,1
			slt r8,r19,r16
			bne r8,r0,loop4

			dsll r8,r17,6
			dsll r9,r18,3
			dadd r8,r8,r9
			dadd r8,r8,r21	#计算result[i][j]的位置
			sd r20,0(r8)		#将结果存入result[i][j]中

			daddi r18,r18,1
			slt r8,r18,r16
			bne r8,r0,loop3

			daddi r17,r17,1
			slt r8,r17,r16
			bne r8,r0,loop2			

			halt

No configure la función BTB, ejecute el programa, observe los resultados de la ventana Estadísticas y grabe la captura de pantalla.

Inserte la descripción de la imagen aquí
A continuación, configure la función BTB (seleccione el elemento Configurar en la barra de menú y luego marque la opción Habilitar búfer de destino de rama en el menú desplegable). Y ejecute el programa aquí, observe los resultados en la ventana Estadísticas y tome una captura de pantalla para registrar.

Inserte la descripción de la imagen aquí
Luego, compare los resultados. Realizaremos un análisis detallado de las causas de esta situación en combinación con el oleoducto.

Nota: no olvide ejecutar un ciclo de 64 ciclos durante la inicialización

Análisis sin BTB

Cuando BTB no está activado, mips predice que nunca saltará de forma predeterminada, por lo que solo cuando finalice el último ciclo, no saltará a la etiqueta del ciclo, es decir, la predicción es correcta. Por tanto, hay 64-1 = 63 paradas tomadas de rama en la fase de inicialización.

Y 8*8*8los ciclos de cada uno, son el último paso en la rama La predicción es correcta, que no hay salto, luego un ciclo de 8 veces, hay siete ramales tomados en pérdida.

El bucle más interno se ejecuta 64 veces, el segundo bucle se ejecuta 8 veces y el bucle más externo se ejecuta una vez. Luego están: 64*7+8*7+1*7 = 511rama tomada paradas

Activar el análisis de BTB

Con BTB encendido, los puestos de venta de sucursales se han reducido a 148.

Entre los 64 ciclos de una sola capa durante la inicialización, se producen un total de pérdida de 1 rama tomada y pérdida de pronunciación incorrecta de 1 rama, es decir, el primer y último salto, por lo que cada uno ocupa 2 ciclos.

Los tres 8*8*8bucles, un total de 73 paradas de pronunciación incorrecta de rama y 73 paradas de rama tomadas, que en conjunto suman 146 ciclos. Entre ellos 73=64+8+1. analizar de la siguiente manera:

Cada vez que finaliza el bucle más interno, se pronunciará mal (porque la predicción anterior salta, pero la última no saltó) y eliminará el elemento correspondiente en el BTB.

Luego comienza el siguiente ciclo, no hay un elemento correspondiente en el BTB, por lo que habrá puestos de sucursales. El bucle más interno se ejecuta 64 veces, por lo que hay 64 paradas de pronunciación incorrecta de rama y 64 paradas de rama tomadas.

El ciclo de segundo nivel se ejecuta 8 veces y el ciclo más externo se ejecuta una vez, y lo mismo es cierto. Así que, al final, el número de pérdidas de pronunciación errónea de las ramas y las pérdidas de las ramas tomadas son ambas 64+8+1=73, y la cantidad de ciclos que representan es73*2=146

Así que al final hay 2+146=148ciclos de pérdida de pronunciación de rama y pérdida de rama tomada

código de diseño de la parte 2 para invalidar BTB

En esta parte, vamos a diseñar un fragmento de código que contiene un bucle. Según las características de BTB, el código que diseñamos hará que la apertura de BTB no sea capaz de optimizar el efecto, pero el rendimiento se verá muy reducido.

Consejo: asegúrese de utilizar las características de BTB, es decir, su juicio de salto se basa en el éxito o el fracaso del salto anterior. Se proporciona el código utilizado y la idea de diseño, y se proporciona una captura de pantalla del resultado en ejecución para demostrar que el código ha logrado el objetivo.

Solo necesitamos diseñar un bucle y luego tener un resultado de salto diferente cada vez. Por ejemplo, salte de acuerdo con si el contador de bucle i es par o impar, porque i es siempre impar y par alternativo:

.data

.text
    dadd r8, r0, r0
    daddi r8, r8, 10    # r8 = i = 10
    daddi r9, r0, 1     # r9 = 1

loop:
    beq r8, r0, end     # 跳出循环
    and r10, r8, r9     # r10 = r8 & 1 取最低位判断奇偶
    daddi r8, r8, -1    # i--

    beq r10, r0, loop   # 为偶数时跳转
    j loop              # 返回

end:
	halt

Tomando i = 10 como ejemplo, se activa el salto par. En este momento, el resultado registrado en el BTB es: "salto", y cuando i–, i = 9, el salto par falla y el BTB no es válido. Cuando i–, i = 8, el salto par se activa nuevamente, pero el BTB se ha despejado en este momento, por lo que la rama tomada se atasca nuevamente

Con el BTB apagado, vimos que se produjeron 11 paradas de rama en 10 ciclos (porque el último beq saltó también se contó como una vez, y cada ciclo j o beq predijo errores, un total de 10 errores)

Inserte la descripción de la imagen aquí

Inserte la descripción de la imagen aquí
Se puede ver que debido a la alternancia par-impar, siempre que nos encontramos con un número impar, las predicciones de beq están sesgadas, lo que lleva a 5 paradas de pronunciación incorrecta de rama y paradas de rama tomada, un total de 10 ciclos.

Además, debido a que antes del ciclo de entrada y salida, ramificamos paradas tomadas más de 2 veces, el resultado final es una 10+4=14rama de ciclos tomados paradas.

parte 3 desenrollado de bucle para optimizar la multiplicación de matrices

Primero, necesitamos tener una cierta comprensión del concepto de desenrollado de bucles.

¿Qué es el desenrollado de bucle? El llamado desenrollado de bucle tiene como objetivo reducir el impacto de la sobrecarga del bucle al realizar más operaciones de datos en cada iteración. La idea básica es intentar linealizar el objeto de operación y acceder a un pequeño grupo de datos lineales en una iteración en lugar de una sola. El programa resultante ejecutará menos iteraciones, por lo que la sobrecarga del ciclo se reduce efectivamente.

A continuación, desenrollaremos el programa de multiplicación de matrices anterior en un ciclo de acuerdo con esta idea. Es necesario expandir el código anterior mediante el desenrollado de bucles para desenrollar el bucle más interno que realiza 8 iteraciones, es decir, reducimos los tres bucles de multiplicación de matrices a dos bucles mediante el aumento del código.

Compare los resultados comparando el desenrollado de bucle (sin BTB habilitado), utilizando BTB (sin desenrollado de bucle) y sin BTB y sin desenrollado de bucle. Compare el número de sus puestos de sucursales de Tanken y sus puestos de predicción errónea de ramas y trate de emitir un juicio.

Debido a que el bucle más interno es el que consume más tiempo, dividimos las 8 acumulaciones del bucle más interno, es decir, escribimos manualmente 8 veces mx1[i][k]*mx2[k][j], enumeramos k para que sea 0 ~ 7:

	.data
str:  .asciiz "the data of matrix 3:\n"
mx1:   .space 512
mx2:   .space 512
mx3:   .space 512

			.text
initial:     daddi r22,r0,mx1	#这个initial模块是给三个矩阵赋初值
             daddi r23,r0,mx2
			daddi r21,r0,mx3
input:		daddi r9,r0,64
			daddi r8,r0,0
loop1:		dsll r11,r8,3
			dadd r10,r11,r22
			dadd r11,r11,r23
			daddi r12,r0,2
			daddi r13,r0,3
			sd r12,0(r10)
			sd r13,0(r11)

			daddi r8,r8,1
			slt r10,r8,r9
			bne r10,r0,loop1

# i=r17, j=r18, k=r19

mul:			daddi r16,r0,8
			daddi r17,r0,0
loop2:		daddi r18,r0,0	#这个循环是执行for(int i = 0, i < 8; i++)的内容
loop3:		daddi r19,r0,0	#这个循环是执行for(int j = 0, j < 8; j++)的内容
			daddi r20,r0,0	#r20存储在计算result[i][j]过程中每个乘法结果的叠加值

            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
            dsll r8,r17,6		# r8 = i*64
			dsll r9,r19,3       # r9 = k*8
			dadd r8,r8,r9
			dadd r8,r8,r22      # r8 = mx1[i][k]
			ld r10,0(r8)		# 取mx1[i][k]的值
			dsll r8,r19,6       # r8 = k*64
			dsll r9,r18,3       # r9 = j*8
			dadd r8,r8,r9       
			dadd r8,r8,r23      # r8 = mx2[k][j]
			ld r11,0(r8)		# 取mx2[k][j]的值
			dmul r13,r10,r11	# mx1[i][k]与mx2[k][j]相乘
			dadd r20,r20,r13	# 中间结果累加
			daddi r19,r19,1     # k++
            # ----------------------------------------------------#
			dsll r8,r17,6
			dsll r9,r18,3
			dadd r8,r8,r9
			dadd r8,r8,r21	#计算result[i][j]的位置
			sd r20,0(r8)		#将结果存入result[i][j]中

			daddi r18,r18,1
			slt r8,r18,r16
			bne r8,r0,loop3

			daddi r17,r17,1
			slt r8,r17,r16
			bne r8,r0,loop2			

			halt

Inserte la descripción de la imagen aquí
Debido a que dividimos el ciclo más interno, el análisis es el siguiente:

BTB no está encendido

Entonces, en ausencia de BTB abierto, inicializamos el ciclo 63 veces en paradas en rama, mientras que el ciclo exterior se ejecuta una vez, hay siete paradas en rama, el ciclo interior se ejecuta 8 veces, hay 8*7veces en paradas en rama, un total de 8*7+1*7+63=126veces la sucursal tomó puestos

Encienda BTB

Cuando se enciende BTB, el bucle de inicialización tiene 64 bucles de una sola capa, y se producen un total de 1 pérdida de derivación tomada y 1 pérdida de pronunciación incorrecta de rama, es decir, el primer y último salto, por lo que cada uno ocupa 2 ciclos.

Y debido a que el bucle está dividido, solo hay 8 bucles en las capas interna y externa. El bucle interno se ejecuta 8 veces, un total de 8 paradas tomadas de rama y paradas por mala pronunciación de la rama, el bucle exterior se ejecuta 1 vez, un total de 1 rama tomadas y 1 rama no pronunciadas, por lo que el número final es 9 veces, un total de 18 ciclos.

Inicialización integral (2) y bucle (18), un total de 20 períodos de pérdidas de sucursales tomadas y pérdidas de pronunciación incorrecta de sucursales

Observaciones finales

¡Los problemas de predicción de rama no ocurrirán sin usar declaraciones de rama! Pensamiento clásico de avestruz

El cálculo de la predicción de rama debe ser cuidadoso. Cuando no se usa BTB, debido a que mips siempre predice que no hay salto, un ciclo de n veces, se producen un total de n-1 ramales tomados

Cuando se enciende BTB, las paradas de derivación tomadas y las paradas de pronunciación incorrecta de la rama ocurren una vez cuando se ingresa al bucle por primera vez y las paradas de pronunciación incorrecta de la rama se producen cuando se sale del bucle por última vez. Sin embargo, la operación de cancelación de BTB requiere 2 ciclos, por lo que el resultado final requiere x2!

En conclusión

El cálculo de bloqueo de la predicción de rama debe ser cuidadoso, considerando el bloqueo causado por la instrucción de inicialización.

Después de encender el BTB, dado que las operaciones de deshacer y registrar toman 2 ciclos, el bloqueo final debe multiplicarse por 2, lo que puede causar fácilmente errores de cálculo. . .

Dividir el bucle es una buena estrategia para evitar la ramificación, ya que es muy fácil de usar en algunos escenarios de aplicación donde el caché es reducido.

Supongo que te gusta

Origin blog.csdn.net/weixin_44176696/article/details/111403939
Recomendado
Clasificación