Sistemas Computacionales - Universidad de Concepción

Anuncio
Sistemas
Computacionales
Ejercicios resueltos y planteados
Mario Medina C.
Depto. Ing. Eléctrica
Facultad de Ingeniería
Universidad de Concepción
2014
ii
Prefacio
Esta es una colección de ejercicios de sistemas computacionales que
espero sea de utilidad a aquellos alumnos empeñados en desarrollar las
habilidades y competencias asociadas a esta materia. Muchos de ellos
aparecen en los textos enumerados en la bibliografía de este documento;
otros han sido creados por el autor para ser usados en tareas y exámenes.
Es mi opinión que la única forma de aprender es haciendo. Se espera
que los ejercicios planteados sean desarrollados por Uds., los alumnos.
Por ello, en la mayoría de éstos, sólo se indica la solución final.
Estoy siempre dispuesto a responder consultas sobre estos ejercicios, ya sea via correo electrónico o en persona. Asimismo, rogaría me
hicieran llegar cualquier corrección o comentario a los ejercicios de este
libro.
Asi que, buena suerte, y provecho!
Mario Medina C.
mariomedina@udec.cl
Índice general
1 Representación digital de información
1
2 Ensamblador Intel x86 y lenguaje C
11
3 Procesador Y86
35
4 Pipelining
45
5 Optimización de código
54
6 Sistemas de memoria
55
7 Planificación de procesos
67
iii
Capítulo
Representación digital de
información
1.1 Suponga que un compilador C para un procesador de 32 bits representa los números negativos utilizando complemento a 2, y que utiliza desplazamientos aritméticos para los desplazamientos a la derecha. Suponga además que x e y son números enteros con signo arbitrarios, y que ux y uy son números enteros sin signo arbitrarios, tal que
ux = (unsigned) x y que uy = (unsigned) y.
Entonces, para cada una de las siguientes expresiones en lenguaje C,
indique si la expresión siempre es verdadera. En caso contrario, indique
posibles valores de x y/o y que la hagan falsa.
a) ((x + y) << 4) + y - x == 17*y + 15*x
b) ~x + ~y == ~(x + y)
c) (int) (ux - uy) == -(y - x)
d) ((x >> 1) << 1) <= x
Solución
a) Verdadera. 17*y equivale a y << 4 + y, y 15*x equivale a x << 4 − x.
b) Falsa. Contraejemplo: ∼ x+ ∼ y = (−x − 1) + (−y − 1) = −(x + y) − 2 =∼
(x + y) + 1.
c) Verdadera
d) Verdadera
1
1
Capítulo 1: Representación digital de información
2
1.2 Suponga que se dispone de un procesador de 32 bits, que usa aritmética de complemento a 2 para la representación de números con signo.
Se utilizan desplazamientos lógicos para los enteros sin signo, y desplazamientos aritméticos para los enteros con signo. Suponga además que
se sabe que x e y son enteros con signo, y que ux y uy son enteros sin
signo. Entonces, para cada una de las siguientes expresiones, indique si
son verdaderas para todos los valores posibles de x e y. En caso que sean
falsas, indique los valores de x y/o y para los cuales las expresiones no
se cumplen.
a) (x >= 0) || ((2*x) < 0)
b) (x & 7) != 7 || (x << 30 < 0)
c) (x * x) >= 0
d) x < 0 || -x <= 0
e) x > 0 || -x >= 0
f ) x*y == ux*uy
g) ~x*y +ux*uy == -y
Solución
a) Falsa. Sea x = −232 . Entonces, 2*x = 0.
b) Verdadera. Si (x & 7) != 7 es falsa, entonces el bit x2 = 1. Al
desplazar x 30 bits a la izquierda, el bit x2 pasa a ser el bit de
signo.
c) Falsa. Si x = 0xFFFF, lo que equivale a 65535, entonces se tiene
que x*x = 0xFFFE0001, es decir, −131071.
d) Verdadera.
e) Falsa. Sea x = −232 . Entonces, x y -x son negativas.
f ) Verdadera.
g) Verdadera.
1.3 Suponga que las variables x, f y d son de tipos int, float y double,
respectivamente. Suponga además que tanto f como d son diferentes
de ∞, −∞ y N aN . Para cada una de las siguientes expresiones, indique
si son verdaderas para todos los valores posibles de las variables involucradas. En caso que sean falsas, indique los valores de dichas variables
para los cuales las expresiones no se cumplen.
3
Capítulo 1: Representación digital de información
a) x == (int)(float) x
b) x == (int)(double) x
c) f == (float)(double) f
d) d == (float) d
e) f == -(-f)
f ) 2/3 == 2/3.0
g) (d >= 0.0) || ((d*2) < 0.0)
h) (d + f) - d == f
Solución
a) Falso. Contraejemplo: x = 232 .
b) Verdadero.
c) Verdadero.
d) Falso. Contraejemplo: x = 1040 .
e) Verdadero.
f ) Falso.
g) Verdadero.
h) Falso. Contrajemplo: d = +∞ y f = 1.
1.4 Sea un procesador de 32 bits que utiliza complemento a 2. Entonces,
para las expresiones siguientes, rellene la siguiente tabla con el tipo del
resultado (signed ó unsigned) y si la expresión es F ó V .
Expresión
−2147483647 − 1
−2147483647 − 1
−2147483647 − 1U
−2147483647 − 1
−2147483647 − 1U
==
<
<
<
<
Tipo
2147483648U
2147483647
2147483647
−2147483647
−2147483647
Solución
La siguiente tabla muestra la solución.
FóV
4
Capítulo 1: Representación digital de información
Expresión
−2147483647 − 1
−2147483647 − 1
−2147483647 − 1U
−2147483647 − 1
−2147483647 − 1U
==
<
<
<
<
2147483648U
2147483647
2147483647
−2147483647
−2147483647
Tipo
FóV
unsigned
signed
unsigned
signed
unsigned
true
true
false
true
true
1.5 En una máquina de 32 bits, sean x, y y z enteros de valor arbitrario.
Entonces, dado el siguiente código,
unsigned ux
double dx =
double dy =
double dz =
= (unsigned int) x;
(double) x;
(double) y;
(double) z;
indique cuáles de las siguientes expresiones siempre son verdaderas. En
caso contrario, debe indicar un contraejemplo para recibir el puntaje
asociado.
a) (x >= 0) || (x < ux)
b) dx + dy == (double) (y + x)
c) dx + dy + dz == dz + dy + dx
d) dx*dy*dz == dz*dy*dx
Solución
a) Verdadero
b) Falso. Contraejemplo: x, y = 231 − 1.
c) Falso. Contraejemplo: x = 3.14, y = DBL_MAX y z = −DBL_MAX.
√
√
d) Falso. Contraejemplo: x = 0.01, y = DBL_MAX y z = DBL_MIN.
donde DBL_MIN y DBL_MAX son los valores mínimos y máximos representables en un número de punto flotante de doble precisión.
1.6 Considere la siguiente representación de punto flotante de 16 bits basada en el estándar IEEE-754:
El bit más significativo es el bit de signo
Los 7 bits siguientes son el exponente, que se almacena como e+63
5
Capítulo 1: Representación digital de información
Los últimos 8 bits son la mantisa, que se codifica con la técnica del
1 implícito.
Siguiendo reglas de representación similares a IEEE-754, indique las
representaciones hexadecimales de los siguientes números:
a) −0
b) Menor número positivo mayor que 1
c) Mayor número positivo denormalizado
d) −∞
e) NotANumber
Solución
La siguiente tabla muestra la solución del problema.
Número
−0
Menor número positivo mayor que 1
Mayor número positivo denormalizado
−∞
NotANumber
Signo
Mantisa
Exponente
1
0
0
1
0
0x00
0x01
0xFF
0x00
0x01
0x00
0x01
0x00
0x7F
0x7F
1.7 Considere la siguiente representación de punto flotante de 8 bits basada en el estándar IEEE-754:
El bit más significativo es el bit de signo
Los 3 bits siguientes son el exponente E, que se almacena como
e+3
Los últimos 4 bits son la mantisa M.
El valor del número se codifica como V = (−1)s × 1.M × 2E .
Siguiendo reglas de representación similares a IEEE-754, indique las
representaciones binarias de los siguientes números:
a) −0
b) El mayor número positivo normalizado
c) El menor número negativo denormalizado
6
Capítulo 1: Representación digital de información
d) ∞
e) 1
f ) NotANumber
Además, indique qué valores son representados por las siguientes secuencias de bits:
a) 0 100 0101
b) 1 010 0110
c) 0 111 1110
d) 1 000 0000
Solución
Número
−0
Mayor número positivo normalizado
Menor número negativo denormalizado
∞
1
NotANumber
Signo
Exponente
Mantisa
1
0
1
0
0
0
000
110
000
111
011
111
0000
1111
0001
0000
0000
0001
a) 0 100 0101 = 2.625
b) 1 010 0110 = 0.6875
c) 0 111 1110 = NotANumber
d) 1 000 1111 = −0.1171875
1.8 La función clasifica_float() recibe como argumento un número de
precisión simple de formato IEEE-754, y debe clasificarlo. Para ello,
complete el código siguiente.
void clasifica_float(float f)
{
// unsigned u tiene el mismo patron de bits que f
unsigned u = *(unsigned *) &f;
// Identifica las partes de f
int signo = u >> 31;
// Bit de signo
Capítulo 1: Representación digital de información
int exp = ________________________;
int frac = _______________________;
// Expresiones siguientes dependen de signo, exp y frac
if (___________________________)
printf("Cero positivo o negativo\n");
else if (___________________________)
printf("Numero denormalizado\n");
else if (___________________________)
printf("Infinito positivo o negativo\n");
else if (___________________________)
printf("Not A Number\n");
else if (___________________________)
printf("Mayor que -1.0 y menor que 1.0\n");
else if (___________________________)
printf("Menor o igual a -1.0\n");
else
printf("Mayor o igual a 1.0\n");
}
Solución
El siguiente código implementa la función pedida.
void clasifica_float(float f)
{
// Unsigned u tiene el mismo patron de bits que float f
unsigned u = *(unsigned *) &f;
//*
int
int
int
Identifica las partes de f
signo = u >> 31;
exp = (u >> 23) & 0xFF;
frac = u & 0x7FFFFF;
// Expresiones siguientes dependen de signo, exp y frac
if (exp == 0x00 && frac == 0x00)
printf("Cero positivo o negativo\n");
else if (exp == 0x00 && frac != 0x00)
printf("Numero denormalizado\n");
else if (exp == 0xFF && frac == 0x00)
printf("Infinito positivo o negativo\n");
else if (exp == 0xFF && frac != 0x00)
printf("Not A Number\n");
else if (exp < 0x7F)
printf("Mayor que -1.0 y menor que 1.0\n");
else if (exp >= 0x7F && signo == 0x01)
printf("Menor o igual a -1.0\n");
7
Capítulo 1: Representación digital de información
8
else
printf("Mayor o igual a 1.0\n");
}
1.9 Escriba una función en C llamada bool isLittleEndian(void) tal
que retorne true si es compilada y ejecutada en un procesador de arquitectura Little Endian, y que retorne false si es compilada y ejecutada en un procesador de arquitectura Big Endian.
Solución
El siguiente código muestra una posible solución.
bool isLittleEndian(void)
{
int x = 0x00000001;
char *p = &x;
return (*p == 1);
}
1.10 Suponga que existen dos formatos de representación de números de
punto flotante de 9 bits similares al estándar IEEE. Éstos son:
Formato A :
1 bit de signo s
5 bits de exponente e, el que se almacena como e + 15
3 bits de mantisa m
Formato B :
1 bit de signo s
4 bits de exponente e, el que se almacena como e + 7
4 bits de mantisa m
a) Para ambos formatos, indique el patrón de bits correspondiente a
1)
2)
3)
4)
5)
el mayor número positivo normalizado
el menor número positivo normalizado
el menor número positivo denormalizado
uno
infinito positivo
b) La siguiente tabla muestra 5 patrones de bit en formato A. Para
cada uno, indique:
9
Capítulo 1: Representación digital de información
1) Su valor, ya sea como entero, potencia o expresado como fracción
2) El patrón equivalente en formato B
3) El valor del patrón en formato B
Es posible que en algún caso, Ud. no pueda representar exactamente el valor pedido en formato B. En ese caso, aproxime siempre
su resultado hacia +∞.
Formato A
Bits
Valor
s
e m
1 01111 001
0 10110 011
1 00111 010
0 00000 111
1 11100 000
0 10111 100
Formato B
Bits
Valor
s
e
m
Solución
a) La siguiente tabla muestra los patrones de bits solicitados y los
valores asociados.
Mayor número pos. normalizado
Menor número pos. normalizado
Menor número pos. denormalizado
Uno
Infinito positivo
Mayor número pos. normalizado
Menor número pos. normalizado
Menor número pos. denormalizado
Uno
Infinito positivo
Formato A
Valor
0 11110 111
0 00001 000
0 00000 001
0 01111 000
0 11111 000
(2 − 2−3 )215
2−14
2−17
+1.0
+∞
Formato B
Valor
0 1110 1111
0 0001 0000
0 0000 0001
0 0111 0000
0 1111 0000
(2 − 2−4 )27
2−6
2−10
+1.0
+∞
10
Capítulo 1: Representación digital de información
b) La siguiente tabla muestra los valores y patrones de bits solicitados. En la tercera línea, es necesario convertir un número normalizado en formato A a un número denormalizado en formato
B. Asimismo, las líneas 4 y 5 requieren aproximar el resultado en
formato B hacia +∞.
Formato A
Bits
Valor
1 01111 001
0 10110 011
1 00111 010
0 00000 111
1 11100 000
0 10111 100
− 98
176
5
− 1024
7 × 2−17
-8192
384
Formato B
Bits
Valor
1 0111 0010
0 1110 0110
1 0000 0101
0 0000 0001
1 1110 1111
0 1111 0000
− 98
176
5
− 1024
2−10
−248
+∞
Capítulo
Ensamblador Intel x86 y
lenguaje C
2.1 La función void decode1(int *xp, int *yp, int *zp) es compilada al siguiente código ensamblador.
movl
movl
movl
movl
movl
movl
movl
movl
movl
8(%ebp), %edi
12(%ebp), %ebx
16(%ebp), %esi
(%edi), %eax
(%ebx), %edx
(%esi), %ecx
%eax, (%ebx)
%edx, (%esi)
%ecx, (%edi)
Los parámetros xp, yp y zp están almacenados a 8, 12 y 16 bytes de la
dirección de memoria apuntada por %ebp. Escriba el código C equivalente al código ensamblador mostrado.
Solución
El siguiente código muestra una posible solución.
void decode1(int *xp, int *yp, int *zp)
{
int tx = *xp;
int ty = *yp;
int tz = *zp;
11
2
Capítulo 2: Ensamblador Intel x86 y lenguaje C
12
*yp = tx;
*zp = ty;
*xp = tz;
}
2.2 El lenguaje ensamblador Intel x86 posee la instrucción leal s, d, que
almacena en la dirección destino d la dirección efectiva calculada para
la dirección fuente s. Esta instrucción puede ser utilizada para realizar
cálculos de la forma a << k + b, donde k puede tomar valores 1, 2 o 3.
Indique todos los posibles múltiplos del registro %eax que pueden almacenarse en el registro %edx utilizando esta instrucción, mostrando el
formato de la instrucción leal s, d para cada uno.
Solución
Los múltiplos del registro %eax que se pueden calcular con la instrucción leal s, d se muestran en la siguiente tabla.
Instrucción
leal
leal
leal
leal
leal
leal
leal
(%eax), %edx
(%eax, %eax), %edx
(%eax, %eax, 2), %edx
(,%eax, 4), %edx
(%eax, %eax, 4), %edx
(,%eax, 8), %edx
(%eax, %eax, 8), %edx
Acción
%edx ← %eax
%edx ← 2%eax
%edx ← 3%eax
%edx ← 4%eax
%edx ← 5%eax
%edx ← 8%eax
%edx ← 9%eax
2.3 Sea el siguiente código ensamblador X86:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %edx
movl 12(%ebp), %eax
movl %ebp, %esp
movl (%edx), %edx
addl %edx, (%eax)
movl %edx, %eax
popl %ebp
ret
Rellene el siguiente fragmento de código C para que sea equivalente al
código ensamblador anterior.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
13
int fun(int *ap, int *bp) {
______ = ______;
______ = ______;
return ______;
}
Solución
El siguiente código C es equivalente al código ensamblador X86 mostrado.
int fun(int *ap, int *bp)
{
int temp = *ap;
*bp += *ap;
return temp;
}
2.4 El siguiente código C
int dw_loop(int x, int y, int n)
{
do {
x += n;
y *= n;
n--;
} while ((n > 0) & (y < n));
return x;
}
es traducido al siguiente código ensamblador, donde las variables x, y
y n están a 8, 12 y 16 bytes de (%ebp), respectivamente.
movl 8(%ebp), %esi
movl 12(%ebp), %ebx
movl 16(%ebp), %ecx
.L6: imull %ecx, %ebx
addl %ecx, %esi
decl %ecx
testl %ecx, %ecx
setg %al
cmpl %ecx, %ebx
setl %dl
andl %edx, %eax
Capítulo 2: Ensamblador Intel x86 y lenguaje C
14
testb $1, %al
jne .L6
a) Genere una tabla de uso de registros
b) Comente el código ensamblador
c) Identifique la condición y el cuerpo del ciclo do-while() y las líneas correspondientes del código ensamblador.
Solución
a) La tabla de registros es
Registro
Variable
Valor Inicial
%esi
%ebx
%ecx
x
y
n
x
y
n
b) El código ensamblador comentado es
movl 8(%ebp), %esi
movl 12(%ebp), %ebx
movl 16(%ebp), %ecx
.L6: imull %ecx, %ebx
addl %ecx, %esi
decl %ecx
testl %ecx, %ecx
setg %al
cmpl %ecx, %ebx
setl %dl
andl %edx, %eax
testb $1, %al
jne .L6
//
//
//
//
//
//
//
//
//
//
//
//
//
Almacena x en %esi
Almacena y en %ebx
Almacena n en %ecx
y *= n
x += n
n-Test n
n > 0
Compara y y n
y < n
(n > 0) & (y < n)
Test LSB
If != 0, ir a lazo
El compilador reconoce que las condiciones (n > 0) y (y < n) se
evalúan sólo a 0 o 1, y que entonces sólo es necesario examinar
el bit menos significativo del resultado para saber el valor de la
condición compuesta.
c) El cuerpo del ciclo está en las líneas 4 a 6 del código C y en las
líneas 6 a 8 del código ensamblador. La condición a evaluar está
en la línea 7 del código C y en las líneas 9 a 14 del código ensamblador.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
15
2.5 En el siguiente código C, se han omitido los operadores de comparación
y los tipos de datos de los cast.
char ctest(int a, int b,
{
char t1 =
a
char t2 =
b
char t3 = (
) c
char t4 = (
) a
char t5 =
c
char t6 =
a
int c)
__
__ (
__ (
__ (
__
__
b;
) a;
) a;
) c;
b;
0;
return t1 + t2 + t3 + t4 + t5 + t6;
}
A partir de ese código, el compilador C genera el siguiente código ensamblador:
movl 8(%ebp), %ecx
movl 12(%ebp), %esi
cmpl %esi, %ecx
setl %al
cmpl %ecx, %esi
setb -1(%ebp)
cmpw %cx, 16(%ebp)
setge -2(%ebp)
movb %cl,%dl
cmpb 16(%ebp),%dl
setne %bl
cmpl %esi, 16(%ebp)
setg -3(%ebp)
testl %ecx, %ecx
setg %dl
addb -1(%ebp),%al
addb -2(%ebp),%al
addb %bl, %al
addb -3(%ebp),%al
addb %dl, %al
movsbl %al, %eax
Suponga que las variables a, b y c están a 8, 12 y 16 bytes de (%ebp),
respectivamente. Indique las partes faltantes del código C.
Solución
Un posible código C equivalente es:
Capítulo 2: Ensamblador Intel x86 y lenguaje C
char ctest(int a, int b, int c)
{
char t1 =
a <
char t2 =
b < (unsigned)
char t3 = (short) c >=
(short)
char t4 = (char) a !=
(char)
char t5 =
c >
char t6 =
a >
16
b;
a;
a;
c;
b;
0;
return t1 + t2 + t3 + t4 + t5 + t6;
}
2.6 Sea el siguiente programa en C:
unsigned int c, v;
for (c = 0; v; c++) {
v &= (v - 1);
}
a) Identifique qué hace este programa.
b) Escriba código en ensamblador Intel X86 de 32 bits equivalente.
Suponga que el registro %ecx contiene la variable c, y que el registro %ebx contiene la variable v. Su código debe retornar el resultado en el registro %eax.
Solución
a) El programa cuenta el número de bits en 1 del argumento v.
b) A continuación, se muestra un programa en ensamblador que realiza la misma función.
xorl %ecx, %ecx
cmpl $0, %ebx
je fin
lazo:
movl %ebx,
decl %eax
incl %ecx
andl %eax, %ebx
jnz lazo
movl %ecx, %eax
# Hace 0 el contador c
# Si v = 0, ir a fin
%eax
# Calcula v - 1
# Incrementa el contador
# Calcula v = v&(v - 1)
# Si no es 0, repetir
# Retorna c en %eax
Capítulo 2: Ensamblador Intel x86 y lenguaje C
17
2.7 Considere el siguiente código ensamblador:
lazo: pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
movl 12(%ebp), %edx
xorl %eax, %eax
cmpl %edx, %ecx
jle Fin
Otro: decl %ecx
incl %edx
incl %eax
cmpl %edx, %ecx
jg Otro
Fin:
incl %eax
leave
ret
Se sabe que el código anterior corresponde a un código C de la forma:
int lazo(int x, int y) {
int result;
for(________; ________; result++) {
________;
________;
}
________;
return result;
}
Reescriba el código C anterior, rellenando las líneas indicadas. Utilice
sólo las variables x, y y result. No utilice los nombres de los registros!
Solución
Para analizar el código ensamblador, comenzaremos por comentarlo:
lazo: pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
movl 12(%ebp), %edx
xorl %eax, %eax
cmpl %edx, %ecx
#
#
#
#
Lee x en %ecx
Lee y en %edx
result en %eax es igual a 0
Compara x con y (x - y)
Capítulo 2: Ensamblador Intel x86 y lenguaje C
jle Fin
# Salta si x es <= a y
Otro: decl %ecx
incl %edx
incl %eax
cmpl %edx, %ecx
jg Otro
#
#
#
#
#
Fin:
# Incrementa result
incl %eax
leave
ret
18
Decrementa x
Incrementa y
Incrementa result
Compara x con y (x - y)
Repite si x > y
Dado que la convención de paso de parámetros de C dice que éstos se
pasan a través de la pila de derecha a izquierda, es claro que x está en
la posición -8(%ebp) y que y está en la posición -12(%ebp). Entonces,
se puede ver que el código C equivalente es:
int lazo(int x, int y) {
int result;
for(result = 0; x > y; result++) {
x--;
y++;
}
result++;
return result;
}
2.8 Considere el siguiente código C, donde M y N son constantes definidas
con #define en alguna parte del código.
int mat1[M][N];
int mat2[N][M];
int sumaElementos(int i, int j)
{
return mat1[i][j] + mat2[i][j];
}
Ese código C genera el siguiente código en lenguaje de ensamblador.
Suponga que el argumento i está en 8(%ebp), y que el argumento j
está en 12(%ebp). Suponga además que mat1 y mat2 corresponden a las
direcciones iniciales en memoria de las matrices correspondientes.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
sumaElementos:
19
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl 12(%ebp), %ecx
sall $2, %ecx
leal 0(, %eax, 8), %edx
subl %eax, %edx
leal (%eax, %eax, 4), %eax
movl mat2(%ecx, %eax, 4), %eax
addl mat1(%ecx, %edx, 4), %eax
leave
ret
Cuáles son los valores de M y N ?
Solución
A continuación, se muestra el código ensamblador comentado.
1
2
3
4
5
6
7
8
9
10
11
12
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl 12(%ebp), %ecx
sall $2, %ecx
leal 0(, %eax, 8), %edx
subl %eax, %edx
leal (%eax, %eax, 4), %eax
movl mat2(%ecx, %eax, 4), %eax
addl mat1(%ecx, %edx, 4), %eax
leave
ret
//
//
//
//
//
//
//
//
//
//
Almacena %ebp en la pila
Actualiza puntero base
%eax <- i
%ecx <- j
%ecx <- 4j
%edx <- 8i
%edx <- 8i - i = 7i
%eax <- 5*%eax = 5i
%eax <- M[mat2 + 4j + 20i]
%eax <- %eax*M[mat1 + 4j + 28i]
En las líneas 9 y 10, se accede a las posiciones mat1 + 28i + 4j y
mat2 + 20i + 4j. Dado que cada elemento de la matriz es un entero
de 32 bits, se deduce entonces que M = 5 y N = 7.
2.9 Considere el siguiente código C, donde M y N son constantes definidas
con #define en alguna parte del código.
int mat1[M][N];
int mat2[N][M];
int copiaElemento(int i, int j)
{
return mat1[i][j] = mat2[j][i];
}
Capítulo 2: Ensamblador Intel x86 y lenguaje C
20
Ese código C genera el siguiente código en lenguaje de ensamblador.
Recuerde que el lenguaje C almacena las matrices por filas. Suponga
que el argumento i está en 8( %ebp), y que el argumento j está en
12( %ebp). Suponga además que mat1 y mat2 corresponden a las direcciones iniciales en memoria de las matrices correspondientes.
copiaElemento:
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl 8(%ebp), %ecx
movl 12(%ebp), %ebx
leal (%ecx,%ecx,8), %edx
sall $2, %edx
movl %ebx, %eax
sall $4, %eax
subl %ebx, %eax
sall $2, %eax
movl mat2(%eax, %ecx, 4), %eax
movl %eax, mat1(%edx, %ebx, 4)
popl %ebx
leave
ret
Cuáles son los valores de M y N ?
Solución
A continuación, se muestra el código ensamblador X86 comentado.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pushl %ebp
pushl %ebx
movl 8(%ebp), %ecx
movl 12(%ebp), %ebx
leal (%ecx,%ecx,8), %edx
sall $2, %edx
movl %ebx, %eax
sall $4, %eax
subl %ebx, %eax
sall $2, %eax
movl mat2(%eax, %ecx, 4), %eax
movl %eax, mat1(%edx, %ebx, 4)
popl %ebx
leave
ret
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Forma marco de activacion
Almacena %ebx en la pila
%ecx <- i
%ebx <- j
%edx <- 9i
%edx <- 4*9i
%eax <- j
%eax <- 16*j
%eax <- 15*j
%eax <- 4*15j
%eax <- M[mat2 + 4*(15*j + i)]
M[mat1 + 4*(9*i + j)] <- %eax
Recupera %ebx de la pila
Elimina marco de activacion
Capítulo 2: Ensamblador Intel x86 y lenguaje C
21
De estudiar los códigos ensamblador y en lenguaje C, se ve que en
las líneas 11 y 12 se accede a las posiciones mat2 + 4(15*j + i) y
mat1 + 4*(9*i + j). Dado que cada elemento de la matriz es un entero de 32 bits, se tiene que M = 15 y N = 9.
2.10 El producto punto de vectores es una operación de reducción que opera
sobre dos vectores de la forma que se indica:
a • b = a1 b1 + a2 b2 + . . . + an−1 bn−1 + an bn
La función int productoPunto(int A[], int B[], int n) en lenguaje C retorna el producto punto de dos vectores A y B de largo n.
int productoPunto(int A[], int B[], int n)
{
int i;
int suma = 0;
for (i = 0; i < n; i++)
suma += A[i]*B[i];
return suma;
}
Escriba una función en lenguaje ensamblador equivalente.
Solución
Primero, debemos transformar el código C a una forma más apropiada
para su implementación en lenguaje ensamblador. Es decir, transformar el ciclo for a un ciclo while, y transformar los accesos a vector vía
índice a notación de punteros.
int productoPunto(int *A, int *B, int n)
{
int i = 0;
int suma = 0;
while (i < n)
{
suma = suma + *(A + 4*i) * *(B + 4*i);
i++;
}
return suma;
}
Luego, traducimos este código C a ensamblador, como se muestra a continuación.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
productoPunto:
pushl %ebp
movl %esp, %ebp
pushl %esi
pushl %edi
xorl %edx, %edx
xorl %eax, %eax
movl 8(%ebp), %esi
movl 12(%ebp), %edi
Lazo:
cmpl %edx, 16(%ebp)
je Fin
movl (%esi, %edx, 4), %ecx
imull (%edi, %edx, 4), %ecx
addl %ecx, %eax
incl %edx
jmp Lazo
Fin:
popl %edi
popl %esi
leave
ret
22
// Prepara la pila
//
//
//
//
//
//
Almacena registro %esi
Almacena registro %edi
i <- 0
suma <- 0
%esi <- A
%edi <- B
//
//
//
//
//
//
//
Compara n con i
Si son iguales, ir a Fin
%ecx <- *(A + 4*i)
%ecx <- *(B + 4*i)*%ecx
suma <- %ecx
i <- i + 1
Repetir
// Recupera registro %edi
// Recupera registro %esi
// Recupera valores de pila
2.11 El Máximo Común Divisor entre dos números puede calcularse recursivamente como se muestra a continuación:
int mcd(int m, int n)
{
if (n == 0)
return m;
if (m == 0)
return n;
mcd(n, m%n);
}
Escriba ahora una función equivalente en ensamblador.
Solución
Se muestra una posible solución. Como sólo hace uso del registro %eax
que es Caller Save y que no retiene valores importantes entre invocaciones recursivas, no es necesario salvarlo en la pila.
mcd:
pushl %ebp
movl %esp, %ebp
// Prepara la pila
Capítulo 2: Ensamblador Intel x86 y lenguaje C
cmpl $0, 12(%ebp)
jne SIGUE1
movl 8(%ebp), %eax
jmp FIN
SIGUE1:
cmpl $0, 8(%ebp)
jne SIGUE2
movl 12(%ebp), %eax
jmp FIN
SIGUE2:
movl 12(%ebp), %eax
ctld
idivl 8(%ebp)
pushl %edx
movl 12(%ebp), %eax
pushl %eax
call mcd
FIN:
leave
ret
23
// Compara n con 0
// Si n == 0, retornar m
// Compara m con 0
// Si m == 0, retornar n
// %eax <- n
// Extiende m a 64 bits
// Calcula m/n
// m%n se agrega a la pila
// %eax <- n
// Agrega n a la pila
2.12 Sea la matriz A de dimensiones 3 ∗ 3, como se muestra:


 a b c 


A =  d e f 


g h i
El determinante |A| de dicha matriz está dado por |A| = aei − af h − bdi +
bf g + cdh − ceg.
Escriba una función en ensamblador Intel x86 det3x3(int A[][3])
que reciba como argumento la dirección inicial de la matriz en 8(%ebp)
y retorne el determinante de dicha matriz en el registro %eax. Suponga
además que el rango de los elementos de A es tal que le basta con usar
la operación de multiplicación imull S, D.
Solución
Se muestra un posible código ensamblador para la función pedida.
det3x3:
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl 8(%ebp), %ebx
movl 16(%ebx), %ecx
imull 32(%ebx), %ecx
// Prepara la pila
//
//
//
//
Almacena registro %ebx en pila
Lee direccion de matriz A
Lee valor e en %ecx
Calcula ei
Capítulo 2: Ensamblador Intel x86 y lenguaje C
movl 20(%ebx), %edx
imull 28(%ebx), %edx
subl %edx, %ecx
imull (%ebx), %ecx
movl %ecx, %eax
//
//
//
//
//
Lee valor f en %edx
Calcula fh
Calcula ei - fh
Calcula a(ei - fh)
Acumula resultado en %eax
movl 20(%ebx), %ecx
imull 24(%ebx), %ecx
movl 12(%ebx), %edx
imull 32(%ebx), %edx
subl %edx, %ecx
imull 4(%ebx), %ecx
addl %ecx, %eax
//
//
//
//
//
//
//
Lee valor f en %ecx
Calcula fg
Lee valor d en %edx
Calcula di
Calcula fg - di
Calcula b(fg - di)
Acumula resultado en %eax
movl 12(%ebx), %ecx
imull 28(%ebx), %ecx
movl 16(%ebx), %edx
imull 24(%ebx), %edx
subl %edx, %ecx
imull 8(%ebx), %ecx
addl %ecx, %eax
//
//
//
//
//
//
//
Lee valor d en %ecx
Calcula dh
Lee valor e en %edx
Calcula eg
Calcula dh - eg
Calcula c(dh - eg)
Acumula resultado en %eax
popl %ebx
leave
ret
// Recupera valor de %ebx
24
2.13 Suponga que los 4 bytes de un entero int de 32 bits se numeran desde
el menos significativo (0) al más significativo (3).
a) Escriba una función en C unsigned int reemplazaByte(unsigned
int x, unsigned int i, unsigned char b) que reemplace el iésimo byte del entero x por el byte b dado. Por ejemplo, escribir reemplazaByte(0x12345678, 2, 0xAB) deberá retornar el entero 0x12AB5678, mientras que reemplazaByte(0x12345678, 0,
0xAB) deberá retornar el entero 0x123456AB.
b) Escriba ahora una función equivalente en lenguaje ensamblador
x86. Recuerde que la convención de paso de argumentos de C establece que éstos se almacenan en la pila de derecha a izquierda.
No olvide respetar las convenciones Caller-Save y Callee-Save.
Solución
a) El siguiente código C implementa la función pedida. Se utiliza una
máscara para anular los bits a reemplazar, y luego se suma el nuevo byte, desplazado i bytes a la izquierda.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
25
unsigned int
reemplazaBytes(unsigned int x, unsigned int i,
unsigned char b) {
unsigned int mascara = 0xFF << (i*8);
return (x & ~mascara) | (b << (i*8));
}
b) El siguiente código ensamblador implementa la función pedida.
Para acceder al byte a modificar, se utiliza la variable i como un
desplazamiento dentro de la copia del entero x que está en la pila.
Luego, esta versión modificada de x se lee al registro %eax para ser
usado como valor de retorno. Como los únicos registros utilizados
son %eax y %ecx, y éstos son Caller-Save, no es necesario almacenarlos en la pila.
pushl %ebp
movl %esp, %ebp
movb 16(%ebp), %al
movl 12(%ebp), %ecx
movb %al, 8(%ebp, %ecx)
movl 8(%ebp), %eax
leave
ret
# Crea marco de activacion
#
#
#
#
#
Lee byte b a en %al
Lee i
b -> i-esimo byte de x
Lee valor modificado
como valor de retorno
2.14 Se desea obtener la traspuesta de una matriz cuadrada M de dimensiones N × N . Para ello, Ud. debe realizar las siguientes tareas:
a) Primero, escriba una función void swap(int *a, int *b) en C
que intercambie los contenidos de dos punteros a enteros.
b) Luego, escriba esta función en lenguaje de ensamblador x86. Recuerde que la convención de paso de argumentos de C establece
que éstos se almacenan en la pila de derecha a izquierda. No olvide respetar las convenciones Caller-Save y Callee-Save.
c) Luego, escriba una función en C llamada void traspuesta(int
*M, int N) que recibe dos argumentos. El primero es un puntero
a un vector de N 2 enteros, utilizado para simular el acceso a una
matriz de N × N enteros, tal como se vió en clases. El segundo
argumento es la dimensión de la matriz. Su función debe utilizar
la función swap() escrita anteriormente.
d) Implemente ahora esta función en lenguaje ensamblador x86.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
26
Solución
a) El siguiente código C implementa la función deseada. No es la
implementación más eficiente, pero refleja la implementación en
lenguaje ensamblador que se muestra a continuación.
void swap(int *a, int *b) {
int aTemp = *a;
int bTemp = *b;
*a = bTemp;
*b = aTemp;
}
b) El siguiente código ensamblador implementa la función swap()}
del punto anterior. Utiliza el registro %ebx, el cual es Callee-Save.
Por ello, el valor de este registro se almacena en la pila al comienzo
de la función y se recupera al final.
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl 8(%ebp), %eax
movl 12(%ebp), %edx
movl (%eax), %ecx
movl (%edx), %ebx
movl %ecx, (%edx)
movl %ebx, (%eax)
popl %ebx
leave
ret
# Crea marco de activacion
#
#
#
#
#
#
#
#
Almacena registro %ebx
Lee a al registro %eax
Lee b al registro %edx
Lee *a al registro %ecx
Lee *b al registro %ebx
Escribe *a a puntero b
Escribe *b a puntero a
Recupera registro %ebx
c) A continuación, se muestra una implementación en C de la función traspuesta() solicitada.
void traspuesta(int *M, int N) {
int i, j;
for (i = 0; i < N; i++) {
for (j = 0; j < i; j++) {
swap(&M[i*N + j], &M[j*N + i]);
}
}
}
d) El siguiente código muestra una posible implementación en lenguaje ensamblador de esta función.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
pushl %ebp
movl %esp, %ebp
xorl %eax, %eax
lazo1: xorl %ecx, %ecx
lazo2: movl 12(%ebp), %edx
mull %ecx, %edx
addl %eax, %edx
sall $2, %edx
addl 8(%ebp), %edx
pushl %edx
movl 12(%ebp), %edx
mull %eax, %edx
addl %ecx, %edx
sall $2, %edx
addl 8(%ebp), %edx
pushl %edx
call swap
addl $8, %esp
incl %ecx
cmpl %ecx, %eax
jl lazo2
incl %eax
cmpl %eax, 12(%ebp)
jl lazo1
leave
ret
27
# Crea marco de activacion
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
Hace i igual a 0
Hace j igual a 0
Lee N
Multiplica j*N
Calcula j*N + i
Multiplica 4*(j*N + i)
Calcula M + 4*(j*N + i)
Empila M + 4*(j*N + i)
Lee N
Multiplica i*N
Calcula i*N + j
Multiplica 4*(i*N + j)
Calcula M + 4*i*N + j)
Empila M + 4*(i*N + j)
Invoca a funcion swap()
Recupera posicion
Incrementa j
Compara j con i (i - j)
Si j < i, ir a lazo2
Incrementa i
Compara i con N (N - i)
2.15 Escriba una función unsigned int unosConsecutivos(unsigned int
x) en lenguaje ensamblador que retorne el largo de la máxima secuencia de 1s consecutivos presentes en el argumento x. Es decir, si el argumento es 0xDBFF1F1C, la función debe retornar 10. No olvide respetar las convenciones Caller-Save y Callee-Save en su código. Sugerencia:
piense en cómo escribiría esta función en C antes de comenzar a programar en ensamblador.
Solución
El siguiente código muestra una posible implementación de la función
deseada en lenguaje de programación C.
unsigned int UnosConsecutivos(unsigned int x)
{
int maxContador = 0;
// Largo de maxima secuencia
int contador = 0;
// Contador de unos
while (x != 0) {
if ((signed int) x < 0) {
Capítulo 2: Ensamblador Intel x86 y lenguaje C
28
contador++;
if (maxContador < contador)
maxContador = contador;
}
else
contador = 0;
x = x << 1;
}
return maxContador;
}
A su vez, el siguiente código muestra una implementación de la función
anterior en lenguaje de ensamblador x86.
unosconsecutivos:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
xorl %eax, %eax
xorl %edx, %edx
#
#
#
#
#
Almacena puntero base en la pila
Redefine el puntero base
Lee el argumento x a %ecx
Hace 0 %eax (maxContador)
Hace 0 %edx (contador)
lazo:
cmpl $0, %ecx
# Compara x con 0
jz fin
# si x es cero, va a fin
jns pos
# Salta si x es positivo
incl %edx
# Si es negativo, contador++
cmpl %edx, %eax
# Compara maxContador y contador
jge shift
# Sigue si maxContador >= contador
movl %edx, %eax
# Actualiza valor de maxContador
jmp shift
shift: sall $1, %ecx
# Desplaza x un bit a la izquierda
jmp lazo
pos:
xorl %edx, %edx # Limpia el contador
jmp shift
# Si i es 0, ir al fin
fin:
movl %ebp, %esp # Recupera valores de %ebp y %esp
popl %ebp
ret
2.16 Sea la siguiente función switcher en lenguaje C, que utiliza un comando switch.
int switcher(int a, int b, int c)
{
int result;
switch(a) {
case ________________: /* Caso A */
Capítulo 2: Ensamblador Intel x86 y lenguaje C
c = ________________;
case ________________: /* Caso
result = ________________;
break;
case ________________: /* Caso
case ________________: /* Caso
result = ________________;
break;
case ________________: /* Caso
result = ________________;
break;
default:
result = ________________;
29
B */
C */
D */
E */
}
return result;
}
Esta función es traducida en lenguaje ensamblador como se muestra a
continuación:
# Codigo de funcion switcher()
# Suponga a en %ebp + 8, b en %ebp + 12, c en %ebp + 16
movl 8(%ebp), %eax
cmpl $7, %eax
ja .L2
jmp *.L7(, %eax, 4)
.L2:
movl 12(%ebp), %eax
jmp .L8
.L5:
movl $4, %eax
jmp .L8
.L6:
movl 12(%ebp), %eax
xorl $15, %eax
movl %eax, 16(%ebp)
.L3:
movl %16(%ebp), %eax
addl $112, %eax
jmp .L8
.L4:
movl 16(%ebp), %eax
addl 12(%ebp), %eax
sall $2, %eax
.L8:
# Tabla de saltos
.L7:
.long .L3
.long .L2
.long .L4
.long .L2
.long .L5
.long .L6
Capítulo 2: Ensamblador Intel x86 y lenguaje C
30
.long .L2
.long .L4
Complete entonces la función switcher mostrada.
Solución
El siguiente código implementa la función pedida.
int switcher(int a, int b, int c)
{
int result;
switch(a) {
case 5: /* Caso A */
c = b^15;
case 0: /* Caso B */
result = c + 112;
break;
case 2: /* Caso C */
case 7: /* Caso D */
result = 4*(b + c);
break;
case 4: /* Caso E */
result = 4;
break;
default:
result = b;
}
return result;
}
2.17 Uno de los problemas intrínsecos de la representación de enteros en un
número finito de bits es la ocurrencia de rebalses.
a) Escriba, entonces, una función en C llamada sePuedenSumar(int
x, int y) que retorne 1 si los enteros con signo de 32 bits x e y se
pueden sumar sin posibilidad de rebalse, y 0 en caso contrario.
b) Escriba ahora código en ensamblador Intel X86 de 32 bits equivalente. Suponga que el registro %ecx contiene la variable x, y que
el registro %ebx contiene la variable y. Su código debe retornar el
resultado 0 ó 1 en el registro %eax.
Solución
Para determinar si dos números enteros se pueden sumar sin que ocurra un error de representación, una técnica sencilla es realizar la suma
Capítulo 2: Ensamblador Intel x86 y lenguaje C
31
y ver el signo del resultado: si la suma de dos números positivos da un
resultado negativo, entonces ocurrió un rebalse. Asimismo, si la suma
de dos números negativos da un resultado positivo, entonces ocurrió
un rebalse. La suma de un número positivo y un número negativo no
puede dar un rebalse.
a) El siguiente código C implementa lo solicitado.
bool sePuedenSumar(int x, int y) {
int suma = x + y;
int rebalse_neg = x < 0 && y < 0 && suma >= 0;
int rebalse_pos = x >= 0 && y >= 0 && suma < 0;
return !rebalse_pos && rebalse_neg;
}
b) El siguiente código Intel x86 implementa lo solicitado.
movl $1, %eax
movl %ecx, %esi
movl %ebx, %edi
xorl %esi, %edi
js fin
addl %ebx, %esi
js sum_neg
testl %ecx, %ecx
js fin_no
jmp fin
sum_neg:testl %ecx, %ecx
jns fin_no
jmp fin
fin_no: xorl %eax, %eax
fin:
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
Hace 1 el registro de resultado %eax
Copia x en %esi
Copia y en %edi
Calcula x XOR y
Si < 0, x e y tienen distinto signo
Calcula suma = x + y
Salta si la suma es negativa
Si x < 0 e y < 0 y la suma
es positiva, no se pueden sumar
Si x < 0 e y < 0, no se pueden sumar
Si x >= 0 e y >= 0, y suma es negativa
entonces no se pueden sumar
En caso contrario, se pueden sumar
Hace 0 el registro resultado %eax
Fin del codigo solicitado
2.18 Escriba una función void invierteRistra(char *cp, unsigned int
n) en lenguaje ensamblador que reciba como argumentos un puntero
a una ristra de C y el número de caracteres de ésta, y que invierta esta
ristra in situ, es decir, en las mismas direcciones de memoria. Suponga que los argumentos a la función están almacenados en 8( %ebp) y
12( %ebp), respectivamente. No olvide respetar las convenciones CallerSave y Callee-Save en su código. Sugerencia: piense en cómo escribiría
esta función en C antes de comenzar a programar en ensamblador.
Solución
El siguiente código de ensamblador X86 muestra una posible solución.
Capítulo 2: Ensamblador Intel x86 y lenguaje C
32
.type invierteRistra, @function
invierteRistra:
pushl %ebp
# Almacena puntero base en la pila
movl %esp, %ebp
# Redefine el puntero base
movl 8(%ebp), %eax # Lee direccion de ristra en %eax
movl 12(%ebp), %edx # Lee el largo de la ristra en %edx
addl %eax, %edx
# Calcula posicion del ultimo
subl $1, %edx
# caracter de la ristra
lazo1:
cmpl %eax, %edx
# Compara puntero con el final
js finLazo1
# de la ristra y salta si es menor
movb (%eax), %ch
# Intercambia caracteres de ristra
movb (%edx), %cl
# utilizando registros intermedios
movb %ch, (%edx)
movb %cl, (%eax)
incl %eax
# Incrementa puntero a ristra en 1
decl %edx
# Decrementa fin de ristra en 1
jmp lazo1
finLazo1:
leave
ret
2.19 Complete el siguiente código C en base al código ensamblador generado.
int funcion(int x, int n)
{
int result = x;
switch(n) {
// Aqui falta el codigo que Ud. debe escribir
}
return result;
}
El código ensamblador Intel x86 generado se muestra a continuación,
donde la columna de la izquierda indica la dirección en memoria de
cada instrucción. Asimismo, la tabla de saltos está almacenada a partir
de la dirección de memoria 2000.
1000:
1001:
1003:
1006:
1009:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl 12(%ebp), %edx
subl $50, %edx
Capítulo 2: Ensamblador Intel x86 y lenguaje C
33
1012:
cmpl $5, %edx
1015:
ja 1040
1017:
jmp *2000(, %edx, 4)
1024:
shll $2, %eax
1027:
jmp 1043
1029:
sarl $2, %eax
1032:
jmp 1043
1034:
leal (%eax, %eax, 2), %eax
1037:
imull %eax, %eax
1040:
addl $10, %eax
1043:
popl %ebp
1044:
ret
. . . . . . . .
2000:
1024
2004:
1040
2008:
1024
2012:
1029
2016:
1034
2020:
1037
Solución
El siguiente código muestra una implementación que cumple con lo
solicitado.
int funcion(int x, int n)
{
int result = x;
switch(n) {
case 50:
case 52:
result <<= 2;
break;
case 53:
result >>= 2;
break;
case 54:
result *= 3;
case 55:
result *= result;
default:
result += 10;
}
return result;
}
Capítulo 2: Ensamblador Intel x86 y lenguaje C
34
2.20 La función 91 es una función recursiva creada por John McCarthy que
tiene la particularidad que se evalúa a 91 para todos los enteros n ≤ 100,
y es igual a n − 10 para n ≥ 101. Su implementación en C es como sigue:
int f91(int n) {
if (n > 100)
return n - 10;
else
return f91(f91(n + 11));
}
Escriba esta función recursiva en lenguaje ensamblador X86. Suponga,
como de costumbre, que esta función recibe un argumento entero en la
pila, y que retorna su valor en el registro %eax.
Solución
El siguiente código muestra una posible implementación.
.type f91, @function
f91:
pushl %ebp
#
movl %esp, %ebp
#
movl 8(%ebp), %eax #
cmpl $100, %eax
#
jg fin_f91
#
addl $11, %eax
#
pushl %eax
#
call f91
#
popl %ebx
#
pushl %eax
#
call f91
#
popl %ebx
#
jmp fin2
fin_f91:
subl $10, %eax
#
fin2:
movl %ebp, %esp
#
popl %ebp
ret
Almacena puntero base en la pila
Redefine el puntero base
Lee el argumento al registro %eax
Compara argumento con 100
Si es 1, terminar
En otro caso, incrementar %eax en 10
Almacena en la pila
Llama a f91
Extrae argumento de la pila
Almacena nuevo argumento en la pila
Llama a f91 de nuevo
Extrae argumento de la pila
Si n es > 100, retorna n - 10
Recupera valores de %ebp y %esp
Capítulo
Procesador Y86
3.1 El algoritmo recursivo de Dijkstra para calcular el máximo común divisor puede escribirse como:


m,
if m = n



mcd(m
−
n,
n),
if
(m > n)
mcd(m, n) = 


 mcd(m, n − m), en otro caso
Recuerde que el máximo común divisor entre dos números enteros positivos n y m es el mayor entero d que divide exactamente tanto n como
m, es decir, m mod d = n mod d = 0.
Escriba una función en int mcd(int m, int n) que implemente este algoritmo, y luego una versión en lenguaje ensamblador Y86. El siguiente código muestra un esqueleto que le puede servir como comienzo. Ejecute su código utilizando el simulador del procesador secuencial
para calcular mcd(18, 14), mostrando la salida del simulador secuencial estándar en modo texto.
# Ejecucion comienza en la direccion 0x00
.pos 0
init:
irmovl Pila, %esp
# define puntero a la pila
irmovl Pila, %ebp
# define puntero base
jmp Main
# ejecuta programa principal
Main:
irmovl $14, %eax
pushl %eax
irmovl $18, %eax
pushl %eax
#
#
#
#
%eax
n es
%eax
m es
35
vale 14
14
vale 18
18
3
36
Capítulo 3: Procesador Y86
call mcd
halt
# llama a mcd(18, 14)
# int mcd(int n, int m)
# INSERTE SU CODIGO AQUI
.pos 0x100
Pila:
# pila del programa comienza aqui
3.2 Sea la función recursiva SumaRec(int *dato, int cuenta), que suma cuenta enteros a partir de la posición dada dato, cuyo código C se
muestra a continuación. Escriba esta función en lenguaje ensamblador
Y86, y luego utilice esta función para calcular la suma de un vector de
8 elementos. Ejecute su código utilizando el simulador del procesador
secuencial.
int SumaRec(int *dato, int cuenta) {
if (cuenta == 1) {
return *dato;
}
return *dato + SumaRec(dato++, cuenta--);
}
El siguiente código muestra un esqueleto que le puede servir como comienzo.
# Ejecucion comienza en la direccion 0x00
.pos 0
init:
irmovl Pila, %esp
# define puntero a la pila
irmovl Pila, %ebp
# define puntero base
jmp Main
# ejecuta programa principal
# vector de elementos
.align 4
vector: .long 0x56ad
.long 0x1786
.long 0x3018
.long 0x0F41
.long 0x000c
.long 0x2918
.long 0x1004
.long 0x0F0F
Main:
irmovl $8, %eax
# %eax vale 8
37
Capítulo 3: Procesador Y86
pushl %eax
irmovl vector, %edx
pushl %edx
call SumaRec
halt
#
#
#
#
cuenta es 8
lee direccion de vector
dato es dir. vector
llama a SumaRec(vector, 8)
# int SumaRec(int *dato, int cuenta)
# INSERTE SU CODIGO AQUI
.pos 0x150
Pila:
# pila del programa comienza aqui
3.3 En los programas de ejemplo del procesador X86, se presenta la instrucción leave, y se indica que es equivalente a las instrucciones Y86
rrmovl %ebp, %esp y popl %ebp. Agregue esta instrucción al conjunto
de instrucciones del procesador Y86, usando la siguiente codificación.
0
D 0
1
2
3
4
5
Use la tabla para popl como guía.
Solución
La tabla de acciones para esta instrucción se muestra a continuación.
Etapa
Acciones
Fetch
iCode : iFun ← M1 [P C]
valP ← P C + 1
Decode
valA ← R[ %ebp]
valB ← R[ %ebp]
Execute
valE ← valB + 4
Memory
valM ← M4 [valA]
Write Back
R[ %esp] ← valE
R[ %ebp] ← valM
PC Update
P C ← valP
3.4 En los programas de ejemplo del procesador Y86, hay ocasiones en que
es necesario sumar un valor constante a un registro. Esto requiere usar
38
Capítulo 3: Procesador Y86
una instrucción irmovl para almacenar la constante en un registro, y
luego una instrucción addl para sumar su valor al registro destino.
Agregue entonces una instrucción iaddl con el siguiente formato:
0
C 0
1
8 rB
2
3
4
5
V
Esta instrucción suma la constante V al registro rB. Describa las computaciones necesarias para implementar la instrucción. Use la tabla para
irmovl y OP como guía.
Solución
La tabla de acciones para esta instrucción se muestra a continuación.
Etapa
Acciones
Fetch
iCode : iFun ← M1 [P C]
rA : rB ← M1 [P C + 1]
valC ← M4 [P C + 2]
valP ← P C + 6
Decode
valB ← R[rB]
Execute
valE ← valB + valC
Memory
Write Back
R[rB] ← valE
PC Update
P C ← valP
3.5 La función recursiva de Takeuchi fue inventada por Ikeo Takeuchi en
1978 para medir el desempeño de compiladores LISP, mientras trabajaba en los laboratorios de la Nippon Telephone and Telegraph Co. Esta
función está definida como sigue:
T (x, y, z) = if x ≤ y then y else T (T (x − 1, y, z), T (y − 1, z, x), T (z − 1, x, y))
Una posible implementación en lenguaje C es:
Capítulo 3: Procesador Y86
39
int takeuchi(int x, int y, int z)
{
if (x <= y)
return y;
else
return takeuchi(takeuchi(x - 1, y, z), takeuchi(y - 1, z, x),
takeuchi(z - 1, x, y));
}
La invocación de la función takeuchi() con argumentos (3, 2, 1) resulta
en 9 invocaciones sucesivas a la función, con diferentes argumentos, y
un resultado final de 3.
Escriba una función en lenguaje ensamblador Y86 que implemente esta función. Recuerde que la convención de paso de parámetros
de C dice que éstos se almacenan en la pila de derecha a izquierda.
No olvide cumplir con las convenciones Caller-Save y Callee-Save.
El siguiente esqueleto de código le puede servir de comienzo.
# Ejecucion en la direccion 0x00
.pos 0
init:
irmovl Pila, %esp
# define puntero a la pila
irmovl Pila, %ebp
# define puntero base
jmp Main
# ejecuta programa principal
Main:
irmovl $1, %eax
# %eax vale 1
pushl %eax
# 3er argumento z es 1
irmovl $2, %eax
# %eax vale 2
pushl %eax
# 2do argumento y es 2
irmovl $2, %eax
# %eax vale 3
pushl %eax
# 1er argumento x es 3
call takeuchi
# llama a takeuchi(3, 2, 1)
halt
# int takeuchi (int x, int y, int z)
# INSERTE SU CODIGO AQUI
.pos 0x200
Pila:
# pila del programa comienza aqui
Simule la ejecución de su función para los argumentos (3, 2, 1)
usando el simulador secuencial ssim-std.exe. Calcule el número
de ciclos que demora la ejecución y la cantidad de memoria utilizada, tanto por el programa como por la pila.
40
Capítulo 3: Procesador Y86
Simule ahora la ejecución de su función usando el simulador segmentado psim.exe. Indique el CPI obtenido, identificando las instrucciones que causan pérdida de ciclos en el pipeline.
Solución
El siguiente código muestra una posible implementación de la función
de Takeuchi en lenguaje ensamblador Y86.
# Ejecucion comienza en la direccion 0x00
.pos 0
init: irmovl Pila, %esp
# define puntero a la pila
irmovl Pila, %ebp
# define puntero base
jmp Main
# ejecuta programa principal
Main: irmovl $1, %eax
# %eax vale 1
pushl %eax
# 3er argumento z es 1
irmovl $2, %eax
# % eax vale 2
pushl %eax
# 2do argumento y es 2
irmovl $3, %eax
# %eax vale 3
pushl %eax
# 3er argumento z es 3
call takeuchi
# llama a takeuchi con argumentos (3, 2, 1)
halt
# int takeuchi(int x, int y, int z)
takeuchi:
pushl %ebp
# almacena puntero base
rrmovl %esp, %ebp
# crea marco de activación
mrmovl 8(%ebp), %ecx
# lee x
mrmovl 12(%ebp), %eax
# lee y
subl %ecx, %eax
# resta y - x
jl Lazo
mrmovl 12(%ebp), %eax
# lee y
rrmovl %ebp, %esp
# recupera puntero a la pila
popl %ebp
# recupera puntero base
ret
Lazo:
mrmovl 16(%ebp), %edx
irmovl $-1, %eax
addl %eax, %edx
mrmovl 12(%ebp), %eax
pushl %eax
pushl %ecx
pushl %edx
call takeuchi
irmovl $12, %edx
addl %edx, %esp
pushl %eax
#
#
#
#
#
#
#
lee z
almacena -1 en %eax
calcula z - 1
lee y
3er argumento es y
2do argumento es x
1er argumento es z - 1
# almacena 12 en %edx
# ajusta el puntero a la pila en 12
# almacena resultado en la pila
41
Capítulo 3: Procesador Y86
mrmovl 8(%ebp), %ecx
mrmovl 12(%ebp), %eax
irmovl $-1, %edx
addl %edx, %eax
mrmovl 16(%ebp), %edx
pushl %ecx
pushl %edx
pushl %eax
call takeuchi
irmovl $12, %edx
addl %edx, %esp
pushl %eax
#
#
#
#
#
#
#
#
lee x
lee y
almacena - 1 en %edx
calcula y - 1
lee z
3er argumento es x
2do argumento es z
1er argumento es y - 1
mrmovl 8(%ebp), %ecx
irmovl $-1, %edx
addl %edx, %ecx
mrmovl 16(%ebp), %edx
mrmovl 12(%ebp), %eax
pushl %edx
pushl %eax
pushl %ecx
call takeuchi
irmovl $12, %edx
addl %edx, %esp
pushl %eax
#
#
#
#
#
#
#
#
call takeuchi
irmovl $12, %edx
addl %edx, %esp
pushl %eax
#
#
#
#
rrmovl %ebp, %esp
popl %ebp
ret
# recupera puntero a la pila
# recupera puntero base
# almacena 12 en %edx
# ajusta el puntero a la pila
# almacena resultado en la pila
lee x
almacena -1 en %edx
calcula x - 1
lee z
lee y
3er argumento es z
2do argumento es y
1er argumento es x - 1
# almacena 12 en %edx
# ajusta el puntero a la pila
# almacena resultado en la pila
llama a takeuchi de nuevo
almacena 12 en %edx
ajusta el puntero a la pila
almacena resultado en la pila
.pos 0x0200
Pila:
# Pila del programa comienza aqui
Algunos comentarios sobre esta solución:
El programa anterior cumple con las convenciones Caller-Save y
Callee-Save, y con la convención de paso de parámetros de C a través de la pila, de derecha a izquierda.
Se escribió el programa intentando minimizar el uso de memoria
en la pila, para así facilitar la ejecución recursiva de la función.
Capítulo 3: Procesador Y86
42
El programa ocupa 236 bytes de memoria, y su ejecución ocupa 68
bytes en la pila. La función takeuchi() misma ocupa 189 bytes de
memoria.
La simulación del programa anterior usando el simulador secuencial ssim-std.exe calcula la función takeuchi(3, 2, 1) en 177
ciclos de reloj.
La simulación del programa anterior usando el simulador segmentado psim.exe calcula la función takeuchi(3, 2, 1) en 229
ciclos de reloj, dando un CPI de 1.29
La función takeuchi() es invocada 9 veces. Cada invocación involucra una instrucción de retorno ret. Las penalidad asociada a
dichas instrucciones es de 3 ciclos. Entonces, en total estas instrucciones generan 27 ciclos de penalidad. Asimismo, hay 9 instrucciones de salto condicional jl. La penalidad en caso de predicción
errónea de salto es de 2 ciclos. Estudiando el comportamiento del
algoritmo, puede verse que este salto es predicho erróneamente 7
veces, por lo que hay 14 ciclos de penalidad asociados a ese salto.
Los ciclos de penalidad restantes se deben a peligros de lectura
y uso. Por ejemplo, es necesario leer el argumento y de la función
desde memoria usando mrmovl 12(%ebp), %eax, y escribirlo nuevamente a la pila usando pushl %eax antes de invocar por primera
vez a la función takeuchi() después del rótulo Lazo.
3.6 El procesador Intel 80386 posee una instrucción bsr S, D conocida
como Bit Scan Reverse, que determina el índice del primer bit en 1 del
registro S, revisando dicho registro de izquierda a derecha, y retorna
dicho índice en el registro D. Esta instrucción no existe en el procesador Y86, pero puede ser implementada como una función en lenguaje
ensamblador.
a) Escriba, una función bsr en lenguaje ensamblador Y86 que opere
sobre el registro %eax y retorne en el registro %edx el índice del
primer bit en 1 del registro %eax. Es decir, si %eax = 0x432FA708,
al terminar su función el registro %edx debe contener el número
30, ya que ése es el mayor bit de %eax que tiene valor 1.
b) Cuál es el tamaño de su función en bytes?
c) Suponga que su función se ejecuta en el procesador secuencial estudiado en clases. Cuántos ciclos de reloj demorará su ejecución
para %eax = 0x12345678?
43
Capítulo 3: Procesador Y86
d) Suponga ahora que su función se ejecuta en el procesador con pipelining y forwarding visto en clases. Recuerde que las penalidades asociadas a este procesador son:
Peligro
Penalidad
Lectura y uso
Predicción errada
Retorno de función
1
2
3
Cuántos ciclos de reloj demorará ahora la ejecución de su función
en este procesador segmentado, para %eax = 0x12345678?
Solución
a) Una posible implementación de la operación bsr S, D se muestra
a continuación.
bsr:
Lazo:
Fin:
pushl %ebp
rrmovl %esp, %ebp
pushl %esi
irmovl $1, %esi
irmovl $31, %edx
xorl %ecx, %ecx
subl %ecx, %eax
jl Fin
addl %eax, %eax
subl %esi, %edx
je Fin
jmp Lazo
popl %esi
rrmovl %ebp, %esp
popl %ebp
ret
# crea marco de activacion
#
#
#
#
#
#
#
#
#
almacena copia de registro %esi
inicializa %esi a 1
inicializa %edx a 31
inicializa %ecx a 0
resta %eax - 0
si resultado es negativo, ir a Fin
si resultado es positivo, duplicar %eax
decrementar %edx en 1
si resultado es 0, ir a Fin
# recupera valor de registro %esi
# recupera marco de activacion
# retorno de funcion
b) Es sabido que las instrucciones irmovl ocupan 6 bytes de memoria, las instrucciones ret ocupan 1 byte, que las instrucciones de
salto ocupan 5 bytes y que el resto de las instrucciones ocupa 2
bytes de memoria. Puede calcularse, entonces, que el código mostrado tiene un tamaño de 48 bytes.
c) La ejecución de la función mostrada en el procesador secuencial
demorará tantos ciclos como instrucciones se ejecuten. En el caso que el registro %eax toma el valor 0x12345678, se ejecutan 30
instrucciones y por lo tanto demora 30 ciclos.
44
Capítulo 3: Procesador Y86
d) La ejecución de la función mostrada en el procesador segmentado con pipelining y forwarding para el caso el caso en que el registro %eax toma el valor 0x12345678 demora 45 ciclos: 30 ciclos
correspondientes a la ejecución del código sin penalidad, mas 12
ciclos de penalidad causados por los 6 saltos condicionales predichos erróneamente, mas 3 ciclos de penalidad del retorno de función.
3.7 El procesador Intel Y86 no posee una instrucción de rotación de registros, pero ésta puede ser implementada como una función en lenguaje
ensamblador.
Escriba una función rotate que reciba como argumento un entero p
entre 0 y 31 y que retorne el resultado de rotar el registro %eax p bits a
la izquierda. si inicialmente se tiene que %eax = 0x12345678, su valor
después de rotarlo 7 bits a la izquierda es %eax = 0x1A2B3C09. Cuál es
el tamaño de su función en bytes?
Solución
Se muestra a continuación una implementación en 53 bytes de la función solicitada.
rotate: pushl %ebp
rrmovl %esp, %ebp
mrmovl 8(%ebp), %edx
irmovl $1, %ecx
lazo:
andl %edx, %edx
je fin
subl %ecx, %edx
andl %eax, %eax
jl neg
addl %eax, %eax
jmp lazo
neg:
addl %eax, %eax
addl %ecx, %eax
jmp lazo
fin:
rrmovl %ebp, %esp
popl %ebp
ret
# crea marco de activacion
# lee p
# inicializa %ecx a 1
# si p es 0, terminar
#
#
#
#
#
#
#
decrementa p
hace esto para pasar por la ALU
salta si %eax es negativo
desplaza %eax a la izquierda 1 bit
vuelve a lazo
desplaza %eax a la izquierda 1 bit
Agrega bit 1 a la derecha de %eax
# recupera marco de activacion
.pos 0x100
Pila:
# Define la pila del proceso
Capítulo
Pipelining
4.1 Se desea ejecutar un cierto código usando el procesador con pipelining
visto en clases. Se ha medido la siguiente tabla de frecuencias para el
código a ejecutar:
El 20 % de las instrucciones corresponden a instrucciones mrmovl
y popl. De éstas, el 30 % genera peligros de lectura y uso.
El 15 % de las instrucciones son instrucciones de salto condicional,
en las cuales el 50 % de las veces se ejecuta el salto y en el 50 %
restante no se ejecuta el salto.
El 5 % de las instrucciones corresponden a instrucciones de retorno ret.
a) Calcule el CPI asociado a la ejecución de este código en el procesador.
b) Se ha decidido modificar el circuito de predicción de saltos de manera que ahora el porcentaje de saltos predichos correctamente es
80 %. Pero, a un costo de alargar el ciclo de reloj del procesador en
10 %. Vale la pena realizar esta modificación?
Solución
a) En el primer caso, es necesario calcular la penalidad asociada a
cada tipo de peligro. Tabla 4.1 muestra este cálculo. El CPI es entonces CP I = 1 + 0.06 + 0.15 + 0.15 = 1.36.
45
4
46
Capítulo 4: Pipelining
Tabla 4.1: Penalidad por tipo de instrucción
Penalidad
Frec. Instr.
Frec. Condición
Burbujas
Producto
Lectura/uso
Predicción errada
Retorno
0.2
0.15
0.05
0.3
0.5
1.0
1
2
3
0.06
0.15
0.15
b) En el segundo caso, el costo de las predicciones erradas es ahora 0.15 × 0.2 × 2 = 0.06, por lo que el CPI es ahora 1.27. Pero, el
procesador usa un reloj 10 % mas lento. Entonces, el CPI efectivo
comparado con el procesador anterior es 1.27 ∗ 1.1 = 1.397. Por lo
tanto, no vale la pena la modificación.
4.2 La figura 4.1 muestra las etapas de la lógica combinacional de un procesador secuencial. Se ha determinado que ésta puede ser dividida en
6 etapas llamadas A a F, donde cada etapa tiene las latencias indicadas.
Se desea crear nuevas versiones de este diseño insertando registros de
pipeline entre bloques. Suponga que un registro de pipeline tiene un
retardo de 20 ps.
Figura 4.1: Procesador segmentado de 6 etapas
a) Cuál es la latencia y el throughput del pipeline actual?
b) Se desea convertir el sistema anterior a un pipeline de 2 etapas.
Dónde insertaría Ud. el registro de pipeline? Cuál será la latencia
y y el throughput del nuevo pipeline?
Capítulo 4: Pipelining
47
c) Se desea ahora convertir el sistema anterior a un pipeline de 3
etapas. Dónde insertaría Ud. los registros de pipeline? Cuál será la
latencia y y el throughput de este nuevo pipeline?
d) Se desea ahora convertir el sistema anterior a un pipeline de 4
etapas. Dónde insertaría Ud. los registros de pipeline? Cuál será la
latencia y y el throughput de este nuevo pipeline?
e) Se desea ahora convertir el sistema anterior a un pipeline de desempeño máximo. Cuántos registros de pipeline insertaría Ud. y adónde? Cuál será la latencia y y el throughput de este nuevo pipeline?
Solución
a) La latencia actual del pipeline es de 320 ps, lo que da un throughput de 3201 ps = 3.125 ∗ 109 instrucciones/s.
b) Se debe insertar un registro de pipeline entre las etapas C y D,
dividiendo así el pipeline en la etapa A-B-C-registro, de duración
190 ps, y la etapa D-E-F-registro de duración 150 ps. La nueva
latencia de este pipeline es 380 ps, y el throughput es 1901 ps =
5.263 ∗ 109 instrucciones por segundo.
c) Se debe insertar un registro de pipeline entre las etapas B y C, y
otro registro de pipeline entre las etapas D y E, dividiendo así el
pipeline en la etapa A-B-registro, de duración 130 ps, la etapa CD-registro, de duración 130 ps y la etapa E-F-registro de duración
100 ps. La nueva latencia de este pipeline es 390 ps, y el throughput
es 1301 ps = 7.693 ∗ 109 instrucciones por segundo.
d) Se debe insertar un registro de pipeline entre las etapas A y B,
otro registro de pipeline entre las etapas C y D y otro registro de
pipeline entre las etapas D y E. Esto divide el pipeline en la etapa A-registro, de duración 100 ps, la etapa B-C-registro, de duración 110 ps, la etapa D-registro de duración 70 ps y la etapa
E-F-registro de duración 100 ps. La nueva latencia de este pipeline es 440 ps, y el throughput es 1101 ps = 9.09 ∗ 109 instrucciones
por segundo.
e) Finalmente, se insertan registros de pipeline entre las etapas A y
B, B y C, C y D y D y E. Esto divide el pipeline en 5 etapas, la más
lenta de las cuales demora 100 ps, por lo que la nueva latencia de
este pipeline es 500 ps, y el throughput es 1001 ps = 1010 instrucciones por segundo.
Capítulo 4: Pipelining
48
4.3 Un procesador tiene un pipeline de 5 etapas, de largo 20, 40, 30, 10, y
20 ps. Se necesita correr un código que ejecuta 24 veces un lazo de 135
instrucciones en este procesador. Cuánto se demorará este código?
Solución
Supondremos que el retardo de los registros está incluido en el tiempo
de las etapas. Entonces, el procesador iniciará la ejecución de una instrucción cada 40 ps. El código requiere la ejecución de 24 ∗ 135 instrucciones, lo que requerirá 3240 ciclos, demorando entonces 3240 ∗ 40 =
129.6 ns.
4.4 Se desea ejecutar un cierto código usando el procesador con pipelining
visto en clases. Al ejecutar un conjunto dado de aplicaciones, se ha medido la siguiente tabla de frecuencias para el código a ejecutar:
El 25 % de las instrucciones corresponden a instrucciones de lectura de datos desde memoria. De éstas, el 25 % genera peligros de
lectura y uso.
El 12 % de las intrucciones son instrucciones de salto condicional,
en las cuales el 70 % de las veces se ejecuta el salto y en el 30 %
restante no se ejecuta el salto.
El 2.5 % de las instrucciones corresponden a instrucciones de retorno ret.
a) Calcule el CPI asociado a la ejecución de este código en el procesador.
b) Se ha decidido modificar el circuito de predicción de saltos de manera que ahora el procesador supone que los saltos condicionales
hacia atrás siempre se toman, y los saltos condicionales hacia adelante nunca se toman. El examen de las aplicaciones ejecutadas
muestra que el 70 % de los saltos condicionales son hacia atrás, y
de éstos el 80 % se toman. Esta modificación alarga el ciclo de reloj
del procesador en un 8 %. Vale la pena realizar esta modificación?
Solución
a) Para calcular el CPI, es necesario calcular las penalidades incurridas por tres tipos de instrucciones. En el caso de instrucciones de lectura y uso, se sabe que sólo en un cuarto de las instrucciones de lectura y uso se incurre en una penalidad de 1 ci-
Capítulo 4: Pipelining
49
clo. Entonces, la penalidad total asociada a estas instrucciones es
0.25 × 0.25 × 1 = 0.0625 ciclos.
Luego, es necesario calcular el costo de una predicción de salto
errada. El procesador siempre predice que el salto se toma. Si éste
efectivamente es tomado, la penalidad es 0. En cambio, si el salto
no se toma, la penalidad es 2 ciclos. Entonces, como la predicción
de salto falla en 3 de cada 10 saltos, la penalidad total asociada a
estas instrucciones es 0.12 × 0.3 × 2 = 0.072 ciclos.
Finalmente, las instrucciones de retorno siempre sufren una penalidad de 3 ciclos. La penalidad total asociada a estas instrucciones es 0.025 × 3 = 0.075 ciclos. Por lo tanto, el CPI total es
CP I = 1 + 0.0625 + 0.072 + 0.075 = 1.2095
b) Para analizar esta alternativa, es necesario modificar el cálculo del
costo de una predicción de salto errada. El procesador predice que
los saltos condicionales hacia atrás siempre se toman, y que los saltos condicionales hacia adelante nunca se toman. De la respuesta
anterior, sabemos que los saltos no son tomados en un 30 % de los
casos. También sabemos que el 70 % de los saltos condicionales
son hacia atrás, y de éstos el 80 % se toman. Esto significa que el
56 % de los saltos son hacia atrás y se toman, por lo que la predicción está correcta y la penalidad asociada es 0, y que el 14 %
de los saltos son hacia atrás y no se toman, con penalidad 2 ciclos.
Además, esto nos dice que el 16 % de los saltos son hacia adelante y no se toman, y que el 14 % de los saltos son hacia adelante y
se toman. Los primeros serán predichos correctamente, por lo que
tienen una penalidad de 0 ciclos y los segundos tendrán una penalidad de 2 ciclos. Por lo tanto, el costo de la predicción de saltos
es 0.12 × (0.56 × 0 + 0.14 × 2 + 0.14 × 2 + 0.16 × 0) = 0.0672. Entonces,
el nuevo CPI será CP I = 1 + 0.0625 + 0.0672 + 0.075 = 1.2047.
Ahora, esta modificación alarga el ciclo de reloj del procesador en
un 8 %, por lo que el nuevo CPI será realmente 1.08 ∗ 1.2047 =
1.301, el cual, al ser mayor que la alternativa a), no hace rentable
realizar este cambio.
4.5 Sea la siguiente función en lenguaje ensamblador X86, que determina
si una ristra es palíndromo, es decir, si se lee de la misma manera de derecha a izquierda que de izquierda a derecha. Esta función recibe como
argumentos la dirección en memoria de la ristra a examinar y su longi-
50
Capítulo 4: Pipelining
tud en caracteres. La primera columna del código indica el número de
la instrucción.
.type palindromo, @function
palindromo:
1.
pushl %ebp
#
2.
movl %esp, %ebp
#
3.
pushl %ebx
#
4.
movl 8(%ebp), %eax
#
5.
movl 12(%ebp), %edx
#
6.
subl $1, %edx
#
7.
movl $0, %ebx
#
8. lazo: movb (%eax, %ebx), %ch
9.
movb (%eax, %edx), %cl
10.
cmpb %cl, %ch
#
11.
jne finNo
#
12.
incl %ebx
#
13.
decl %edx
#
14.
cmpl %ebx, %edx
#
15.
js finSi
#
16.
jmp lazo
17. finSi: movl $1, %eax
18.
jmp fin
19. finNo: movl $0, %eax
20. fin:
popl %ebx
#
21.
leave
22.
ret
Almacena puntero base en la pila
Redefine el puntero base
Salva en la pila el registro %ebx
Lee direccion de la ristra en %eax
Lee el largo de la ristra en %edx
Calcula offset del ultimo caracter
Calcula offser del primer caracter
Compara los caracteres
Si son distintos, no es palindromo
Incrementa indice in %ebx
Decrementa indice en %edx
Compara los indices
Si es negativo, es palindromo
Recupera el registro %ebx de la pila
Suponga que este código se ejecuta en un procesador segmentado de 5
etapas similar al visto en clases.
a) Identifique en el código los posibles peligros de lectura y uso, indicando los números de las instrucciones donde se producen.
b) Identifique en el código las posibles instancias de stalling.
c) Identifique en el código las posibles instancias de forwarding.
Solución
a) Los peligros de lectura y uso se producen al leer un dato desde
memoria en una instrucción, y utilizar este mismo dato en la instrucción siguiente. Esto ocurre:
1) entre las instrucciones 5 y 6,
2) entre las instrucciones 8 y 9, y
Capítulo 4: Pipelining
51
3) entre las instrucciones 9 y 10.
b) En nuestro procesador simple, stalling ocurre en caso de lectura
y uso, en el caso de saltos condicionales, y en el retorno de una
función. Esto ocurre:
1) entre las instrucciones 5 y 6 (lectura y uso), donde se introduce una burbuja,
2) entre las instrucciones 8 y 9 (lectura y uso), donde se introduce una burbuja,
3) entre las instrucciones 9 y 10 (lectura y uso), donde se introduce una burbuja,
4) entre las instrucciones 10 y 11 (salto condicional), donde se
introducen dos burbujas,
5) entre las instrucciones 14 y 15 (salto condicional), donde se
introducen dos burbujas, y
6) después de la instrucción 21 (retorno de función), donde se
introducen tres burbujas.
c) El forwarding se produce cuando existe dependencia de datos entre instrucciones, y es necesario redirigir un registro entre etapas
de una instrucción en ejecución. Para este análisis, supondremos
que los registros normalmente son leídos al comenzar el ciclo ID,
y que normalmente son escritos al terminar la etapa WB. Entonces,
cualquier transferencia de datos entre registros en otras etapas requiere forwarding. En este caso, se tienen las siguientes instancias:
1) Forwarding del registro %esp desde la etapa EXE de la instrucción 1 a la etapa ID de la instrucción 2.
2) Forwarding del registro %esp desde la etapa MEM de la instrucción 1 a la etapa ID de la instrucción 3.
3) Forwarding del registro %ebp desde la etapa MEM de la instrucción 2 a la etapa ID de la instrucción 4.
4) Forwarding del registro %ebp desde la etapa WB de la instrucción 2 a la etapa ID de la instrucción 5.
5) Forwarding del registro %edx desde la etapa MEM de la instrucción 5 a la etapa ID de la instrucción 6. Esta última etapa se
repite 1 vez debido a que existe un peligro de lectura y uso.
6) Forwarding del registro %ebx desde la etapa EXE de la instrucción 7 a la etapa ID de la instrucción 8.
Capítulo 4: Pipelining
52
7) Forwarding del registro %ch desde la etapa MEM de la instrucción 8 a la etapa ID de la instrucción 9. Esta última etapa se
repite 1 vez debido a que existe un peligro de lectura y uso.
8) Forwarding de los códigos de condición desde la etapa MEM de
la instrucción 9 a la etapa EXE de la instrucción 10. Además,
en esta instrucción existe un peligro de lectura y uso.
9) Forwarding del registro %ebx desde la etapa MEM de la instrucción 11 a la etapa ID de la instrucción 13.
10) Forwarding del registro %edx desde la etapa EXE de la instrucción 12 a la etapa ID de la instrucción 13.
11) Forwarding de los códigos de condición desde la etapa MEM de
la instrucción 13 a la etapa EXE de la instrucción 14.
4.6 Suponga ahora que este procesador tiene un CPI ideal de 1 instrucción
por ciclo, sin considerar penalidades. Determine cuántos ciclos de reloj
demorará la ejecución completa de esta función si sus argumentos son
la ristra radar y el largo 5, considerando ahora las penalidades. En este
caso, suponga que no hay predicción de saltos.
Solución
Ejecutar el código mostrado con los argumentos radar y 5 requiere la
ejecución de 35 instrucciones. Éstas incluyen 7 casos de lectura y uso
(que introducen 7 burbujas), 6 instrucciones de salto condicional (que
introducen 12 burbujas) y una instrucción ret, que introduce 3 burbujas, para un total de 22 burbujas. Por ende, la ejecución de este código requiere 57 ciclos de reloj. A su vez, esto corresponde a un CPI de
57
35 = 1.628.
4.7 Suponga ahora que el procesador en cuestión se modifica de manera de
incorporar predicción de salto. Los saltos condicionales hacia adelante se predicen como no tomados, y los saltos condicionales hacia atrás
se predicen como tomados. No hay penalidad asociada al caso de una
predicción acertada.
Además, se sabe que el costo de esta modificación al procesador es el
aumentar un 2 % su período máximo de reloj.
Determine ahora el número de ciclos de reloj que demorará la ejecución completa de la función en este nuevo procesador, y compare la
ejecución de este código con el caso anterior.
Capítulo 4: Pipelining
53
Solución
Ejecutar el código mostrado con los argumentos radar y 5 requiere la
ejecución de 6 instrucciones de salto condicional, todas ellas hacia adelante. De éstas, 5 no se toman y sólo 1 de ellas se toma. Entonces, al
usar el esquema de predicción descrito, hay 5 predicciones acertadas y
1 predicción errada. Esto reduce la penalidad asociada de 12 ciclos a
sólo 2 ciclos. Por ello, este código se ejecuta en sólo 47 ciclos, para un
CPI de 47
35 = 1.343. Como el ciclo de reloj es 2 % más lento, la ejecución
del código demorará 47 × 1.02 = 47.94 ciclos del reloj original. Por lo
tanto, esta solución es conveniente porque lleva a una ejecución más
rápida de la función dada.
Capítulo
Optimización de código
5.1 Sea la siguiente función en C:
void funcion(int *xp, int *yp) {
*xp = *xp + *yp;
*yp = *xp - *yp;
*xp = *xp - *yp;
}
a) Identifique qué hace esta función
b) Identifique qué problemas puede tener esta función si ambos argumentos apuntan a la misma posición de memoria.
Solución
a) La función intercambia los datos apuntados por los argumentos.
b) Si ambos punteros apuntan a la misma posición de memoria, entonces esta posición toma siempre el valor 0.
54
5
Capítulo
Sistemas de memoria
6.1 Suponga una memoria principal de 8192 bytes y una memoria cache
de 64 bytes, organizada como 16 líneas de 4 bytes cada una, dividida
en dos conjuntos.
El contenido de la cache es como se muestra en la siguiente tabla, donde
la primea columna es el número de línea, V es el bit de validez, y B0 a
B3 son los bytes 0 a 3, respectivamente.
0
1
2
3
4
5
6
7
Tag
09
45
EB
06
C7
71
91
46
V
1
1
0
0
1
1
1
0
B0
86
60
2F
3D
06
0B
A0
B1
Cache asociativa de 2 conjuntos
B1 B2 B3
Tag V B0
30 3F 10
00 0 99
4F E0 23
38 1 00
81 FD 09
0B 0 8F
94 9B F7
32 1 12
78 07 C5
05 1 40
DE 18 4B
6E 0 B0
B7 26 2D
F0 0 0C
0A 32 0F
DE 1 12
B1
04
BC
e2
08
67
39
71
C0
B2
03
0B
05
7B
C2
D3
40
88
B3
48
37
BD
AD
3B
F7
10
37
a) Indique en un diagrama los bits de la dirección física correspondientes a
1) el tag
2) el índice
3) el número del byte en la línea
55
6
Capítulo 6: Sistemas de memoria
56
b) El procesador genera la dirección 0x0E34. Indique la línea de la
cache accesada y el byte accedido. Indique además si el acceso genera un fallo en cache.
6.2 Ud. ha comprado un nuevo computador, y estudios de desempeño en
éste le indican que:
95 % de los accesos a memoria son atendidos por la memoria cache.
Cada bloque de memoria cache es de 2 palabras, y el bloque completo es transferido desde memoria RAM a la memoria cache en
caso de una falta de cache.
El procesador genera, en promedio, 109 direcciones de memoria
por segundo.
25 % de estas direcciones son escrituras a memoria.
Suponga que el sistema de memoria soporta 109 accesos por segundo, pero el bus de interconexión sólo soporta un acceso a la
vez (no puede haber 2 lecturas o escrituras a memoria en forma
simultánea).
Suponga que, en promedio, el 30 % de los bloques en la cache han
sido modificados.
Calcule el tiempo de acceso promedio a memoria y el porcentaje de
utilización del bus de interconexión para los siguientes casos:
a) La memoria cache es Write-Through
b) La memoria cache es Write-Back
6.3 Suponga que la ejecución de un programa en un computador con un
sistema de memoria ideal muestra un CPI de 1.5. El sistema de memoria real del computador tiene una latencia de 40 ciclos, después de los
cuales la memoria RAM es capaz de entregar 4 bytes en cada ciclo de
reloj. Suponga además que hay 32 bytes en una línea de memoria cache, que el 20 % de la instrucciones son instrucciones de transferencias
de datos, y que el 50 % de estas instrucciones son escrituras a bloques
de memoria modificados. Se sabe que este computador puede utilizarse
con
Una memoria cache unificada de 16 KiB con asociatividad directa,
que utiliza Write-Back y tiene una tasa de fallas de 2.9 %.
Capítulo 6: Sistemas de memoria
57
Una memoria cache unificada de 16 KiB con asociatividad de dos
vías, que utiliza Write-Back y tiene una tasa de fallas de 2.2 %.
Una memoria cache unificada de 32 KiB con asociatividad directa,
que utiliza Write-Back y tiene una tasa de fallas de 2.9 %.
Calcule para todos los casos el CPI efectivo del sistema.
6.4 Suponga que tiene el siguiente código C que se ejecutará en un sistema con una memoria cache directamente asociativa de 1024 bytes de
tamaño, con 16 bytes por línea.
struct posicion {
int x;
int y;
};
struct posicion malla[16][16];
int xTotal = 0; yTotal = 0;
int i, j;
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
xTotal += malla[i][j].x;
}
}
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
yTotal += malla[i][j].y;
}
}
Suponga además que:
Un entero int ocupa 4 bytes.
La cache está inicialmente vacía.
La matriz malla se almacena en memoria a partir de la posición
0x00.
Las variables i, j, xTotal y yTotal se almacenan en los registros
del procesador.
Entonces,
a) Calcule la tasa de fallos de la memoria cache
b) Repita lo anterior para el siguiente lazo
Capítulo 6: Sistemas de memoria
58
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
xTotal += malla[i][j].x;
yTotal += malla[i][j].y;
}
}
c) Repita el punto anterior, pero ahora suponiendo una memoria cache del doble del tamaño
d) Repita ahora los dos puntos anteriores para el siguiente lazo
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
xTotal += malla[j][i].x;
yTotal += malla[j][i].y;
}
}
Solución
La matriz malla contiene 256 elementos de tipo posicion, cada uno de
ellos de 2 enteros, ocupando 8 bytes. Entonces, la matriz ocupa 2048
bytes, así que no cabe completamente en la memoria cache. Además,
una línea de cache contiene 16 bytes o 2 elementos de la matriz malla.
Por ello, no hay aciertos en cache por reutilización de datos y sólo puede haber aciertos en accesos a datos que hayan sido precargados en la
cache.
a) El primer ciclo anidado accede primero a todos los miembros x
y luego a todos los miembros y de las estructuras posición de la
matriz malla. El primer acceso a un miembro x transfiere 2 estructuras posicion a la memoria cache, pero sólo la escritura al
miembro x del segundo elemento transferido serán aciertos en la
cache. Esta situación se repite para los accesos a los miembros y
de la matriz. Así, la tasa de fallos es 50 %.
b) En este caso, se accede a los elementos de la matriz por filas, y a
ambos miembros de una estructura posición en forma secuencial.
El acceso al primer miembro x será un fallo en la cache, pero los
tres accesos siguientes serán aciertos en la cache, así que la tasa de
fallos será 25 %.
c) La tasa de fallo no se ve afectada si el tamaño de la memoria se
duplica.
Capítulo 6: Sistemas de memoria
59
d) En este caso, se accede a los elementos de la matriz por columnas,
por lo que el acceso al miembro x será un fallo de la cache, pero
el acceso al miembro y del mismo elemento será un acierto. Por
ello, la tasa de fallos será de 50 %. Si la memoria cache se duplica,
entonces la tasa de fallos disminuye a 25 %.
6.5 Considere el siguiente código C, que calcula la traspuesta de una matriz
typedef int matriz[2][2];
void traspuesta(matriz destino, matriz fuente) {
int i, j;
for (i = 0; i < 2; i++) {
for (j = 0; j < 2; j++) {
destino[j][i] = fuente[i][j];
}
}
}
Suponga que este código se ejecuta en una máquina de 32 bits con las
siguientes características:
La matriz destino comienza en la dirección de memoria 0 y la
matriz fuente comienza en la dirección de memoria 0x0010.
El sistema posee una cache de datos L1 de 16 bytes de tamaño,
asociatividad directa, una política de escrituras Write-Through y
líneas de 8 bytes.
Esta cache está inicialmente vacía.
Los únicos accesos a memoria son las lecturas y escrituras a las
matrices destino y fuente.
Entonces:
a) Para cada elemento de las matrices destino y fuente, indique si
su acceso fue una falla o un acierto en cache.
b) Calcule la tasa de aciertos de la matriz.
c) Calcule el número de bytes transferidos entre RAM y cache.
6.6 Ud. está escribiendo un nuevo juego 3D para celulares que le darán
fama y fortuna, y hoy debe escribir una función que limpie la pantalla
Capítulo 6: Sistemas de memoria
60
antes de dibujar la siguiente imagen. La pantalla tiene 480 líneas de
640 pixeles cada una. El procesador que Ud. está utilizando tiene una
cache directa de 64 KiB con líneas de 4 bytes. Las estructuras de datos
básicas que Ud. está usando son:
struct pixel {
char r;
char g;
char b;
char a;
};
struct pixel buffer[480][640];
int i, j;
char *cptr;
int *iptr;
Suponga que sizeof(char) = 1, sizeof(int) = 4, que buffer está
almacenado a partir de la dirección de memoria 0x0000, que la cache
está inicialmente vacía y que las variables i, j, cptr y iptr están almacenadas en los registros del procesador, de manera que los únicos
accesos a memoria son a direcciones dentro de buffer.
a) Ud. primero escribe el siguiente código para limpiar la pantalla.
Calcule la tasa de aciertos de la cache.
for (j = 0; j < 640; j++) {
for (i = 0; i < 480; i++) {
buffer[i][j].r = 0;
buffer[i][j].g = 0;
buffer[i][j].b = 0;
buffer[i][j].a = 0;
}
}
b) Luego, Ud. escribe el siguiente código para limpiar la pantalla.
Calcule la tasa de aciertos de la cache.
char *cptr;
cptr = (char *) buffer;
for (; cptr < ((char *) buffer + 640*480*4; cptr++) {
*cptr = 0;
}
Capítulo 6: Sistemas de memoria
61
c) Finalmente, Ud. escribe el siguiente código para limpiar la pantalla. Calcule la tasa de aciertos de la cache.
int *iptr;
iptr = (int *) buffer;
for (; iptr < buffer + 640*480; iptr++) {
*iptr = 0;
}
d) Cuál código usará Ud.?
Solución
En este problema, el tamaño de la pantalla determina la cantidad de
memoria necesaria para desplegar una imagen como 640 ∗ 480 ∗ 4 =
1228800 bytes, donde cada pixel se almacena en 4 bytes. Para limpiar
la pantalla, cada pixel será accedido sólo una vez, así que los únicos
aciertos posibles son en accesos a datos precargados en una línea de la
cache, de tamaño 4 bytes.
a) En este caso, acceder al primer elemento r de la estructura de datos pixel es un fallo en la cache y provoca la transferencia de la
estructura completa desde memoria a la cache. Luego, los accesos
a los otros tres elementos de la estructura son aciertos en la cache.
Este patrón se repite para cada pixel accedido, así que la tasa de
aciertos de la cache es 75 %.
b) En este caso, se accede secuencialmente a cada elemento de la memoria por byte. Nuevamente, no hay reutilización de datos excepto en la línea de cache. En este caso, el acceso al primer byte de
la línea es un fallo en la cache, pero los accesos a los tres bytes siguientes son aciertos en la cache. Por ello, la tasa de aciertos es de
75 %.
c) En este caso, se accede secuencialmente a cada elemento de la memoria en unidades de 4 bytes, que es el tamaño de la línea de cache. No hay reutilización de líneas de cache, así que todos los accesos a memoria son fallos en la cache, por lo que la tasa de aciertos
de la cache es de 0 %.
d) La principal componente del tiempo de ejecución de estos códigos
será el tiempo para transferir los datos entre memoria principal y
memoria cache. Aun cuando las dos primeras alternativas tienen
62
Capítulo 6: Sistemas de memoria
tasas de acierto mayores, los tres códigos involucran igual cantidad y frecuencia de transferencias entre memoria principal y memoria cache. La última opción ejecuta menos instrucciones en un
lazo más compacto. Si suponemos que el procesador puede escribir 4 bytes a memoria cache en un mismo ciclo de bus, la última
alternativa bien puede ser la más rápida.
6.7 La siguiente tabla muestra los parámetros de diferentes caches para
un procesador de 32 bits. En dicha tabla, C es el número de bytes de
datos de la cache, B es el tamaño del bloque de cache en bytes, y E es
el número de bloques por conjunto. Calcule S: el numero de vías de la
cache, t: el numero de bits en el tag, i: el número de bits usados como
índice, b: el número de bits usados como desplazamiento en el bloque,
y T : el número total de bits en la cache, incluyendo datos, tag, bit de
validez y de modificación.
C
B
E
1024 4
4
1024 4 256
2048 8
1
2048 8 128
4096 32
1
8192 32
4
S
t
i
b
T
Solución
La tabla se llena usando las fórmulas S =
t = 32 − b − i, y T = 8C + CB (t + 2).
C
B
E
1024 4
4
1024 4 256
2048 8
1
2048 8 128
4096 32
1
8192 32
4
S
64
1
256
2
128
64
t
24
30
21
28
20
21
i
6
0
8
1
7
6
b
2
2
3
3
5
5
C
BE ,
b = log2 (B), i = log2 (S),
T
8192 + 6656 = 14848
8192 + 8192 = 16384
16384 + 5888 = 22272
16384 + 7680 = 24064
32768 + 2816 = 35584
65536 + 5888 = 71424
Capítulo 6: Sistemas de memoria
63
6.8 Se le ha pedido escribir el código para dirimir la elección de alcalde en
la comuna de San Pablo de la Paz, donde hay 7 candidatos a la alcaldía. Ud. desarrolla entonces un programa en C y busca optimizar su
desempeño. El software que Ud. desarrolle se ejecutará en un procesador de 32 bits que posee una memoria cache directa de 1024 bytes, con
bloques de 64 bytes. Suponga además que inicialmente esta cache esta
vacía.
En su código, Ud. utiliza la siguiente estructura de datos
struct Voto {
int candidatos[7];
int valido;
};
struct Voto matrizVotos[16][16];
// Estas variables se almacenan en registros de la CPU
register int i, j, k;
Entonces, Ud. escribe el siguiente código C para inicializar las estructuras de datos a utilizar.
for (i = 0; i < 16; ++i) {
for (j = 0; j < 16; ++j) {
matrizVotos[i][j].valido = 0;
}
}
for (i = 0; i < 16; ++i) {
for (j = 0; j < 16; ++j) {
for (k = 0; k < 7; ++k) {
matrizVotos[i][j].candidatos[k] = 0;
}
}
}
Su colega escribe el siguiente código C para realizar la misma función.
for (i = 0; i < 16; i++) {
for (j = 0; j < 16; j++) {
for (k = 0; k < 7; k++) {
matrizVotos[i][j].candidatos[k] = 0;
}
matrizVotos[i][j].valido = 0;
}
}
Capítulo 6: Sistemas de memoria
64
Su jefe dice que dará un bono de producción al programador cuyo código tenga el menor número de faltas de cache. Suponiendo que el área de
datos de su programa comienza en la posición de memoria 0x4000, que
un dato int ocupa 4 bytes, y que las variables i, j y k están en registros
de la CPU, quién de Uds. dos recibe el bono? Justifique su respuesta
calculando la tasa de aciertos en cada caso.
Solución
Dado que el bloque es de 64 bytes y cada estructura Voto ocupa 32 bytes, entonces un bloque de cache almacena 2 estructuras Voto. En ese
caso, acceder a cualquier miembro del dato matrizVotos[0][0] transfiere las estructuras matrizVotos[0][0] y matrizVotos[0][1] a memoria cache.
En el primer código, el primer conjunto de ciclos realiza 256 accesos
a memoria, de los cuales la mitad son fallas en cache. El segundo conjunto de ciclos realiza 1792 accesos a memoria, de los cuales 1 de cada
14 es una falla en cache, y el resto son aciertos. Entonces, la tasa de
aciertos es 128+1664
= 87.5 %.
2048
En cambio, el segundo código también realiza 2048 accesos a memoria,
de los cuales 1920 corresponden a aciertos en la cache, lo que da una
1920
tasa de aciertos de 2048
= 93.75 %. Por ello, su colega recibe el bono.
6.9 Suponga que el programa palindromo del ejercicio 4.5 se ejecuta utilizando una cache directa unificada de 64 bytes de tamaño, con bloques
de 4 bytes cada uno. Calcule la tasa de aciertos de la cache al ejecutar la
función anterior con los argumentos radar y el largo 5, donde la ristra
a procesar está residente en memoria a partir de la posición de memoria 0x200. Recuerde que la memoria cache es accedida por bytes por la
CPU.
Solución
Analizaremos la ejecución de las instrucciones de la función con los
argumentos radar y el largo 5. Por simplicidad y falta de información,
no consideraremos el efecto de la pila.
Inicialmente, se ejecutan las instrucciones 1 a 15, después se ejecutan
las instrucciones 8 a 15, luego nuevamente las instrucciones 8 a 14,
luego las instrucciones 16, 17 y finalmente las instrucciones 19 a 21.
La ejecución de estas 27 instrucciones involucra leer 74 bytes desde
memoria RAM. A su vez, estos bytes de memoria están almacenados
en 13 líneas de 4 bytes que serán transferidas consecutivamente a los
65
Capítulo 6: Sistemas de memoria
bloques 1 a 13 de la memoria cache. Como la cache inicialmente está
vacía, estas 13 transferencias producen 13 fallos en cache. Entonces, la
ejecución del código produce 13 fallos y 61 aciertos en cache.
Por otro lado, los 5 bytes del argumento se almacenan a partir de la
posición de memoria 0x200. Esto corresponde a 2 líneas de memoria,
las que se transferirán a los bloques 0 y 1 de la memoria cache. Afortunadamente, no hay interferencia en el uso del bloque 1 de la memoria
cache por parte del código. Durante la ejecución, se accede a los bytes
0x200, 0x201, 0x203 y 0x204 una vez, y al byte 0x202 dos veces. Estos
accesos generan 2 fallos y 4 aciertos en cache.
Entonces, podemos decir que, en total, se realizan 80 accesos a cache,
de los cuales 15 son fallos y 65 son aciertos, para una tasa de aciertos
de 81.25 %.
6.10 Intel está diseñando una nueva CPU y quieren consultarle su opinión.
Las alternativas en consideración son: una cache de 32 KiB de arquitectura Harvard dividida en 16 KiB para instrucciones y 16 KiB para
datos, o una cache unificada de 32 KiB. Mediciones realizadas entregan
las siguientes tasas de fallo en función del tamaño de la cache:
Tamaño
Cache de instrucciones
Cache de datos
Cache unificada
16 KiB
32 KiB
0.64 %
0.39 %
6.47 %
4.82 %
2.87 %
1.99 %
Además, se sabe que:
El 75 % de los accesos a memoria son lecturas de instrucciones
El tiempo de acceso a la cache es de 1 ciclo de reloj
El tiempo de acceso a RAM es de 50 ciclos de reloj
La memoria cache es de tipo write-through, con buffers de escritura
La memoria cache de arquitectura Harvard tiene dos puertos de
acceso, lo que permite leer una instrucción y un dato en un ciclo
La memoria cache unificada tiene sólo un puerto de acceso, por lo
que un acceso de lectura ó escritura de datos requiere de 1 ciclo de
reloj adicional.
Dado lo anterior,
Capítulo 6: Sistemas de memoria
66
a) calcule la tasa de fallos para la cache de arquitectura Harvard,
b) calcule el tiempo de acceso promedio para ambas arquitecturas de
cache.
Cuál solución recomienda Ud.?
Solución
El que la memoria cache sea de tipo write-through con buffers de escritura implica que, desde el punto de vista de la CPU, las escrituras a cache
demoran un ciclo, ya que la transferencia de datos entre la cache y la
memoria RAM ocurre bajo el control del buffer.
a) La tasa de fallos para la cache de arquitectura Harvard se puede
calcular como:
(75 % ∗ 0.64 %) + (25 % ∗ 6.47 %) = 2.0975 %
b) El tiempo de acceso promedio de la cache de arquitectura Harvard
es:
1
3
∗ ((1 − 0.64 %) + 0.64 % ∗ 50) + ∗ ((1 − 6.47 %) + 6.47 % ∗ 50) = 2.028
4
4
En cambio, el tiempo de acceso promedio de la cache unificada es:
3
1
∗((1−1.99 %)+1.99 %∗50)+ ∗((1−1.99 %)+1+1.99 %∗50) = 2.225
4
4
Por lo tanto, la cache de arquitectura Harvard tiene un desempeño
superior.
Capítulo
Planificación de procesos
7.1 En un centro de cómputo se desea ejecutar las siguientes tareas:
Nombre Prioridad Duración TLlegada
A
B
C
D
E
F
4
8
1
3
2
5
4
3
8
2
6
5
0
2
3
5
6
8
Para cada uno de los siguientes algoritmos de planificación de procesos,
determine el tiempo medio de retorno Tr , el tiempo medio de espera Tw
y el largo promedio de la cola de procesos en espera L.
Shortest Process Next (SPN)
Shortest Remaining Time (SRT)
Highest Response Ratio Next (HRRN)
Planificación via prioridades
Round Robin, con q = 2
Nótese que los algoritmos de planificación HRRN y SPN no son expropiativos, mientras que SRT, Round Robin y planificación por prioridades si lo son. Además, considere que, a menor valor, mayor es la
prioridad.
67
7
68
Capítulo 7: Planificación de procesos
En caso de ser necesario, suponga que la cola de procesos en espera es
una cola FIFO que, en caso de coincidencia, da prioridad a los procesos
nuevos por sobre los procesos expulsados por la CPU.
Solución
La siguiente tabla muestra los parámetros solicitados para los algoritmos de planificación dados.
Algoritmo
Tr
Tw
Shortest Process Next (SPN)
9.6̄ 5.0
Shortest Remaining Time (SRT)
9.6̄ 5.0
Highest Response Ratio Next (HRRN) 10.6̄ 6.0
Planificación via prioridades
16.0 11.3̄
Round Robin, con q = 2
13.5 8.83̄
L
1.071
1.071
1.285
2.428
1.892
Descargar