ASD - Arquitectura de Sistemas Distribuidos PDF
ASD - Arquitectura de Sistemas Distribuidos PDF
ASD - Arquitectura de Sistemas Distribuidos PDF
Arquitectura de
Sistemas Distribuidos
Departamento de Arquitectura y Tecnología de Computadores
Visión “clásica”: Arquitecturas basadas en el juego de instrucciones [ISA, Instruction Set Architecture]:
- CISC (Complex Instruction Set Computer) Computador con un conjunto de instrucciones complejas.
- RISC (Reduced Instruction Set Computer) Computador con un conjunto reducido de instrucciones,
posteriormente llamado de instrucciones sencillas.
A partir de aquí construimos una arquitectura dependiendo del problema y de la máquina de destino.
Aparece la GPU (arquitectura creada para el tratamiento de vídeos, fotos…) optimizada con librerías, de
forma que el procesador se libera de realizar dichas tareas, por tanto, se maximiza el rendimiento pero
a base de restricciones de coste, potencia, disponibilidad, herramientas software, etc.
A debate:
- ¿Interesa dominar todos los tipos de arquitectura? O por el contrario ¿interesa concentrarse en la
arquitectura actual de más prestaciones o rendimiento?
1
1.2 Leyes y principios de Arquitectura.
La Ley de Moore expresa que «aproximadamente cada dos años se duplica el número de
transistores en un circuito integrado». Esto es, entre un 40% y un 55% cada año; luego se
cuadriplica el número de transistores de un chip cada tres años. Este incremento está
apoyado en las evoluciones de las arquitecturas. Además, se pueden añadir más niveles de
caché, mayores algoritmos, predictores de salto…
Gordon Moore
Sabiendo que:
A1 = A0 ∙ r
A2 = A1 ∙ r y=a+xb
...
An = A0 ∙ rn
La importancia de la jerarquía de memoria ha aumentado con los avances en el rendimiento de los procesadores.
En la figura anterior, se muestra el rendimiento del procesador respecto a la mejora histórica en cuanto al tiempo
de acceso a la memoria principal. La línea de los procesadores muestra el aumento de las solicitudes promedio
de memoria por segundo (es decir, la inversa de la latencia entre referencias de memoria), mientras que la línea
de la memoria muestra el aumento de accesos en la DRAM por segundo (es decir, la inversa de la latencia de
acceso DRAM). La situación en los monoprocesadores es realmente un poco peor, ya que la velocidad pico de
acceso a memoria es más rápido que la tasa media, que es lo que se representa.
A partir de 1980 el rendimiento es la línea de base, la diferencia en el rendimiento, medido como la diferencia
en el tiempo entre las peticiones de memoria del procesador (para un solo procesador o núcleo) y la latencia de
un acceso DRAM, se representan gráficamente en el tiempo. Hay que tener en cuenta que el eje vertical debe
ser en una escala logarítmica para representar el tamaño de la diferencia de rendimiento procesador - DRAM. La
línea de base es de 64 KB de memoria DRAM en 1980, con una mejora del rendimiento de 1’07 por año en la
latencia. La línea de procesadores supone una mejora del 1’25 por año hasta 1986, una mejora de 1’52 hasta el
año 2000, una mejora de 1’20 entre 2000 y 2005, y ya, ningún cambio en el rendimiento del procesador (en
función de cada núcleo), entre 2005 y 2010.
2
Miguel Ángel Cifredo
macifredo@gmail.com
Capacidad Memoria Flash: 50% - 60% / año (15x-20x más barato por bit que la memoria DRAM)
Multiprocesadores
CISC
RISC
3
1.2.2 Ley de Amdahl
Amdahl pretende averiguar la mejora máxima global de un sistema cuando sólo una parte
de éste es mejorado. La Ley de Amdahl establece que:
T ejecución inicial
1-F F
1-F F/Aceparcial
T ejecución final
Normalizamos:
Tejecución inicial = 1, donde 0 ≤ F ≤ 1,
Tejecución inicial 1
F Aceleraciónparcial = =
Tejecución final F
Tejecución final = (1 - F) + (1 - F) +
Aceleraciónparcial } Aceleraciónparcial
4
Miguel Ángel Cifredo
macifredo@gmail.com
Llegamos a la conclusión de que no se puede alcanzar el paralelismo ideal. Tan sólo una porción σ del programa
puede ser ejecutada en paralelo.
1 1
Si p>> entonces Speedup → Valor de la asíntota = (1 - σ) +
σ = 1−σ
= (1 − σ)−1 .
∞
Ejercicio: Supongamos que consigo paralelizar el 50% de un programa, de forma que se puede ejecutar en 10
procesadores a la vez (obteniendo una aceleración parcial de 10). ¿Cuál sería la aceleración total?
1
Aceleración = = 1'81̂
0'50
(1 - 0'50) +
10
El resultado indica que la máquina paralela es un 81% más rápida que la máquina secuencial.
Nota: La aceleración debe ser siempre mayor o igual a 1. Si fuera menor, significaría desaceleración.
Ejercicio: Suponer que estamos considerando una mejora que corra 8 veces más rápida que la máquina original,
pero sólo es utilizable el 40% del tiempo. ¿Cuál es la aceleración global lograda al incorporar la mejora?
Aceleración mejorada = 8
1
Aceleración global = 0'40 = 1'53
(1 - 0'40) +
8
La Ley de Amdahl da lugar a la Ley de Rendimientos Decrecientes: «La mejora incremental en la aceleración
conseguida por una mejora adicional en el rendimiento de una parte del cálculo disminuye tal como se van
añadiendo mejoras». Un corolario importante de la Ley de Amdahl es que si una mejora sólo es utilizable por
una fracción de una tarea, no podemos aumentar la velocidad de la tarea más que el recíproco de 1 menos esa
fracción.
Un error común al aplicar la Ley de Amdahl es confundir “fracción de tiempo convertido para utilizar una mejora”
y “fracción de tiempo después de utilizar la mejora”. Si, en lugar de medir el tiempo que podría utilizar la mejora
en un cálculo, midiésemos el tiempo después de utilizar la mejora, los resultados serían incorrectos.
La Ley de Amdahl puede servir como guía para determinar cuánto una mejora aumenta el rendimiento y cómo
distribuir los recursos para mejorar la relación costo/rendimiento. El objetivo, claramente, es emplear recursos
de forma proporcional al tiempo que se requiere en cada parte.
5
Entonces, ¿cómo podemos mejorar la aceleración?
Supongamos que queremos obtener una aceleración de valor 10 utilizando 80 máquinas. ¿Qué porcentaje del
código tendremos que optimizar?
1
Atotal = Fragmento = 10
(1 - Fragmento) +
80
Fragmento 10 Fragmento
1 = 10 ∙ ((1 - Fragmento) + ) ⇒ 10 - 10 Fragmento + = 1 ⇒ Fragmento = 0'91
80 80
Por tanto, tenemos que paralelizar el 91 % del código para aumentar la aceleración un valor de 10.
Se trata de una regla empírica que indica que « un procesador emplea el 90% del tiempo en la ejecución del 10%
del código ». Es importante así, el orden de los accesos a memoria. Por tanto, si conseguimos que ese 10% de
tiempo esté lo más cerca posible del procesador, aumentamos en un 90% la optimización. Esta regla se puede
plantear como el Principio de Localidad (todos los programas favorecen una parte de su espacio de direcciones
en cualquier instante de tiempo). Tiene dos dimensiones: Localidad temporal (si se referencia a un elemento,
tenderá a ser referenciado pronto) y Localidad espacial (Si se referencia a un elemento, los elementos cercanos
a él tenderán a ser referenciados).
De esta forma, aparecen las cachés de distintos niveles por lo que el procesador tiene poco tiempo de espera.
Ejercicio. Teniendo en cuenta la Ley de Amdahl y la del Principio de Localidad 10/90, si se quiere optimizar un
código, conviene más concentrarse en los bucles que realicen muchas iteraciones.
MR = 1/4 MR = 1
Como el tamaño de la memoria caché suele ser menor que el tamaño del vector, no se puede guardar en ella tal
cantidad de datos.
6
Miguel Ángel Cifredo
macifredo@gmail.com
Ejercicio:
Section A: 180 líneas, que es el 90% del código pero supone el 10% del tiempo de ejecución.
Section B: 20 líneas, que es el 10% del código pero supone el 90% del tiempo de ejecución.
Mejora de 90 en A:
T 1
→ = 1'11
1 1
( )
90 TA + TB 90 ∙ 0'10 + 0'90
Mejora de 10 en B:
T 1
1 → 1 = 5'26
TA + 10 ∙TB 0'10 + (10) ∙ 0'90
7
1.3 Rendimiento y productividad.
Productividad (throughput): Número de tareas que puede realizar una máquina en un segundo. Duplicar
el número de máquinas implica duplicar la rentabilidad, pero cada tarea sigue tardando el mismo tiempo.
Rendimiento (performance): Inversa del tiempo que se tarda en realizar una tarea. No es un factor fácil
de aumentar.
1
Rendimiento =
Tiempoejecución tarea
𝐶𝑃𝑈𝑐𝑖𝑐𝑙𝑜𝑠
𝐶𝑃𝑈𝑡𝑖𝑒𝑚𝑝𝑜 =
𝐹𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎 𝑑𝑒𝑙 𝑟𝑒𝑙𝑜𝑗
𝐶𝑃𝑈𝑐𝑖𝑐𝑙𝑜𝑠
𝐶𝑃𝐼 =
𝑁º 𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠
1
𝑅𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜 =
𝑡𝑒𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛
1
𝑇𝑒𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛,𝑎𝑛𝑡𝑒𝑠 𝑅𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜2 𝑎 ≥ 1. 𝑥
𝑅𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜1
𝐴𝑐𝑒𝑙𝑒𝑟𝑎𝑐𝑖ó𝑛 = = = ⇒ [
𝑇𝑒𝑗𝑒𝑐𝑢𝑐𝑖𝑜𝑛,𝑑𝑒𝑠𝑝𝑢é𝑠 1 𝑅𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜1 𝑥% 𝑚á𝑠 𝑟á𝑝𝑖𝑑𝑎 𝑙𝑎 𝑚á𝑞𝑢𝑖𝑛𝑎 𝑑𝑒𝑙 𝑐𝑜𝑐𝑖𝑒𝑛𝑡𝑒
𝑅𝑒𝑛𝑑𝑖𝑚𝑖𝑒𝑛𝑡𝑜2
8
Miguel Ángel Cifredo
macifredo@gmail.com
Actualmente casi se consigue el límite del flujo de datos (Data Flow Limit). Los dos factores que más nos limitan
son: la predicción de saltos y los accesos a la memoria caché de datos.
Los CPIBloqueo son tiempos en los que el procesador no realiza nada. Sólo “se ven” las dependencias reales a
los largo del tiempo (pipeline) de mayor transcendencia, las más simples se “enmascaran”. Veamos un
ejemplo de enmascaramiento:
El CPIEstructural es el menos común, generalmente porque hay pocas ALUs (UF Unidades Funcionales).
# Fibonacci
c = 0; b = 1;
for ( i = 2; i < N ; i++){
a = b + c;
c = b; Son operadores aritméticos de tipo entero (ADD, MOVE, MOVE)
b = a;
}
El CPIControl hace referencia a la gestión del PC (Program Control) con las instrucciones JUMP y BRANCH. Hoy en
día existen predictores de salto de forma que, si acierta entonces no hay bloqueo. Este acierto suele
producirse en bucles grandes, donde siempre se acierta salvo en la última iteración.
El CPIMemoria hace referencia a los fallos de caché (L1, L2, L3, RAM, HD,…) debido a que los datos grandes no
caben en la memoria caché de nivel 1 (L1). Se trata de reducir los fallos de memoria caché L1.
Veamos un ejemplo para recorrer una matriz, que resolvemos de 2 formas. Debemos tener presente que los
datos estructurados se almacenan en memoria en forma de lista continua, esto es, que nuestra
representación bidimensional está realmente almacenada como una única sucesión de sus distintas filas. Por
lo que si recorremos cada uno de los vectores aprovechamos el principio de localidad.
# 1ª Forma # 2ª Forma
for ( f = 0; f < N ; f++){ for ( c = 0; c < N ; c++){
for ( c = 0; c < N ; c++){ for ( f = 0; f < N ; f++){
a[f][c] = 0; a[c][f] = 0;
} }
# Mejor solución (90% de ejecución)
9
¿Qué factores limitan más a una máquina secuencial?
Con un buen predictor se puede analizar un código el código de forma general, si en nuestro caso se descubre
que se toma un 70% de los saltos y en el otro 30% restante no se toma, es lógico, pues en un bucle se toman
todos los saltos excepto el último. Si el salto es tomado, lo será casi siempre y viceversa. Así, por la Ley de Amdahl,
debemos optimizar el caso común, el 70%.
Para conseguir el rendimiento óptimo de una instrucción por ciclo, otro de los obstáculos que nos encontramos
es el de las dependencias de control, esencialmente, los saltos. Para lograrlo contamos con elementos que nos
permite la gestión de los saltos tomados y no tomados, esto son:
- BPB (Buffer Predictor Branch, Buffer de predicción de saltos): Cada salto va a llevar un historial de
comportamientos mediante una máquina de estados.
- BTB (Buffer Target Branch, Buffer de destinos de saltos): Se le añade la dirección de destino a la que va
a ser tomado. El porcentaje de acierto del BTB es de un 97%, por lo que estamos ahorrando muchos
ciclos de bloqueo.
BPB BTB
10
Miguel Ángel Cifredo
macifredo@gmail.com
Hay magnitudes muy usadas pero no son exactas e independientes del software y del sistema operativo, como:
o MIPS: Millones de instrucciones por segundo. Esta magnitud se propuso cuando las instrucciones eran
sencillas (instrucciones enteras). Por ejemplo, para el cálculo de una función trigonométrica (ej. Sen(α)),
se traducía esta función compleja en otras funciones más sencillas. Cuando llegaron los procesadores
capaces de ejecutar las instrucciones complejas, hacían lo mismo pero con menos instrucciones en
ensamblador. Por tanto, su MIPS es menor (tiene menos instrucciones) pero se ejecuta más rápido.
𝑁º 𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 𝑁º 𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 𝑓𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎
𝑀𝐼𝑃𝑆 = = 6 =
10 ∙ 𝑇𝑖𝑒𝑚𝑝𝑜𝐸𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛 10 ∙ (𝑁º 𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 ∙ 𝐶𝑃𝐼 ∙ 𝑃𝑒𝑟𝑖𝑜𝑑𝑜 )
6 106 ∙ 𝐶𝑃𝐼
o MFLOPS (GFLOPS) Millones (Gigas) de operaciones en coma flotante por segundos (Flotating point
Operations Per Second). Se usan en código científico y son sólo con los operadores + - * / y otras
transcendentes. El problema que tienen es que dependen del juego de instrucciones que se estén
ejecutando en el código: las instrucciones enteras tardan menos tiempo de ejecución que las
instrucciones en punto flotante.
𝑁ú𝑚𝑒𝑟𝑜 𝑑𝑒 𝑜𝑝𝑒𝑟𝑎𝑐𝑖𝑜𝑛𝑒𝑠 𝑒𝑛 𝑐𝑜𝑚𝑎 𝑓𝑙𝑜𝑡𝑎𝑛𝑡𝑒 𝑟𝑒𝑎𝑙𝑖𝑧𝑎𝑑𝑎𝑠
𝑀𝐹𝐿𝑂𝑃𝑆 =
106 ∙ 𝑇𝑖𝑒𝑚𝑝𝑜𝐸𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛 (𝑠)
Por otro lado, hay mejores medidas como los valores medios geométricos de un conjunto determinado de
programas (colección benchmarks).
𝑁
𝑛
𝑀𝑒𝑑𝑖𝑎 𝑔𝑒𝑜𝑚é𝑡𝑟𝑖𝑐𝑎: 𝐸̅ = √∏ 𝑡𝑖
𝑖=1
𝑡𝑦
Tienen la siguiente propiedad: 𝐴𝑦𝑥 = 𝑡 Si lo dividimos por el tiempo de referencia de una tercera
𝑥
máquina (histórica o bien tabulada por el SPEC), entonces con el valor obtenido se puede calcular la
aceleración de la nueva con respecto a la de referencia. Por tanto,
𝑡𝑦1
𝑁
𝑛 ⁄𝑡
̅̅̅̅̅ 𝑖
𝐴𝑦𝑥 = √∏ 𝑡
𝑥1
𝑖=1 ⁄𝑡
𝑖
Estas medidas de rendimiento se utilizan dentro de un benchmark concreto, en el que se conocen todas las
opciones de compilación, ISA, … y tomando como referencia una máquina de la era actual a la que evaluamos.
Sin embargo, el problema que se le puede encontrar es que hay máquinas que tienen implementado benchmarks
en hardware, por lo que se ejecuta más rápido en hardware.
11
1.5 Tipos de paralelismo.
Actualmente el paralelismo es casi la única forma de aumentar el rendimiento y/o la productividad. El proceso
de implementación del paralelismo implica procesar una instrucción que incluye un número de operaciones más
simples. Dichas operaciones se organizan en una secuencia de etapas.
12
Miguel Ángel Cifredo
macifredo@gmail.com
Se incrementa la productividad (throughput); esto es, el número de instrucciones ejecutadas por segundo.
Se incrementa la latencia; esto es, el tiempo requerido para ejecutar una instrucción individualmente.
Se producen retrasos adicionales debido a: Registros de la cadena y División no uniforme de las etapas.
13
Hoy en día estamos cercanos al límite del flujo de datos (Data Flow Limit).
¿Qué dos factores limitan más el paralelismo ILP extraíble en benchmarks reales?
– Fallos en la predicción de saltos: La BTB es cada vez más sofisticada, compleja y grande.
– Accesos a memoria caché de datos: Aunque tiene un ancho de banda (AB MEM) altísimo, la latencia de
acceso decrece poco, y tiene muchos accesos.
Se plantean dos enfoques de planificación con el objetivo de reducir el tiempo de ejecución de un programa en
una máquina encadenada:
Para reducir el tiempo de ejecución en una máquina encadenada deberemos reducir uno de los operandos de la
siguiente ecuación: CPU = NºInstrucciones x Ciclos_por_instrucción x Ciclo_reloj.
o Reducir el número de instrucciones. Procesadores LIW o VLIW (Very Long Instruction Word). Se trata de
macroinstrucciones con un paralelismo explícito, esto es, preparadas contener varias operaciones dentro
de un bloque. Utiliza técnicas de planificación estáticas, lo que supone gran dependencia del compilador.
o Reducir los ciclos por instrucción (CPI de los superescalares). Ejecutan varias
instrucciones por ciclo. Utilizan técnicas dinámicas, lo que supone una mayor
complejidad del hardware.
o Reducir т (ciclo del reloj). Procesadores superpipeline, supersegmentado o superencadenado. Por tanto,
la variación de la velocidad del reloj hará que tenga más etapas en la cadena y mayor consumo de energía
o potencia.
14
Miguel Ángel Cifredo
macifredo@gmail.com
Procesadores para sistemas empotrados: VLIW o superescalares sencillos para DSP, PDAs, smartphones…
- ARM v7 Cortex.
- OMAP3 processors (Texas Instrument).
- MSC81xx (Freescale) multi-core DSP.
Esta tabla muestra las características de cuatro procesadores IBM Power. Todos salvo el Power6 tuvieron
planificación dinámica, el cual la tuvo estática, y por tanto, todos los predecesores mantuvieron dos pipelines de
carga y almacenamiento. El Power6 tiene las mismas unidades funcionales que el Power5 salvo por una unidad
decimal. El Power7 utiliza DRAM para el nivel L3 de caché.
15
1.5.2 Paralelismo a nivel de datos (DLP).
– Es difícil de vectorizar.
– El juego de instrucciones cambia cada varios años, lo que conlleva al cambio de librerías y cambio en el
proceso de vectorización.
16
Miguel Ángel Cifredo
macifredo@gmail.com
Esquema de multiprocesador:
17
1.5.4 Paralelismo a nivel de peticiones (RLP)
Suele darse en grandes servidores, con mucho paralelismo de datos donde se producen cientos de peticiones
independientes por segundo. Por lo que una misma petición externa suele dividirse en varias peticiones.
Cuello de botella
(tamaño de los buses)
- Balanceo de carga.
- Virtualización.
- Alta disponibilidad.
- Reducción de costes.
- Reducción del consumo energético.
18
Miguel Ángel Cifredo
macifredo@gmail.com
El cambio a varios procesadores por chip, alrededor de 2005, no vino de algún avance que simplificara
enormemente la programación paralela o del hecho de que fuera fácil el construir ordenadores multinúcleo. El
cambio se produjo porque no había otra opción debido a la imposibilidad de seguir incrementando el ILP.
El incremento en el rendimiento es ahora una tarea del programador. La era en la que los programadores
dependían de los diseñadores de hardware para acelerar sus programas, sin mover un dedo ha terminado.
Si los programadores quieren que sus programas vayan más rápidos en cada generación, tienen que hacer sus
programas más paralelos.
La versión popular de ley del incremento del rendimiento de Moore, en cada generación de la tecnología, ahora
depende de los programadores.
Control de Instrucciones:
Unidad de ejecución:
19
1.6 Data-Flow.
Ejemplo:
for (i = 0 ; i < limit ; i++)
{
acc = acc * x[i];
}
La figura superior es una representación gráfica del código del bucle interno. Las
instrucciones se mueven dinámicamente en una o dos instrucciones, cada una de
las cuales recibe los valores de las otras operaciones o de los registros y producen
valores para otras operaciones y registros. Se muestra el objetivo de la instrucción
final de la etiqueta loop, la cual salta a la primera instrucción mostrada.
Dado que la multiplicación FP de simple precisión tiene una latencia (p.e. 4 ciclos)
mayor que la de la suma entera (p.e. 1 ciclo), la cadena de la izquierda tendrá un
camino crítico, que requiere 4∙n ciclos para ejecutarse.
20
Miguel Ángel Cifredo
macifredo@gmail.com
.L488:
LOAD %rax, x(%rdx*4) ; Carga x[i]
MUL %xmm0, %rax, %xmm0 ; Multiplica acc por x[i]
LOAD %rax, x((%rdx+1)*4) ; Carga x[i+1]
MUL %xmm0, %rax, %xmm0 ; Multiplica acc por x[i+1]
ADD $2, %rdx ; Incrementa el índice en i
CMP %rdx, %rbp ; Compara el límite i
JG .L488 ; Si >, ir al bucle
La figura superior es una representación gráfica del código del bucle interno. Cada
iteración tiene dos instrucciones mul, cada una de las cuales son enviadas y cargadas (load)
en una operación mul.
Dado que el camino crítico era el factor limitante para el rendimiento del código sin el
desenrollado del ciclo, sigue siendo así con el desenrollado.
21
1.6.3 Data-Flow. Desenrollado de bucles con múltiples acumuladores.
.L488:
LOAD %rax, x(%rdx*4) ; Carga x[i]
MUL %xmm0, %rax, %xmm0 ; Multiplica acc0 por x[i]
LOAD %rax, x((%rdx+1)*4) ; Carga x[i+1]
MUL %xmm1, %rax, %xmm1 ; Multiplica acc1 por x[i+1]
ADD $2, %rdx ; Incrementa el índice i
CMP %rdx, %rbp ; Compara el límite i
JG .L488 ; Si >, ir al bucle
En la figura superior hay que notar que no hay dependencia entre las dos
operaciones mul.
Observamos que cada uno de dichos caminos críticos, uno para los elementos
pares (acc0) y otro para los impares (acc1).
22
Miguel Ángel Cifredo
macifredo@gmail.com
.L488:
LOAD %rax0, x(%rdx*4) ; Carga x[i]
LOAD %rax1, x((%rdx+1)*4) ; Carga x[i+1]
MUL %rax0, %rax0, %rax1 ; Multiplica x[i] por x[i+1]
MUL %xmm1, %rax0, %xmm1 ; Multiplica por acc
ADD $2, %rdx ; Incrementa el índice i
CMP %rdx, %rbp ; Compara el límite i
JG .L488 ; Si >, ir al bucle
La primera multiplicación dentro de cada iteración puede ser realizada sin tener
que esperar al valor acumulado de la iteración anterior.
23
Ejercicio propuesto 1:
El programa invierte los diferentes tonos de grises (255 tonos) mediante la diferencia al complementario.
Nos centraremos en el del bucle interno por ser la parte del proceso más común, suponiendo que las
operaciones de acceso a memoria requieren 3 ciclos y las operaciones aritméticas de 1 único ciclo.
c) Hallar el número de instrucciones mínimo, por iteración, suponiendo que img[f][c] está apuntando a
un registro base. Usar las instrucciones: LOAD, STORE, SUB, ADD, CMP, MOVE0, BRANCH. ¿Cuál de las
instrucciones que se han indicado sobra?
24
Miguel Ángel Cifredo
macifredo@gmail.com
Ejercicio propuesto 2:
Supongamos que se realiza una mejora que favorece un determinado modo de ejecución en un factor de 10.
Dicho modo mejorado es usado en un 50% del tiempo medido una vez que el modo de ejecución es aplicado
(nótese que el caso es distinto a la definición habitual). Calcular la aceleración obtenida y el porcentaje de tiempo
de ejecución original convertido a modo rápido.
Consideremos un cambio en el juego de instrucciones de una máquina load/store de la que se dispone de las
siguientes estadísticas:
Supongamos que un 25% de las operaciones tipo ALU usan un operando que no volverá a usarse. Proponemos
añadir instrucciones ALU con un operando fuente en memoria. Estas instrucciones “registro-memoria” duran 2
ciclos. Este nuevo conjunto de instrucciones extendido incrementa la duración de los saltos a 3 ciclos, aunque
no afecta a la duración del ciclo de reloj. ¿Mejoraría esto las prestaciones de la CPU?
Por otro lado, el benchmark “Whetstone” contiene un total de 195.578 operaciones en punto flotante. Se puede
ejecutar sobre un sistema empotrado de bajo consumo (basado en un Motorola 68020 a 16’67 MHz con
coprocesador externo), de dos formas, usando un compilador, que permite usar rutinas software para emular
las operaciones en punto flotante, en lugar de usar el coprocesador.
Con software:
- Duración de una iteración = 13’6 segundos.
- CPI = 6.
Con el coprocesador:
- Duración de una iteración = 1’08 segundos.
- CPI = 10.
Se pide:
d) De media, ¿Cuántas operaciones enteras son necesarias para realizar cada operación en punto flotante
en software?
25
Solución:
A partir de las estadísticas descritas en el enunciado, con el nuevo diseño aparecen una instrucción nueva, al
fusionar una Load y una ALU: Por ejemplo para una máquina de tres operandos:
Es decir, con esta forma, por cada 2 instrucciones de este tipo, se crea una única tipo ALU fuente, decreciendo el
número de instrucciones totales. Como en el 25% de las ALU se puede hacer esto tenemos una frecuencia del
43%, por tanto, 0’43 ∙ 0’25 ≈ 0’11, lo que significa que habría 11 instrucciones serian ALU fuente (desapareciendo
11 ALUs y 11 Loads). Para el nuevo diseño hay que tener en cuenta que la frecuencia de instrucciones cambia,
en concreto aparece un nuevo tipo de instrucción y desaparecen algunas ALU y Load. Se resume el cambio en la
siguiente tabla, donde en la segunda columna se expresa en qué se convertirían las 100 instrucciones de la
máquina anterior.
El CPI medio de la máquina original se calcula ponderando los CPI medios de cada una de las instrucciones:
𝑇𝑖𝑝𝑜𝑠 𝑖𝑛𝑠𝑡𝑟
𝑐𝑖𝑐𝑙𝑜𝑠
𝐶𝑃𝐼𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 = ∑ 𝐹𝑗 𝐶𝑃𝐼𝑗 = (0′ 43 ∙ 1′ 0) + (0′ 57 ∙ 2′ 0) = 1′ 57
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖ó𝑛
𝑗=1
El CPI medio de la máquina nueva se calcula igualmente así (cuidado con los porcentajes):
𝑇𝑖𝑝𝑜𝑠 𝑖𝑛𝑠𝑡𝑟
100 𝑐𝑖𝑐𝑙𝑜𝑠
𝐶𝑃𝐼𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 = ∑ 𝐹𝑗 𝐶𝑃𝐼𝑗 = [(0′ 32 ∙ 1′ 0) + (0′ 24 ∙ 3′ 0) + (0′ 33 ∙ 2′ 0)] ∙ = 1′ 91
89 𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖ó𝑛
𝑗=1
Para calcular la aceleración entre los dos procesadores, hemos de tener en cuenta los cambios en las CPI y
también en el número de instrucciones, ya que en el diseño nuevo la cantidad de instrucciones disminuye (100
instrucciones originales se convierten en 89):
𝑡𝑒𝑗𝑒𝑐. 𝑛𝑢𝑒𝑣𝑜 𝑇 ∙ 𝑁𝑖𝑛𝑠𝑡𝑟. 𝑛𝑢𝑒𝑣𝑎𝑠 ∙ 𝐶𝑃𝐼𝑛𝑢𝑒𝑣𝑜 𝑇 ∙ 89 ∙ 1′91 170
𝐴 = = = = = 1′ 08 > 1
𝑡𝑒𝑗𝑒𝑐. 𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 𝑇 ∙ 𝑁𝑖𝑛𝑠𝑡𝑟. 𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 ∙ 𝐶𝑃𝐼𝑜𝑟𝑖𝑔𝑖𝑛𝑎𝑙 𝑇 ∙ 100 ∙ 1′57 157
Podemos concluir con indicando que el nuevo diseño es aproximadamente un 8% más lento que el original.
26
Miguel Ángel Cifredo
macifredo@gmail.com
1.7 Ejercicios.
2. Las tarjetas de red antiguas enviaban o recibían datos a una velocidad de 100 Mbit/s. ¿Cuántos años
aproximadamente tardaron en hacerlo a 1 Gbit/s, si el ritmo anual de progreso ha sido del exactamente del
100%? 13’0 años, 0’5 años, 1’1 años, 3’4 años.
3. Una tarjeta de red trasmite información a 1 Gb/s ¿Cuánto tiempo tardará aprox. en enviarse 125 KB? Pista:
al igual que v = espacio/tiempo , usar: v = 1 Gb/s, espacio = 125 KB
a) 125 KB = 125/8 Kb = 15’6 Kb. Como 1 Gb= 106 Kb, tardará 15’6/ 106 s, es decir 15’6 microsegundos.
b) 125 KB = 8 * 125 Kb = 1 Mb. Luego tardará 1000 s, pues hay que dividir la velocidad por el tamaño,
es decir (1 Gb/s) / 1 Mb
d) 125 KB = 8 * 125 Kb = 1 Mb. Luego tardará 1 ms, ya que 1 Mb es la milésima de 1 Gb. O bien, si se
divide el tamaño por la velocidad tenemos: 1 Mb / (1 Gb/s) = 0’001 s
4. ¿Cuántos años se tardará en procesar 10 GFLOPS en un PC? Datos: progresión rendimiento anual: 50%. Hoy
en día es de 3 GFLOPs
5. Si una caché está vacía y sólo se sabe que tiene 64 bytes por línea, ¿se puede calcular el MR (razón de fallos)
para este código? Si la respuesta es no, ¿por qué? Si la respuesta es afirmativa ¿cuánto valdrá?
double x[8123];
for (i=0; i< 4096; i++) x[i] = 10.0;
6. Si una caché está vacía y sólo se sabe que tiene 32 Bytes por línea, ¿Cuál es el MR (razón de fallos) si se
recorre un vector para leer todos sus elementos, por ejemplo así:
double x[32*N];
for (i=0; i< 32*N; i++) a += x[i];
8. ¿Qué tamaño completo en Bytes tendrá esta estructura (sumar Bytes de cada campo)?
struct {
float f;
char c[4];
int i[2];
double d;
};
27
10. ¿Cuál de estos programas tiene más localidad de acceso a datos (ambos hacen lo mismo)?
for (f=0; f<m; f++)
for (c=0; c<m; c++)
a[f][c] = a[f][c] + b[f][c];
- El compilador la realiza.
- El procesador la realiza en tiempo de ejecución.
- El procesador la realiza antes de ejecutar.
- La jerarquía de memoria se encarga de ella.
- Varía a lo largo de los años.
- El compilador la realiza.
- El procesador la realiza en tiempo de ejecución.
- El procesador la realiza antes de ejecutar.
- La jerarquía de memoria se encarga de ella.
- Varía a lo largo de los años.
15. ¿Por qué interesa medir el rendimiento con una colección de programas?
17. Si dos máquinas son compatibles (ejemplo AMD e Intel) y usan la misma frecuencia, ¿es cierta esta fórmula
de aceleración para una aplicación? A = CPIIntel / CPIAMD
18. El primer bucle tiene un CPI de 0’4, pero es segundo de 5’0. SI ambos tienen el mismo número de
instrucciones y la frecuencia de CPU es la misma ¿Por qué tiene más GFLOPS el segundo?
float x[], y[], z[];
for (i=0; i<1024; i++)
if (x[i] == y[i])
x[i] = z[i];
for (i=0; i<1024; i++)
x[i] = x[i] * y[i] / z[i];
21. ¿Qué propiedad tienen las medias geométricas para calcular la aceleración para una colección de programas
entre dos máquinas?
22. ¿Cuál es el número de instrucciones en vuelo para un superescalar de 15 etapas y que ejecuta 4 instrucciones
a la vez?
24. ¿Cuáles son las principales causas de bloqueo en procesadores GPP actualmente?
28
Miguel Ángel Cifredo
macifredo@gmail.com
Nuestro objetivo es eliminar riesgos reduciendo los bloqueos. Típicamente se pueden reducir los bloqueos de
datos, ya que los bloqueos estructurales dependen del hardware.
Tenemos que recordar que las dependencias no reales o ficticias las veíamos como, por un lado
antidependencias, WAR (write after write) j escribe un registro que i lee donde el orden inicial (i antes que j)
debe ser preservado. Por otro lado, dependencias de salida WAW (write after write), i y j escriben en el mismo
registro donde igualmente el orden debe ser preservado. Estas dependencias se pueden evitar mediante técnica
de renombrado de los registros. Luego es importante tener un conjunto amplio de registros para poder
renombrar y reordenar (ventaja RISC).
Sin embargo, las dependencias reales RAW (read after write) no pueden eliminarse, son inherentes al algoritmo
(habrá algoritmos con más RAW y otros con menos). Eliminarlas no es tema relativo a la Arquitectura sino a la
Algorítmica.
Por tanto, realmente un programa podría escribirse atendiendo exclusivamente a las dependencias reales, sin
ser necesarios en un primer momento los registros, claro que cuando se diseña un procesador como máquina
síncrona, aparecen obligatoriamente los registros como el estado de la máquina almacenado tras cada periodo.
Nuestro objetivo será intentar que el programa se ejecute como el grafo siguiente:
29
En un encadenamiento básico, un bloqueo por cualquier tipo de dependencia lo hace totalmente sobre la cadena
(pipelining), lo que aumenta el CPI de la máquina. Luego, reordenando el código, podemos eliminar ciertos
bloqueos, típicamente los de acceso a los datos.
Pero existe un límite, en la aceleración alcanzable, el límite de CPI (Data Flow Limit, límite del flujo de datos),
que es en sí un límite de las prestaciones de la máquina, donde sólo las etapas EX y MEM son relevantes y las
etapas IF, ID y WB se realizan en paralelo. Los procesadores actuales han llegado prácticamente a ese límite.
Por ejemplo, supongamos que las fases “accesorias” de una instrucción (en las que la instrucción se busca, se
decodifica, etc.) duran cero ciclos, y que únicamente duran un tiempo apreciable las operaciones, entre ellas, el
acceso a la memoria. El grafo de dependencias se convertiría en el siguiente cronograma (sólo fases EX, MEM):
30
Miguel Ángel Cifredo
macifredo@gmail.com
La planificación dinámica consiste en reordenar la ejecución de las instrucciones del código de un programa para
reducir los bloqueos mientras se mantiene el flujo de datos (pipelining), eliminando las dependencias ficticias
tanto WAR como WAW.
El compilador no necesita conocer la microarquitectura.
Ventajas: {
Maneja casos donde las dependencias no se conocen en tiempo de compilación.
Incremento considerable en la complejidad del hardware.
Inconvenientes: {
El control de las excepciones se hace muy complicado.
Ejecución fuera de orden.
La planificación dinámica implica: {
Completado fuera de orden.
Ejecuta las instrucciones sólo cuando los operandos están disponibles.
Algoritmo de Tomasulo: {
Renombra los registros por hardware para eliminar las dependencias WAR y WAW.
Los accesos a memoria (ejecutados en búferes de carga y de almacenamiento) pueden tener dependencias entre
los datos que leen o escriben. Para ello debe ocurrir que la dirección de un acceso sea igual a la de otro.
Evidentemente esta dependencia no se puede detectar en tiempo de compilación. Por ejemplo:
SW 20(R3), R9
LW R1, 8(R4) Si ocurre que R3=4 y R4=16, entonces: 20+R3 = 8+R4 provocando RAW en memoria.
En este caso, antes de que una orden LOAD acceda a la memoria caché, se debe comparar su dirección efectiva
con todas las de los búferes de almacenamiento. Si hay coincidencia entonces se detecta una dependencia real
en memoria, y hay que esperar a que la orden STORE termine. De no existir tal comparación de direcciones, no
se podría ejecutar una orden LOAD antes de que acabaran todas las órdenes STORE (caché actualizado). Si bien las
órdenes STORE suelen ser las últimas en ejecutarse.
Los búferes de STORE son como un buffer de escritura (conserva la dirección y el dato escrito). Los datos de las
órdenes STORE se guardan en el buffer y se dan por realizados. Las órdenes LOAD son más prioritarias ya que
puede haber otras instrucciones esperando por sus datos.
En procesadores con pocos registros de usuario (CISC) el código tendrá muchos accesos a memoria, por lo que
el riesgo es mayor y se necesita más hardware para resolverlo. Encontramos dependencias en una instrucción
del siguiente tipo donde, dada una sucesión de operaciones aritméticas, hay que respetar el orden de prioridad:
a[i] = a2 * a3 / a4 + a3;
MULTF F1, F2, F3 Dependencia Real. La división no se puede realizar hasta que no
DIVF F2, F1, F4 termine la multiplicación.
ADDI F1, F2, F3 Dependencia Ficticia. Reutilización del registro F1 sustituyendo
SF (R1), F1 valores.
Nota: El registro F2 es reutilizado, antes era un registro fuente y después un registro destino, esto es debido a
que ya no se vuelve a utilizar y se dispone de pocos registros auxiliares.
31
Hasta ahora todas las técnicas de segmentación ejecutan las instrucciones en orden. Si una instrucción no puede
ejecutarse, la cadena se detiene completamente:
La espera que ADDF introduce en SUBF, ADDI, ... , podría eliminarse si se permitiera que la ejecución (EX) no se
hiciera en orden (out-of-order execution), permitiendo que SUBF y ADDI comiencen antes su ejecución (EX) que
ADDF. Es decir, estamos reordenando la fase EX en tiempo de ejecución (dinámicamente).
La fase de decodificación (ID) siempre debe ir en orden para poder comprobar las dependencias.
Vamos a suponer que se comprueban las dependencias de datos y los riesgos estructurales en una única etapa,
en la ID (o IS, Issue en adelante), por tanto, se puede considerar que debe hacer dos cosas:
- Decodificación y Emisión (Issue): Decodificar la instrucción y comprobar las dependencias entre los
datos, así como los riesgos estructurales (anotándolos de alguna forma).
- Lectura de operandos (Read Operands): Leer los operandos disponibles (no tienen RAW cercana) y
esperar por los operandos que tienen dependencia.
Todas las instrucciones pasan en orden por la etapa ISsue, y a partir de aquí pueden ser detenidas o desviadas a
la Unidad Funcional de ejecución correspondiente. Hay que tener en cuenta que la etapa ISsue no puede
desordenarse, porque entonces no se sabrían cuáles son las dependencias.
Como hemos indicado anteriormente, hay que apuntar la información de las dependencias en algún sitio:
- Si la anotación es una tabla central, el algoritmo se llama marcador centralizado (scoreboard) (1964).
- Si la anotación es distribuida, el algoritmo más usual es el Algoritmo de Tomasulo (1967).
32
Miguel Ángel Cifredo
macifredo@gmail.com
Se hace necesario por tanto, utilizar una serie de entradas en cada Unidad Funcional de la máquina, llamadas
Estaciones de Reserva. A éstas llegan las emisiones (IS) de instrucciones con toda la información de la misma,
también llegan los valores de los operandos fuente, si han podido leerse, o cierta información (etiqueta) que
indica qué operando falta (no por ello se bloquea la cadena).
Una Estación de Reserva (RS, Reservation Station) es el buffer (registro) donde esperan las instrucciones
emitidas hasta que puedan ser ejecutadas, junto a las ALU’s (Unidades Funcionales). Dicha espera se
debe a la no disponibilidad de todos los operandos fuente (se produce RAW: una instrucción anterior
aún no ha generado algún operando requerido). Las Estaciones de Reserva tienen una serie de campos
para anotar el valor de los operandos fuente, y si es necesario, el valor del resultado de la operación y la
etiqueta asociada al registro destino. Cada Estación de Reserva está a la “escucha” del CDB para capturar
por Bypass los operandos que necesite.
Los Buffers de Memoria son las Estaciones de Reserva para instrucciones de carga/almacenamiento. Es
el lugar (registro) donde esperan los accesos a memoria emitidos hasta que disponen de todos sus
operandos y el bus de memoria (caché) esté libre. En el algoritmo clásico y en general, hay buffers
separados, unos para instrucciones de carga y otros para las de almacenamiento.
El Bus Común de Datos (CDB, Common Data Bus) une la salida de las Unidades Funcionales, la carga de
memoria con el fichero de registros, las estaciones de reserva y los buffers. Las Unidades Funcionales
mandan el dato de salida a través del BCD para que lo lean el resto de elementos de la CPU (es decir, se
usa en la fase WB, produciéndose un Bypass). Se trata de un recurso común por el que hay que competir.
En general, las máquinas suelen tener más de un CDB (por ejemplo, uno para enteros y otro para coma
flotante) puesto que el número de WB por cada ciclo puede ser mayor que 1. Cualquier CDB tiene dos
buses: uno para el resultado de una operación (tamaño de 32 líneas para Int/Float, 64 líneas para Double)
y otro para la etiqueta asociada al mismo (tamaño log2 del número de etiquetas).
Las Etiquetas (tags) son identificadores que se asocian a los registros destino cada vez que se emite una
instrucción. A partir del renombrado de un registro destino con una etiqueta, es ésta la que circula por
toda la CPU (estaciones de reserva, buffers, fichero de registros, CDB, etc.). Una etiqueta estará en uso
desde que la instrucción se emite hasta que su resultado es puesto en el CDB. En el algoritmo clásico, el
nombre de la etiqueta coincide con el de la Estación de Reserva, por ejemplo, si la UF de MULT tiene 3
Estaciones de Reserva, entonces las etiquetas serán MULT1, MULT2, MULT3 (codificadas en binario).
Por tanto, tal como estamos viendo, el renombrado de registros es posible gracias a las Estaciones de Reserva,
que contienen principalmente: la instrucción, valores de los operandos almacenados (cuando están disponibles)
y el número de las Estaciones de Reserva de aquellas instrucciones que suministrarán los valores en espera.
Las Estaciones de Reserva recogen y almacenan un operando tan pronto como esté disponible (no implica
necesariamente el guardado en fichero de registros). Las instrucciones pendientes designan la Estación de
Reserva a la cual envían su salida. Los valores de los resultados son puestos en el bus CDB. Únicamente la última
escritura de un registro modifica el fichero de registros.
Cuando las instrucciones son emitidas, el registro especificado es renombrado con el nombre de la Estación de
Reserva. De hecho, puede haber más Estaciones de Reserva que registros.
33
2.2.3 Dependencias en el flujo de datos.
Debemos saber que las dependencias ficticias se pueden destruir si existen registros libres. Por ejemplo, a cada
registro destino se le asigna uno nuevo renombrándolo, lo que implica tener que seguirle el rastro, respetando
siempre las dependencias reales. En el código siguiente observamos la técnica del renombrado de registros:
MULTF RS1 , F2 , F3
DIVF RS2 , RS1, F4
Este proceso de renombrado de registros se realiza dinámicamente y de
ADDF RS3 , RS2, F3 forma automática por el ensamblador. Dichos registros son en realidad
SF (R1), RS3 punteros (buffers) llamados Estaciones de Reserva (RS, Reservation Staions).
¿Se pueden procesar todas las iteraciones en paralelo? Si es así, entonces diremos que es vectorizable.
34
Miguel Ángel Cifredo
macifredo@gmail.com
Cuenta con un buffer de reordenado que mantiene el resultado de la instrucción entre el completado y
el commit. Este buffer tiene cuatro campos:
El proceso de reordenación implica la modificación de las Estaciones de Reserva, donde la fuente de los
operandos ahora es el buffer de reordenado en vez de las Unidades Funcionales. Así pues, los valores no
son escritos ni en registros ni en memoria hasta que la instrucción es confirmada (commit).
En caso de fallo de la predicción, las entradas especulativas son limpiadas del ROB (Re-Order Bus).
Las excepciones no son reconocidas hasta que no están listas para ser confirmadas (commit).
Predicción dinámica.
Centrado en los saltos condicionales con una tasa de acierto en torno al 95%.
Utiliza los BTB (Branch-Target Buffers) de predicción del siguiente PC, indexados por el PC actual.
35
Especulación dinámica.
Se trata de dar un paso más para superar las dependencias de control. Ejecuta las instrucciones de la
rama predicha, pero sólo confirma los resultados si la predicción fue correcta. Esto es, ejecuta el commit
de la instrucción y le permite actualizar el fichero de registros cuando dicha instrucción ya no es
especulativa. Así pues, las ejecuciones son fuera de orden, pero los commit son en orden.
Para llevar a cabo esto, se necesita un hardware adicional para evitar cualquier acción irreversible hasta
que se produce el commit de las instrucciones, como la actualización del estado.
Por tanto, la BTB predice los saltos pero puede fallar, el procesador puede especular y debe deshacer los
cambios en caso de haber fallado con la predicción. A nivel de software, sólo se ejecutan instrucciones
reversibles y, a nivel de hardware, se incluyen elementos que permiten el deshacer (rollback).
Pero cabe preguntarnos ¿Cuánto hay que especular? Una mala predicción reduce el rendimiento y la
potencia de la máquina, respecto a la no especulación, pudiendo causar fallos adicionales (caché, TLB).
La especulación de múltiples saltos complica mucho la recuperación, de hecho ningún procesador puede
resolver múltiples saltos por ciclo.
Debemos tener en cuenta que la especulación mejora la eficiencia energética sólo cuando incrementa
las prestaciones significativamente.
Se requiere de un hardware de especulación para todas las instrucciones, por tanto, todas las escrituras
de MEM o WB no se hacen en el fichero de registros o en la caché de datos, sino que se alojan en un
“búfer de reordenación” (ROB, Re-Order Buffer). Tal como se asignó en la emisión de la instrucción (IS)
una entrada en dicho ROB, el microprocesador tiene en cada ciclo dos “estados”, dos PC, los siguientes:
Las instrucciones se emiten siempre en orden (“in-order issue”). El estado especulativo siempre avanza
con cada emisión. Una instrucción sólo hace “CM, completion” si las instrucciones anteriores ya lo han
hecho. Tenemos que, para toda predicción, si se produce una interrupción, las siguientes situaciones:
36
Miguel Ángel Cifredo
macifredo@gmail.com
Este algoritmo se puede aplicar a otro tipo de sistemas donde hay ciertos recursos compartidos por diversos
agentes, y se quieren gestionar los recursos de forma distribuida. Evidentemente la implementación que
veremos aquí es únicamente de tipo hardware.
Durante su ejecución provoca un reordenamiento dinámico en la ejecución (fase EX) de las instrucciones, aunque
la emisión sigue siendo en el orden del código original. Además, permite la ejecución de instrucciones no
dependientes de otras anteriores, aunque estas últimas estén bloqueadas esperando por algún operando fuente.
Realizará un renombrado dinámico de registros para evitar riesgos por dependencias (WAR, WAW) entre
instrucciones. Todos los registros de destino se renombran y el nuevo nombre que toman se llama etiqueta (tag).
En este algoritmo se ponen una serie de entradas, las Estaciones de Reserva, en cada Unidad Funcional. A éstas
llegan las emisiones (IS) de instrucciones con toda la información de las mismas, también llegan los valores de
los operandos fuente, si han podido leerse, o cierta información en forma de etiquetas (tags) que indica qué
operando falta (no se bloquea la cadena). Cada Estación de Reserva está a la “escucha” del CDB para capturar
por bypass los operandos que necesite.
En la implementación del Algoritmo de Tomasulo, las Estaciones de Reserva son registros asociados a las
Unidades Funcionales, de manera que forman una pequeña cola de instrucciones a la entrada de la Unidad
Funcional (de unas pocas por Unidad Funcional si la operación no es muy común, como la división DIV, a muchas
si es muy frecuente). Cada registro contendrá la información necesaria para la ejecución de la instrucción y para
la llegada de los operandos que le falten, en caso de que la instrucción esté en espera.
37
2.2.5.1 Etapas del Pipeline en el Algoritmo de Tomaluso:
Se puede definir la especificación exacta (en lenguaje C o con simuladores) del Algoritmo de Tomasulo. Que se
implementa posteriormente sobre hardware.
Dado que con la arquitectura hardware de dicho algoritmo la cadena no se bloqueará cuando haya RAW, la
cadena (pipelining) debe cambiar.
Fetch (IF): Acceso a la memoria caché de instrucciones para leer la instrucción apuntada por el PC.
Issue (IS): Emisión de la instrucción. En el algoritmo clásico esta etapa se hace en un sólo ciclo (hoy en
día esta etapa dura varios ciclos de reloj). Realizaría lo siguiente:
- Decodificación de la instrucción.
- Lectura de los operandos que ya estén disponibles.
- Envío hacia la Estación de Reserva, la cual sería la auténtica emisora.
Al final de esta etapa IS, se intenta enviar la instrucción a una Estación de Reserva (buffer de
carga/almacenamiento), asignando una etiqueta (tag) al registro destino.
- Si hay una Estación de Reserva libre en la Unidad Funcional requerida por la operación entonces
se emite la instrucción mandando a la Estación de Reserva los operandos disponibles. Para los
que no estén disponibles, se anota la etiqueta por la que esperan.
- Si no hay una Estación de Reserva libre o no quedan etiquetas (tag) libres, la etapa IS se
bloqueará hasta que una Estación de Reserva de la Unidad Funcional se libere. En el algoritmo
clásico, y siempre que todo se realice en una única fase IS, esto provoca una detención de la
cadena de ejecución.
Execution (EX): Cuando una Estación de Reserva obtiene todos sus operandos, empieza a ejecutarse, por
lo que la primera instrucción preparada es la primera que se ejecutará. La Estación de Reserva puede
quedar libre cuando comience a ejecutarse en la Unidad Funcional el primer EX1, entonces, esta etapa
conservará en algún otro sitio dos campos: uno para el resultado y otro para la etiqueta asociada al valor
de salida enviándose en WB junto al resultado de la operación. Igualmente, también puede quedar libre
al final de la etapa WB. La Estación de Reserva tendrá todos los campos necesarios para operandos
fuente, resultado y etiqueta.
Write Back (WB): El resultado de una operación y su etiqueta se escriben en el CDB, de manera que las
Estaciones de Reserva que estén esperando por este valor (tienen la misma etiqueta en alguno de sus
operandos) lo toman, es decir, se produce un bypass o camino de desvío a través del CDB. Igualmente,
el valor del CDB se escribe en el fichero de registros de coma flotante si su etiqueta coincidiera con
alguno. La etiqueta se libera tras la escritura. El fichero de registros deberá, por tanto, tener información
sobre la última etiqueta que va asociada a cada registro.
Es necesario un arbitrador de cada CDB, ya que si varias Estaciones de Reserva quieren escribir a la vez
en un CDB, el arbitrador dará permiso sólo a una, a la más antigua en su emisión.
NOTA: La fase MEM no existe, sino que se considera dentro del EX para los buffers de memoria.
38
Miguel Ángel Cifredo
macifredo@gmail.com
Emisión: Obtiene la siguiente instrucción de una cola FIFO y, si hay Estaciones de Reserva disponibles
entonces le emite la instrucción con los valores de los operandos que estén disponibles en ese momento; y
si no la hay o no está disponible algún operando, envía los datos de la Estación de Reserva que lo generará.
Pero, si ya no quedase alguna Estación de Reserva disponible entonces bloquea la instrucción.
Ejecución: Si algún operando no está disponible, monitoriza el CDB. Cuando el operado aparece por el CDB,
lo guardan las Estaciones de Reserva que estuvieran esperándolo. Cuando todos los operandos necesarios
están disponibles (almacenados en la RS), se ejecuta la instrucción. Las cargas y los almacenamientos deben
ser ejecutadas en el orden correcto, vigilando la dirección efectiva. No se permite iniciar la ejecución de
instrucciones hasta que todos los saltos que la preceden, en orden natural del programa, se hayan resuelto.
Escritura de Resultados: Escribimos (almacenamos) los resultados sobre el CDB, las Estaciones de Reserva,
buffers de almacenamiento y, en su caso, el Fichero de Registros, que necesiten dicho dato. Los
almacenamientos deben esperar a recibir tanto la dirección como el valor a almacenar.
La implementación del Algoritmo de Tomasulo en las máquinas actuales sigue la misma idea principal del
algoritmo visto, pero se puede encontrar con distintas modificaciones o adaptaciones. Antiguamente se aplicaba
este algoritmo sólo para instrucciones largas en coma flotante, código para supercomputadores científicos. De
forma que la cadena era distinta para coma flotante que para enteros. Actualmente las operaciones enteras
también llevan planificación dinámica similar al Algoritmo de Tomasulo. En el Algoritmo Clásico, la etiqueta y las
Estaciones de Reserva son lo mismo. Se supone una etiqueta por cada Estación de Reserva, luego para poder
emitir, el requisito es único: ¿hay alguna Estación de Reserva disponible? Cuando la Unidad Funcional termina la
ejecución, se devuelve el resultado a la Estación de Reserva, ésta pondrá el resultado en el CDB cuando este se
encuentre disponible, liberándose la Estación de Reserva y por ende, la etiqueta.
Existen arquitecturas con Estaciones de Reserva que se liberan al empezar la fase EX (cuando todos los operandos
fuente están disponibles). Cuando la Unidad Funcional termina la ejecución, se recoge el resultado de la
operación en otro registro interno. En cuanto haya un CDB disponible se libera tal registro interno. También
existen arquitecturas con “pilas de etiquetas” (tag-pools). Las etiquetas son un recurso común y compartido.
Para emitir una instrucción se precisa un requisito doble: debe existir una etiqueta disponible y una Estación de
Reserva libre (la etiqueta tiene asociado un elemento hardware). El registro destino se renombra con la etiqueta
(y no coincide con la Estación de Reserva). Un ejemplo usual de pila de etiquetas donde las Estaciones de Reserva
se liberan en EX, se da en los micros que renombran, dinámicamente y para todas las instrucciones emitidas, los
registros lógicos (de usuario, de las instrucciones de ensamblador) por registros físicos (internos, no visibles al
programador). Los registros físicos funcionan como etiquetas. Suele haber muchos más registros físicos que
lógicos, por ejemplo: Power PC, MIPS.
Todos tienen varios CDB para permitir más de una escritura al mismo tiempo. En algunos micros, la fase WB no
existe, en el último ciclo de ejecución se escribe en el CDB, tal como se hace en el DLX sin planificación dinámica,
el desvío de un dato por un bypass no tarda ningún ciclo. Así se acercan al límite flujo de datos. La cadena tiene
diferente número de etapas (no es la clásica IF IS EX WB). Generalmente la fase IS se subdivide en varias subfases,
pues hay que hacer muchas operaciones: decodificación, lectura de operandos, renombrado, emisión, etc.
39
2.2.5.3 Casos de estudio del Algoritmo de Tomasulo.
Caso 1: Instrucciones con todos los operandos disponibles, se ejecutan sin esperas, como un pipeline.
Sería el caso trivial, donde tiene un CPI ideal. La sucesión de fases sería:
IF → IS → EX → WB
Caso 2: Instrucciones con dependencia real de otra instrucción anterior. Un ejemplo sería:
Caso 3: Las Estaciones de Reserva están agotadas y las instrucciones no pueden anotar su información
en alguna de ellas, lo que provoca un bloqueo por desconocer la información de la instrucción anterior.
IF - - - IS EX WB
Los sistemas superescalares se han acercado al pleno límite del flujo de datos (Data Flow Limit), viéndose
restringido por las características del propio código de la aplicación.
¿Qué dos factores limitan más el paralelismo extraíble en los benchmarks reales?
2. Accesos a memoria de datos (fallos de caché, consumo de ancho de banda de RAM masivo).
El programador es el encargado de reducir estos factores, esto es, de simplificar los patrones de acceso y del
comportamiento de los saltos.
40
Miguel Ángel Cifredo
macifredo@gmail.com
Ejemplo 1:
Ejemplo 2: Existe una correlación entre el if y el while antes de los saltos. No se sabe si se deberá ejecutar el
bloque siguiente o el de destino.
flag = 0;
while (flag == 0)
{
// operaciones…
if (condición) { flag = 1; }
}
Contamos con la caché predictiva BTB (Branch Target Buffer) que está unida a la memoria caché y accede a las
instrucciones (32 bits) en la fase IF, que predice el siguiente PC. Trata con el IF predicho, de forma que, si acierta
en la predicción, funciona bien, dando lugar a un CPI ideal.
Se nos plantea por tanto dos casos, si acierta en la predicción, entonces el CPI bloqueo = 0 pero, si falla (se detecta
salto en la fase EX), nuestras predicciones no habrán servido de nada. Lo óptimo sería indicar el IF en la siguiente
instrucción después de EX para poder contar los números de ciclos de bloqueo, contar la fase de la última válida
y la actual válida, esto es, IF primera e IF última. En el ejemplo 1 anterior, el número de ciclos de bloqueo es 5.
Nota: En los sistemas superescalares, donde m = 4, su fase más lenta es IS (por la planificación dinámica). En
equipos con arquitectura RISC, la fase IF cuenta con 8 ∙ 32 = 256 bits, por lo que desde la caché de instrucciones
manda 256 bits a la CPU. En la actualidad, el porcentaje de fallos es pequeño, con una media en torno al 4%.
1
Si el bucle anterior tuviera 100 iteraciones y salta en la última, tendríamos un porcentaje de saltos = 100
.
41
Motivo 2. “Accesos a memoria de datos (Fallos de caché, Consumo de ancho de banda de RAM masivo)”.
Acierto en la caché L1, que sería el caso ideal, teniendo en cuenta la duración de acierto de dicha caché.
Fallo en la caché L1, es el caso menos probable, con el mismo ratio o porcentaje de fallos.
Ejemplo:
for(i=0; i<1000; i++) { Tendríamos la siguiente correlación:
for (j=0; j<3; j++) {
// bucle interno F→F→F→V →F→F→F→V→F→F→F→V
}
// instrucciones del bucle externo como el bucle interno sigue ese patrón
}
se acierta casi siempre.
selectora
Máquina
Predicción 1
esquema de predicción local y otro global, y seleccionan la → Predicción
predicción de uno de ellos según el estado de un contador de Info. global →
saturación similar existente en los predictores simples, en los que se Predicción 2
requiere un cierto número de fallos consecutivos para cambiar de
estrategia.
En caso de acierto, el pipeline contempla esas fases y se permite la segmentación. De este modo tendríamos:
IF IS M1 M2 M3 WB
IF IS M1 M2 M3 WB
IF IS M1 M2 M3 WB
Cuando se produce un fallo en la caché L1, podemos determinar el “Miss rate” o “% fallos”, aunque estos suelan
ser pocos. Veamos, mediante un ejemplo, un fallo de caché:
LOAD IF IS EX Acceso a caché L2 WB
IF IS ○ ○ ○ ○ ○ ○ ○ EX WB
IF IS ○ ○ ○ ○ ○ ○ ○ ○ EX WB
Si hay pocos fallos, hay instrucciones IF IS ○ ○ ○ ○ ○ ○ ○ ○ ○ EX WB
IF IS EX WB
independientes que pueden seguir IF IS EX WB
trabajando…
Caché L1 ≈ 3 a 5 ciclos
Caché L2 ≈ 10 ciclos
A saber, los tiempos de acceso a las memorias: {
Caché L3 ≈ 20 ciclos
RAM ≈ 100 ciclos
Tomando el ejemplo anterior, si en vez de tener que acceder a caché L2, se accediera a RAM, aproximadamente
tendrían 400 instrucciones en espera, se llegaría al probable agotamiento de las Estaciones de Reserva y con ello,
al bloqueo del sistema.
42
Miguel Ángel Cifredo
macifredo@gmail.com
Se produce sobre estructuras de grandes volúmenes de datos (vectores), que no caben en la memoria caché,
por lo que se producen fallos forzosos en los accesos a RAM. El ancho de banda de la RAM (ABRAM) es de 20 GB/s
Ejemplo: Partimos de la premisa que tenemos una RAM lenta. Si el tiempo de ejecución es por acceso a RAM…
Tenemos en cuenta que el bucle DAXPY utiliza variables tipo Double, esto es, requiere 64 bits (8 Bytes).
Así mismo, si se accediera en tres ocasiones a memoria, el tiempo de ejecución sería:
b) ¿Cuántos ciclos/iteración tendrá si la frecuencia es de 4 GHz? ¿Dichos ciclos/iteración son mayores que los
ciclos ideales por iteración? Tenemos,
Según estamos estudiando, podemos concluir que, la predicción de saltos es la alternativa dinámica, en tiempo
de ejecución, para reducir los bloqueos por dependencias de control. Contamos con diversos predictores:
Predictor básico de 2-bit: Para cada salto predice tomado o no tomado y, si la predicción es errónea dos
veces consecutivas, cambia la predicción.
Predictor con correlación: Puede ser bien mediante el uso de múltiples predictores de 2-bit para cada
salto, o bien, uno para cada combinación posible de los resultados de las anteriores n ramas.
Predictor local: Puede ser bien, como en el caso anterior, mediante el uso de múltiples predictores de
tipo 2-bit para cada salto, o bien, uno para cada combinación posible de los resultados de las anteriores
n ocurrencias de este salto.
Predictor por Torneo: Combina un predictor con correlación con otro predictor local.
1 ciclo 2 ciclos
Pendiente = 1 ⇒ CPI = 1 instrucción = 1 Pendiente = 2 ⇒ CPI = 1 instrucción = 2
Nos preguntarnos ¿cuándo se bloquea el Algoritmo de Tomasulo? Pues al encontramos con un bucle de tipo:
1 ciclos 1 2 ciclos
CPIideal = = CPIreal = =2
3 instrucciones 3 1 instrucción
1
Entonces, CPIbloqueo = 2 - = 1'6̂
3
44
Miguel Ángel Cifredo
macifredo@gmail.com
Debemos tener en cuenta que las Unidades Funcionales sí están segmentadas, lo que permite el pipeline, tal
como se muestra:
IS EX1 EX2 EX3 WB
IS EX1 EX2 EX3 WB
IS EX1 EX2 EX3 WB
← Pipeline (forma normal)
Las Unidades Funcionales de las divisiones, por ser una operación muy compleja, están formada por elementos
combinacionales que no permiten la segmentación en el pipeline. Ante ello, podemos evitar las divisiones
multiplicando por sus valores inversos. El siguiente cronograma consta de 6 Estaciones de Reserva y 3 pipelines:
20 ciclos
CPIreal = = 20 que coincide con la duración de la Unidad Funcional.
1 instrucción
Motivo 5. “Uso de las mismas operaciones (código conteniendo sólo operaciones aritméticas)”.
Veamos un ejemplo, con sólo sumas enteras, con los siguientes parámetros: Grado de superescalaridad m = 4,
número de Unidades Funcionales para la operación suma entera, 2. Tenemos entonces:
45
2.3 Planificación Estática.
Las técnicas dinámicas están empotradas on-chip, de forma transparente al usuario, por lo que la mejora del
resultado se consigue en el tiempo empleado por sus componentes de carácter generalista (General Purpose
Process). Se trata de procesadores dinámicos superescalares (Dynamic Superscalar Processors) pero que
consumen mucha energía.
Respecto a las técnicas estáticas podemos indicar que dependen del programador, lo que requiere el uso
correcto de los “flags” del compilador que utilice; esto es, se basan en ajustes muy finos y detallistas ya que son
altamente dependientes de la arquitectura interna del microprocesador (endo-arquitectura). La sincronización
entre microprocesador y órdenes a ejecutar mantiene una relación muy débil con las diferentes versiones de
este, lo que puede obligar a recompilar todo el código para una nueva versión de microprocesador. Por tanto es
la técnica más utilizada para sistemas empotrados o dedicados, ya que además, consumen menos energía.
Las técnicas estáticas están soportados por procesadores estáticos superescalares (Static Superscalar Processors)
que no cuentan con el Algoritmo de Tomasulo, entre los que podemos destacar los VLIW que reducen el número
de instrucciones para disminuir el tiempo de ejecución. En realidad cuentan con macro instrucciones, por lo que
al final, realizan muchas operaciones, pero que pueden ser independientes y ejecutar muchas de ellas de forma
concurrente. Para encontrar cuáles son factibles y evitar las dependencias reales, se necesita de una planificación
estática avanzada que las reordene y se evite sus bloqueos.
Consiste en la reordenación de instrucciones dentro de un bloque de código básico. Entendiendo como boque
de código básico al conjunto de instrucciones que no contenga alguna de tipo salto. En lenguaje C se identifican
con aquellas encerradas entre los caracteres { y }.
La reubicación del fragmento de código indicado en el ejemplo, implica ciertos ajustes en los registros. Debemos
tener en cuenta que, concretamente en el bucle DAXPY, utilizamos números tipo Double, por lo que la variación
del incremento en dichos registros será de 8 en 8.
LD FX, 0(R1) IF IS EX WB
LD FY, 0(R2) IF IS EX WB
MULTD Fm, Fa, FX IF IS ○ EX1 EX2 EX3 EX4 WB
ADDI R1, R1, 8 IF IS EX WB
ADDI R2, R2, 8 IF IS EX WB
SGT R3, R1, RfinX IF IS EX WB
ADDD FY, Fm, FY IF IS ○ ○ EX1 WB
SD 0(R2), FY IF IS ○ ○ ○ EX WB
BEQZ R3, bucle IF IS EX WB
Software pipelined.
47
2.3.3.1 Desenrollado del código de bucles.
Tengamos presente que los bucles representan más del 90% del tiempo de ejecución en un procesador.
Veamos cómo desenrollar los bucles paralelizables (vectoriazables) mediante iteraciones independientes.
Ejemplo:
Paso 1:
Bucle_original:
LD F2, 0(R1) ; Instrucciones útiles
MULTD F4, F2, F24 ; F24 = s
SD (R3)0, F4
;---------------------------
ADDI R1, R1, 8 ; instrucciones de overhead (Control del bucle)
ADDI R3, R3, 8
SLTI R7, R1, fin_array_x ; Puntero al final de x[M]
BNEZ R7, Bucle_origianl
Paso 2:
Bucle_intermedio_2: ; Desenrollando el bucle principal
LD F2, 0(R1)
MULTD F4, F2, F24
SD (R3)0, F4
LD F2, 8(R1)
MULTD F4, F2, F24
SD (R3)8, F4
LD F2, 16(R1)
MULTD F4, F2, F24
SD (R3)16, F4
;----------------------
ADDI R1, R1, 8*3
ADDI R3, R3, 8*3
SLTI R7, R1, fin_array_x
BNEZ R7, Bucle_intermedio_2
48
Miguel Ángel Cifredo
macifredo@gmail.com
Paso 3: El renombrado de registros evita las dependencias WAR, WAW. Notaremos (’) para simplificar el código.
Bucle_intermedio_3:
LD F2, 0(R1)
MULTD F4, F2, F24
SD (R3)0, F4
LD F2’, 8(R1)
MULTD F4’, F2’, F24
SD (R3)8, F4’
LD F2’’, 16(R1)
MULTD F4’’, F2’’, F24
SD (R3)16, F4’’
;----------------------
ADDI R1, R1, 8*3
ADDI R3, R3, 8*3
SLTI R7, R1, fin_array_x
BNEZ R7, Bucle_intermedio_3
SD (R3)0, F4
SD (R3)8, F4’
SD (R3)16, F4’’
;----------------------
ADDI R1, R1, 8*3
ADDI R3, R3, 8*3
SLTI R7, R1, fin_array_x
BNEZ R7, Bucle_desenrollado
Paso 5: Tenemos la opción de poner instrucciones después de aquellas de mayor duración para evitar bloqueos.
Bucle_desenrollado_opcional:
LD F2, 0(R1)
LD F2’, 8(R1)
LD F2’’, 16(R1)
SD (R3)0, F4
SD (R3)8, F4’
SD (R3)16, F4’’
;----------------------
SLTI R7, R1, fin_array_x ;
49
Veamos otro ejemplo con los siguientes bucles:
for (i=0; i<N; i+=2)
{
y[i] = a * x[i] + y[i];
y[i+1] = a * x[i+1] + y[i+1];
}
for (i=i; i<N; i++)
{
y[i] = a * x[i] + y[i];
}
Quedaría en bajo nivel como sigue, tonamos con un super-índice el número de la iteración (i + super-índice):
* Se indica un valor de 16 debido a que la iteración del bucle es cada 2 saltos (i+=2) de tipo doublé (8+8 = 16)
Evidentemente, el máximo rendimiento lo tendríamos si desenrollamos las “infinitas” iteraciones del bucle.
Otro aspecto muy importante es que dado que el recuento de instrucciones se reduce, el CPI no es una buena
ciclos
medida del rendimiento, en tal caso deberíamos utilizar: tamaño del vector
Existen bucles que resultan muy complejo su desenrollado, por ejemplo cuando el número de iteraciones es
totalmente desconocido en tiempo de compilación, lo que provoca que el compilador no llegue a realizar el
desenrollado. Tal es el caso de cualquier cálculo científico iterativo, como el de determinar un rango de error:
while(error > ε) {
Xk+1 = f(Xk, entrada);
error = || XK+1 – Xk ||;
}
50
Miguel Ángel Cifredo
macifredo@gmail.com
Ejercicio:
a) Porcentaje de saltos.
b) Número de instrucciones ejecutadas.
Téngase en cuenta que si desenrollásemos las k iteraciones, este valor de k debería ser múltiplo de
las 1.000 iteraciones.
Solución:
La reordenación de las instrucciones reduce el porcentaje de saltos al permitir insertar más instrucciones
entre dos saltos.
51
2.3.4 Software pipelined.
Un bucle software-pipelined intercala instrucciones de diferentes iteraciones sin desenrollar el bucle, por lo que
sólo es útil para bucles paralelizables. Es por tanto una alternativa al desenrollado de bucles cuando, bien
tenemos pocos registros (como ocurre en máquinas CISC) o bien, cuando nos preocupa el tamaño del código.
Esta técnica se basa en la segmentación del software con encadenamiento de iteraciones. El desenrollado es
eficaz, pero crece de manera significativa el tamaño del código estático, lo que suele aumentar el número de
fallos de caché, exigiendo contar para ello con muchos registros para resolver el renombrado.
Esquema de un bucle DAXPY paralelizable escrito de izquierda a derecha, donde hay muchas dependencias
reales. Dichas dependencias sólo han sido indicadas en la iteración 0, pero se repiten en el resto de iteraciones.
Prólogo (cálculo previo)
Iter 0 LDX0 LDY0 MULTD0 ADDD0 SD0 overhead
Las columnas coloreadas y punteadas centrales se corresponden con las mismas operaciones útiles de cada una
de las distintas iteraciones. Se genera por tanto un nuevo bucle con operaciones independientes.
52
Miguel Ángel Cifredo
macifredo@gmail.com
Veamos, como en el caso anterior, un nuevo ejemplo a partir del siguiente código:
for (i=0 ; i<M ; i++ ) { y[i] = x[i] * s; }
Bucle_original:
; Instrucciones útiles
LD F2, 0(R1)
MULTD F4, F2, F24
SD (R3)0, F4
; Instrucciones de overhead
ADDI R1, R1, 8
ADDI R3, R3, 8
SLTI R7, R1, fin_array_x
BNEZ R7, Bucle_original
Las flechas rojas (horizontales) se corresponden con una ejecución normal y las flechas verdes (verticales) con
software pipelined.
Veamos cómo las instrucciones con dependencias reales se separan en iteraciones diferentes:
Inicio:
LD0
MULTD0
LD1
Bucle_SW_PP:
SD0 (K)
MULTD1 (K+1)
LD2 (K+2)
; Instrucciones overhead (incluyendo un salto a Bucle_SW_PP)
Final:
SD M-2
MULTD M-1
SD M-1
Las instrucciones de overhead deben modificar constantes correctamente, lo que no es una tarea fácil.
Aparecen instrucciones adicionales de overhead tanto en el inicio como en el final.
Al menos debemos ejecutar 2 iteraciones.
Algunos compiladores avanzados pueden implementar estas técnicas, pero el Software pipelined es
difícil de implementar.
El soporte Hardware es de mucha utilidad (por ejemplo IA-64).
53
2.3.5 Ejecución predictiva o condicional.
Los saltos especificados en el código son el mayor de los límites para la planificación estática. Algunos saltos son
fácilmente predecibles, por ejemplo, los bucles normalmente son tomados, y los saltos por overflow
normalmente no se toman. Sin embargo hay otros bucles que no son tan fáciles de predecir, por ejemplo:
for (i=0; i<n; i++) {
if (a[i] < 0) {
b[i] = -a[i];
} else {
b[i] = a[i];
}
}
Una solución sería extender el conjunto de instrucciones para incluir instrucciones predicadas o condicionales:
Estos nuevos tipos de instrucciones pueden ser usados para eliminar los saltos, y convertir las dependencias de
control en dependencias de datos.
Las instrucciones predicadas o condicionales (Conditional MOVe [Not] Zero) se comportan como una instrucción
tipo if implementada en hardware, tenemos:
Por tanto, el objetivo de la ejecución predictiva o condicional es eliminar la instrucción if ... else cuando:
Los microprocesadores MIPS, Alpha, PowerPC, SPARC, e Intel x86 soportan los juegos de instrucciones predictivas
o condicionales. Además, las arquitecturas ARM, el Intel VLIW Itanium IA-64 y muchos DSPs (Digital Signal
Processor) soportan la predicación completa para casi todas las instrucciones.
Ejemplo: k = min(c, m, y); //mínimo de 3 valores Traducido a bajo nivel usando instrucciones predictivas.
54
Miguel Ángel Cifredo
macifredo@gmail.com
Ejemplo:
Vamos a utilizar otro conjunto de instrucciones predictivas, válidas para todas las instrucciones, las conocidas
como Predicación Universal (Full Predication):
[ Rc] Operador Rd, Rf1, Rf2
[!Rc] Operador Rd, Rf1, Rf2
Ejemplo:
Observamos que hemos predicado el cuerpo condicionado por el if , el registro Rcond. Esto es, sólo se ejecutará
si el registro Rcond vale 1 (es verdadero).
55
Ejemplo: ¿Qué ocurriría en un bloque condicional cuyo cuerpo es muy grande?
if (Rcond)
{
Oper1 [ Rcond] Oper1
Oper2 [ Rcond] Oper2
Oper3 [ Rcond] Oper3
. . .
}
else
{
Oper1 [!Rcond] Oper1
Oper2 [!Rcond] Oper2
Oper3 [!Rcond] Oper3
. . .
}
Es evidente que resulta un conjunto enorme de instrucciones independientes y reordenable sin saltos.
Todas las instrucciones se ejecutarán, independientemente del valor de Rcond. Solo que se llevarán a
efecto aquellas en las que se verifique que Rcond = 1 (es verdadera).
Ejemplo: Tenemos que tener en cuenta que esta técnica sólo es conveniente cuando el cuerpo del condicionante
tiene pocas instrucciones, en torno a 6 o 7 como máximo, ya que el código resultante obtenido ejecuta
siempre ambos conjuntos de instrucciones.
Supongamos el siguiente fragmento de código dentro de un bucle, los registros que utilizaremos:
aux = aux * 2; Raux
if (aux < v[j])
{ Rv que apunta a &v[j]
v[j] = 2 * v[j]
} else {
v[j] = 0;
w[j] = -w[j]; Rw que apunta a &w[j]
}
56
Miguel Ángel Cifredo
macifredo@gmail.com
2.4 Ejercicios.
Técnicas Estáticas.
1. Dibujar con flechas las dependencias RAW de la siguiente traza de ejecución (son dos iteraciones de un
bucle). Indicar cuales son dependencias ficticias (WAW y WAR):
LF F0,0(R1),
ADDF F4,F0,F2
SF 0(R1),F4,
SUBI R1,R1,4 // resta
BNEZ R1,BUCLE
LF F0,0(R1),
ADDF F4,F0,F2
SF 0(R1),F4,
SUBI R1,R1,4
BNEZ R1,BUCLE // salto condicional
3. Dibujar el camino crítico (cadena más larga de operaciones con dependencia real RAW). Suponemos que la
duración de toda operación es la misma.
LF F0,0(R1)
LF F2,0(R2)
MULTF F4,F0,F2
ADDF F4,F4,F2
ADDI R1, R1, 4
SUBI R2, R2, 4
SLT R4,R1,R2 // comparación
SF 0(R1),F4
BNEZ R4,BUCLE // salto condicional
4. Si se desenrollan tres iteraciones del bucle SAXPY ( variables float; cuerpo del bucle: y[i] += a * x[i] )
¿cuál sería el número de operaciones de memoria y de FP por iteración?
5. ¿Qué interesa más desenrollar el bucle interno o el externo del siguiente código?
for (f=0; f<m; f++)
for (c=0; c<m; c++)
a[f][c] = a[f][c] + b[f][c];
6. Desenrollar 3 iteraciones del bucle interno del anterior (en lenguaje de alto nivel).
57
8. Dibujar el camino crítico del bucle desenrollado anterior.
9. Indique cómo se escriben en lenguaje C los siguientes bucles para que sean paralelizables, si se utilizan varios
acumuladores parciales dentro del bucle, los cuales después se suman tras el bucle. Traducir a ensamblador
el último bucle.
for (i=0; i<1024; i++)
p[i] = p[i] + x[i] * y[i];
for (i=0; i<1024; i++)
p = p + x[i] * y[i];
10. Desenrollar 4 veces el bucle que calcula el máximo de un vector, usando máximos parciales, donde cada
máximo parcial extrae el máximo de elementos contiguos del vector (una cuarta parte):
max=a[0];
for (f=1; f<m; f++)
if (max<a[f])
max = a[f];
11. ¿Qué cantidad de cargas de memoria y de sumadores en paralelo se necesitan para poder ejecutar este bucle
en forma de árbol binario (utilizando la reasociación de sumas)?
for (f=0; f<m; f++) { suma += (a[f] * b[f]); }
12. Indique cuáles de los siguientes bucles son paralelizables directamente (traduciendo a ensamblador
directamente), y cuáles podrían paralelizarse con cambios en el código de alto nivel).
for (i=0; i<1024; i++)
x[i] = x[i] * y[i];
for (i=0; i<1024; i++)
x[i+1] = x[i] * y[i];
for (i=0; i<1024; i++)
x[i-1] = x[i] * y[i];
for (i=0; i<1024; i++)
p[i] = p[i] + x[i] * y[i];
for (i=0; i<1024; i++)
p = p + x[i] * y[i];
13. Buscar en internet un bucle que resuelva una ecuación por un método iterativo, ¿su bucle será paralelizable
o no? Dibujar su camino crítico.
15. Dibujar en un grafo el camino crítico de una sola iteración del siguiente bucle. Después dibujar un esquema
de cómo se aplicaría el método del software pipeline.
for (i=0; i<1024; i++)
p[i] = (x[i] * y[i]) + (z[i] * w[i]);
16. Pensar en qué valores deben tener c1 y c2, para que los tres siguientes bucles hagan la misma operación.
Traducir el primero de ellos a ensamblador usando instrucciones con predicación (Full predication)
for (i=0; i<1024; i++)
if (cond[i]==0) p[i] = x[i] / y[i];
else p[i] = z[i];
for (i=0; i<1024; i++)
p[i] = (x[i] / y[i]) * c1[i] + (z[i] * (!c1[i]));
for (i=0; i<1024; i++)
p[i] = (x[i] / y[i]) & c2[i] | (z[i] & (~c2[i]));
58
Miguel Ángel Cifredo
macifredo@gmail.com
Técnicas dinámicas.
1. Poner dos ejemplos en donde una BTB con correlación consiga menor porcentaje de fallos que una BTB que
sólo guarda información local de cada salto.
2. Suponiendo que la predicción de una BTB es siempre T para todo salto, hallar el porcentaje de fallos para un
bucle anidado de 10 iteraciones externas y 3 internas.
4. Hallar el camino crítico para una única iteración del anterior bucle.
6. Esbozar el cronograma del Algoritmo de Tomasulo (fases IS E1 para todas las instrucciones, excepto para
MULT que es IS E1 E2 E3) para una única iteración del anterior bucle. Suponer que se emiten 5 instrucciones
por ciclo.
8. Dibujar un cronograma de 6 instrucciones con RAW para un procesador que emiten 3 instrucciones por ciclo
donde las etapas son IS E1 E2.
9. Sea un programa que sólo contiene instrucciones enteras (etapas son IS E1) entre las cuales siempre existe
una dependencia real entre cada pareja de instrucciones consecutivas. Para el Algoritmo de Tomasulo clásico
con estaciones de reserva y que emiten 3 instrucciones por ciclo, ¿existirá bloqueo estructural por
agotamiento de RS? Calcule el CPI tras muchas instrucciones (en el estacionario).
10. Sea un bucle que contiene 1 instrucción de DIV por iteración (UF no segmentada que dura 30 ciclos). La
duración de cada iteración, ¿crecerá con el número de instrucciones de overhead del bucle?
11. Un procesador tiene 1 UF de MULT segmentada, y suponemos que ejecuta un código sólo con
multiplicaciones (que no tiene dependencia entre sí). ¿Será el CPI función de la duración de la UF de MULT?
12. Un procesador tiene 4 UF de MULT segmentadas, y suponemos que ejecuta un código sólo con
multiplicaciones (que no tiene dependencia entre sí). ¿Será el CPI función de la duración de la UF de MULT?
¿Cuál será la aceleración respecto del caso anterior?
13. Sea un bucle de 5 instrucciones que ejecuta K iteraciones. Suponiendo que se emiten 5 instrucciones/ciclo,
comparar la duración entre los casos: BTB acierta siempre, BTB falla siempre, BTB sólo falla en la última
iteración. Suponer una latencia del salto de 10 ciclos.
14. ¿Cuáles son las causas posibles de que se agoten las R.S. y que por tanto haya bloqueos de emisión?
15. Para cada causa, poner un ejemplo de un código en lenguaje de alto nivel, donde haya al menos una
instrucción que cumpla tal causa.
16. Supongamos que toda Unidad Funcional dura 1 ciclo y que se emiten 3 instrucciones/ciclo y que la BTB
siempre acierta en la predicción. Escribir un cronograma simplificado, donde se señalen las instrucciones de
la segunda iteración que necesitan que exista un ROB, porque modifican el estado del procesador (escriben
en registro o memoria).
59
17. Para el siguiente cronograma, pensar cómo habría que añadir la fase CM (con 4 instrucciones por ciclo). Las
fases E pueden tener escrituras en registro o memoria (WB o MEM).
IS E1 E2 E3
IS E1 E2 E3
IS E1
IS E1
IS E1
IS E1 E2 E3
IS E1
IS E1 E2 E3
18. Para el código dado, suponiendo que la caché y la BTB siempre acierta, calcular el número de ciclos por
iteración, sabiendo que se pueden dar estos 3 cuellos de botella. Suponer 3 instrucciones de overhead
(enteras) por iteración. ¿Cuál es el cuello de botella principal?
a) m = 5 (grado superescalaridad).
b) Sólo 2 Unidades Funcionales INT.
c) Sólo 2 puertos de caché.
double x[N], y[N], z[N], w[N], y1[N], y2[N];
for (i=0; i<3000; i++) {
y1[i] = x[i] * y[i];
y2[i] = w[i] + z[i];
}
19. Ídem que el anterior si el código se desenrolla 6 iteraciones previamente. ¿Acelera la ejecución el
desenrollado?
20. Sea un VLIW puro con p = 5 operaciones por macro-instrucción. Para el bucle SAXPY de 1000 iteraciones,
escribir como quedarían las macro-instrucciones y cuál sería su duración en ciclos, si se desenrollan 5
iteraciones y se intenta reducir el número de macro-instrucciones. Duración ADDF y MULTF 2 ciclos, resto 1
ciclo. Sólo 2 puertos de caché.
21. Sea el bucle SAXPY de 5 instrucciones útiles y 3 de overhead. Se van a estudiar el código ensamblador escalar
y el código vectorizado con registros vectoriales (multimedia) de 128 bits (sufijo ps). Hallar los GFLOPS de
ambos (frec = 3 GHz) y la aceleración A entre ambos para estos 3 casos, si m = 4 (grado superescalaridad).
60
Miguel Ángel Cifredo
macifredo@gmail.com
ILP.
1. ¿Cuál es el CPI ideal de un VLIW con 5 operaciones por instrucción y que además es superescalar de grado
m = 3?
2. ¿Cuál es el CPI ideal de un superescalar de grado m = 4, que además es superencadenado y tiene 15 etapas
por instrucción?
3. Un programa RISC tiene N instrucciones de 32 bits y se ejecuta con CPI medio de 0’5. Si el compilador es
capaz de compactar una media de 5 instrucciones en cada macro-instrucción de 256 bits (p=8) para un VLIW
puro, ¿cuál es la aceleración entre el RISC y el VLIW? Suponer igual frecuencia y que nunca fallan las cachés.
4. ¿Cuál sería la relación de tamaño de código entre el RISC y el VLIW del caso anterior?
5. Calcular el IPC real de un superescalar de grado 4, que tiene CPI bloqueo = 0’5
6. Hallar la relación de CPI entre caso ideal y real para un superescalar de grado m, con CPI bloqueo = c
7. Si el grado de superescalaridad de un procesador es m pero solo ejecuta m-1 instrucción por ciclo, sería
correcta esta afirmación: “Podemos hablar de un IPC bloqueo de 1 instrucciones/ciclo, de manera que el
IPC real = m – IPC bloqueo”.
8. Si el CPI bloqueo de una máquina es 0’5, y su IPC ideal es 3, entonces el IPC real es: IPC ideal – 2 = 1, puesto que el
IPC bloqueo es la inversa del CPI bloqueo (1/0’5) ¿es correcto este cálculo?
9. A pesar del ejercicio anterior, recuerde que los cálculos deben hacerse con el CPI (puesto que siempre se
pueden sumar diferentes CPI, pero casi nunca se pueden sumar los IPC). Calcular entonces el CPI bloqueo
del ejercicio anterior.
10. ¿Cuál es el número de macroinstrucciones en vuelo para un RISC (planificación estática) de grado m de
superescalaridad, 15 etapas, y que es VLIW con p operaciones por macroinstruccion? ¿y el número de
operaciones en vuelo?
- El compilador prepara las instrucciones para que las emita el procesador sin comprobar nada.
- El procesador no emite las instrucciones nunca.
- El procesador prepara las instrucciones reordenándolas antes de emitirlas.
- Ninguna de las otras es correcta.
- El compilador prepara las instrucciones para que las emita el procesador sin comprobar nada.
- El procesador no emite las instrucciones nunca.
- El procesador prepara las instrucciones reordenándolas antes de emitirlas.
- Ninguna de las otras es correcta.
13. Para un DLX con Algoritmo de Tomasulo, especulación dinámica y fases IF IS EX WB, qué características tiene:
- In-order-execution.
- Out-of-order-execution.
- Static issue.
- Dynamic issue.
61
14. Para un DLX con Algoritmo de Tomasulo y fases IF IS EX WB, qué frase/s son verdad para su BTB:
15. Clasificar los tipos de procesadores con ILP según sean la planificación y la emisión estáticas o dinámicas.
16. Ordenar de mayor a menor consumo de potencia, los procesadores: VLIW, superescalar estático,
superescalar dinámico.
17. ¿Por qué no es cierto que un superencadenado con frecuencia 3 veces mayor que el procesador que no lo
es, sea 3 veces más rápido?
DLP.
1. ¿Cuál es la aceleración ideal para registros multimedia de 256 bits, si se usan variables float? ¿Y double?
2. Un bucle vectorizado ejecuta 4 veces menos iteraciones que su versión escalar, ¿ejecuta también 4 veces
menos accesos a memoria? ¿y consume también 4 veces menos ancho de banda ABRAM?
62
Miguel Ángel Cifredo
macifredo@gmail.com
P1. Sea el bucle SAXPY de 5 instrucciones útiles y 3 de overhead. Se van a estudiar el código ensamblador escalar
y el código vectorizado con registros vectoriales (multimedia) de 128 bits (sufijo ps). Hallar los GFLOPS de ambos
(frecuencia = 3 GHz) y la aceleración A entre ambos para estos 3 casos, si m = 4 (grado superescalaridad).
b) La caché falla 1 de cada 48 accesos (float) y hay una penalidad de 20 ciclos de bloqueo por fallo.
c) Las cachés fallan mucho, de forma que los accesos a RAM (ABRAM de 4 GB/s) son el cuello de botella.
Solución:
El bucle escalar SAXPY viene definido por las siguientes instrucciones de bajo nivel:
63
Apartado b) La caché falla 1 de cada 48 accesos (float) y hay una penalidad de 20 ciclos de bloqueo por fallo.
16 iteraciones
Escalar ≡ [
1 iteración tiene 3 accesos
El “patrón” de ejecución para los 48 accesos sería
4 iteraciones
Vectorial ≡ [
{ 1 iteración tiene 12 accesos
Apartado c) Las cachés fallan mucho, de forma que los accesos a RAM (ABRAM de 4 GB/s) son el cuello de botella.
La degradación será total cuando el acceso a RAM sea el cuello de botella. Partiendo de tal cuello
tenemos, tanto para escalar como para vectorial:
Estas dos relaciones son inherentes al SAXPY
tanto Vectorial como Escalar
⏞ 𝑀𝑈𝐿𝑇𝐷,𝐴𝐷𝐷𝐷
ABRAM
( ⏞
2 ) FLOP
⏞4 GBytes 1 elto. procesado GFLOPS
GFLOPS = ∙ ∙ = 0'6̂
1 segundo 1 elto. procesado segundo
( ⏟
3 ) ∙ 4 Bytes
𝐿𝐹,𝐿𝐹,𝑆𝐹
64
Miguel Ángel Cifredo
macifredo@gmail.com
b) Hallar el ancho de banda de acceso a la RAM (AB RAM) mínimo para que no suponga cuello de botella.
c) Si se desenrollaran 4 iteraciones con acumuladores parciales, ¿Cuáles serían los valores para los
apartados anteriores a.2 y b?
Solución:
Apartado b) Hallar el ancho de banda de acceso a la RAM (AB RAM) mínimo para que no suponga cuello de botella.
1 iteración 1 LD GBytes 1 segundo ≥ 1 iteración
∙ ∙ AB ∙
⏟ 1 LD 8 Bytes segundo 2 Gciclos ⏟ ⏟4 ciclos
Cuello de botella del acceso a RAM debe ser más rápido caso a.2
Ahora el nuevo camino crítico es el de cualquier acumulador, pero en una iteración se hacen 4
sumas (4 elementos de X[i]), luego el número de ciclos por iteración no cambia, sigue siendo:
4 ciclos
1 iteración de 4 ADDD
Para el apartado b)
Se consume en todos 4 ciclos, esto es, 4 accesos a datos de tipo Double, luego:
1 iteración (desenrollada) 1 LD GBytes 1 segundo 1 iteración (desenrollada)
∙ ∙ AB ∙ ≥
4 LD 8 Bytes segundo 2 Gciclos 4 ciclos
Por tanto, se despeja AB (que está expresado en GBytes/segundo) y se obtiene:
GBytes
AB ≥ 16
segundo
66
Miguel Ángel Cifredo
macifredo@gmail.com
Se han elegido aleatoriamente bucles del libro Numerical Recipes in C: The Art of Scientific Computing. Second
Edition, William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery.
Pistas:
- El ejercicio 2 tiene dependencia WAR en memoria d[k-1] con respecto a d[k], pero es paralelizable.
- El ejercicio 4 tiene la notación de C de puntero que se incrementa sp1 += 2 para cambiar de elementos,
pero es también paralelizable.
- El ejercicio 5 tiene un for con if() dentro, por lo que necesitaríamos estudiar instrucciones predicadas.
67
Ejercicio 4. Transformadas de Fourier de datos reales en dos y tres dimensiones (Fourier Transforms of Real Data
in Two and Three Dimensions).
// Note how this can be made a single for-loop
// in- stead of three nested ones by using the pointers sp1 and sp2.
for (j=1; j<=N*N*N/2; j++) {
r = sp1[0]*sp2[0] - sp1[1]*sp2[1];
i = sp1[0]*sp2[1] + sp1[1]*sp2[0];
sp1[0] = fac * r;
sp1[1] = fac * i;
sp1 += 2;
sp2 += 2;
}
Ejercicio 5. Cuasi (es decir, Sub) secuencias aleatorias (Quasi- (that is, Sub-) Random Sequences).
for (k=1; k<=MAXDIM; k++) {
//Stored values only require normalization.
for (j=1; j<=mdeg[k]; j++)
iu[j][k] <<= (MAXBIT-j);
// Use the recurrence to get other values.
for (j=mdeg[k]+1; j<=MAXBIT; j++) {
ipp = ip[k];
i = iu[j-mdeg[k]][k];
i ^= (i >> mdeg[k]);
for (l=mdeg[k]-1; l>=1; l--) {
if (ipp & 1) i ^= iu[j-l][k];
ipp >>= 1;
}
iu[j][k] = i;
}
}
}
68
Miguel Ángel Cifredo
macifredo@gmail.com
3.1 Introducción.
Los arquitectos de computadores han buscado durante mucho tiempo El Dorado del diseño de los
computadores: crear sistemas potentes simplemente conectando muchos computadores más pequeños y
resistentes. Esta visión tomada es el origen de los multiprocesadores. Los clientes encargan tantos procesadores
como puedan permitirse y reciben un computador con prestaciones proporcionales al número de procesadores.
Para ello, el software de los multiprocesadores debe diseñarse para funcionar correctamente con un número
variable de procesadores. Como ya sabemos, el consumo de potencia se ha convertido en el principal enemigo
de los microprocesadores y por tanto de los centros de datos. Se pueden obtener mejores prestaciones por vatio
y/o por julio reemplazando los procesadores grandes e ineficientes por muchos procesadores más pequeños y
eficientes, siempre y cuando el software sea capaz de aprovecharlos al máximo. Así, en los multiprocesadores se
unen la mejora de la eficiencia energética y la escalabilidad de las prestaciones.
Como el software de los multiprocesadores debe ser escalable, algunos diseños mantienen su operatividad aún
en presencia de fallos en el hardware, es decir, si en un multiprocesador con n procesadores falla uno, el sistema
podría continuar funcionando correctamente con n - 1 procesadores, y así mejorar la disponibilidad.
El término “altas prestaciones” puede significar alta productividad de trabajos independientes, lo que se llama
paralelismo a nivel de trabajos o paralelismo a nivel de procesos. Estos trabajos paralelos son aplicaciones
independientes y actualmente constituyen una carga de trabajo importante y habitual de los computadores
paralelos. Utilizaremos el término programa de procesamiento paralelo para referirnos a un programa que se
ejecuta en varios procesadores simultáneamente.
En las últimas décadas ha habido grandes problemas científicos que han necesitado computadores mucho más
rápidos que los disponibles en cada momento, y estos problemas han servido para justificar muchos diseños de
computadores paralelos. Algunos de estos problemas pueden ser resueltos con un clúster formado por
microprocesadores alojados en servidores o PCs independientes. Además, los clústeres son útiles también para
otras aplicaciones no científicas, tales como motores de búsqueda, servidores web y bases de datos.
El problema del consumo de potencia han propiciado que los multiprocesadores sean el centro de atención, y
los futuros aumentos de las prestaciones aparentemente pasan por poner más procesadores en cada chip, en
lugar de disponer de mayores frecuencias de reloj o mejores CPI. Estos sistemas se llaman microprocesadores
multinúcleo en lugar de microprocesadores multiprocesador, probablemente para evitar nombres redundantes.
En consecuencia, los procesadores en un chip multinúcleo se llaman simplemente núcleos. La tendencia actual
es duplicar el número de núcleos cada dos años y, por tanto, los programadores tienen que preocuparse del
rendimiento o reconvertirse en programadores paralelos, ya que programa secuencial equivale a programa lento.
El principal reto que debe afrontar la industria de los computadores es crear hardware y software para facilitar
la escritura de programas de procesamiento paralelo, que se ejecuten eficientemente en términos de
prestaciones y consumo de potencia a medida que el número de núcleos escala geométricamente.
Este giro repentino en el diseño de los microprocesadores ha pillado desprevenido a muchos, por lo que hay
mucha confusión en la terminología y en lo que está significa. En la siguiente figura se intenta clarificar los
términos serie, paralelo, secuencial y concurrente. Las columnas representan el software que es inherentemente
secuencial o concurrente, mientras que las filas representan el hardware que es serie o paralelo. Por ejemplo,
los programadores de compiladores piensan que este como un proceso secuencial: los pasos son análisis léxico,
análisis sintáctico, generación de código, optimización, etc. Por el contrario, los programadores de sistemas
operativos piensan en ellos como un conjunto de programas concurrentes: procesos cooperantes que procesan
sucesos de E/S de trabajos independientes que se ejecutan en un computador.
69
Software
Secuencial Concurrente
Multiplicación de matrices en MatLab Sistema Operativo Windows Seven
Serie
ejecutándose en un Intel Pentium 4. ejecutándose en un Intel Pentium 4.
Hardware
Multiplicación de matrices en MatLab Sistema Operativo Windows Seven
Paralelo
ejecutándose en un Intel Xeon e5345. ejecutándose en un Intel Xeon e5345.
Categorización de hardware y software y ejemplos del punto de vista de la aplicación sobre concurrencia y el punto de vista del
hardware sobre paralelismo.
En la tabla anterior podemos destacar que el software concurrente puede ejecutarse en un hardware serie, como
el sistema operativo en el miocroprocesador Intel Pentium 4, o en un hardware paralelo, como el sistema
operativo en el procesador Intel Xeon e5345 (Clovertown). Lo mismo podemos decir de un software secuencial.
Por ejemplo, el programador de MatLab recibe una multiplicación de matrices pensando secuencialmente, pero
la puede ejecutar en serie en el Pentium 4, o en paralelo, en el Xeon e5345. Se podría pensar que el único reto
de la revolución paralela es averiguar cómo conseguir que el software secuencial tenga altas prestaciones en un
hardware paralelo, pero además hay que conseguir que los programas concurrentes tengan altas prestaciones
cuando se ejecutan en un multiprocesador y que éstas aumenten con el número de procesadores. Teniendo en
mente esta distinción, hablaremos de programa de procesamiento paralelo o software paralelo para referirnos
a software secuencial o concurrente que se ejecuta en un hardware paralelo.
La dificultad del paralelismo no está en el hardware; la principal dificultad es que muy pocas aplicaciones se han
reescrito para completar sus tareas en menos tiempo en un multiprocesador. Es difícil escribir software que
utilice varios procesadores para terminar una tarea de forma más rápida, y el problema empeora al aumentar el
número de procesadores.
¿Por qué esto es así? ¿Por qué es más difícil desarrollar programas de procesamiento paralelo que programas
secuenciales? La razón principal es que las prestaciones y la eficiencia de un programa de procesamiento paralelo
en un multiprocesador tienen que ser mejores; en caso contrario es preferible utilizar un programa secuencial
en un monoprocesador, ya que la programación es más sencilla. De hecho, las técnicas de diseño de
monoprocesadores, tales como superescalar y ejecución fuera de orden, aprovechan el paralelismo a nivel de
instrucción, normalmente sin la participación del programador. Estas técnicas disminuyen la demanda de
reescritura de programas para multiprocesadores, porque sin que los programadores tengan que hacer nada
más, sus programas secuenciales se ejecutarán más rápido en los nuevos computadores.
¿Por qué es difícil escribir programas de procesamiento paralelo que sean rápidos, especialmente cuando
aumenta el número de procesadores? Usando la analogía de ocho periodistas intentando escribir un artículo con
la esperanza de hacer el trabajo ocho veces más rápido, la tarea debe dividirse en ocho partes de la misma
complejidad, porque en caso contrario algunos periodistas estarían sin hacer nada, esperando a que terminase
en los que tienen las partes más complejas. Otro riesgo para las prestaciones se produciría cuando los periodistas
tienen que emplear demasiado tiempo en comunicarse con sus compañeros en lugar de trabajar en su parte del
artículo. Podemos extraer algunas conclusiones de esta analogía con la programación paralela, los desafíos
incluyen la planificación de las tareas, el equilibrio de la carga, el tiempo de sincronización y la sobrecarga de las
comunicaciones entre colaboradores. El reto es todavía más difícil cuantos más periodistas participen en la
escritura del artículo y cuantos más procesadores estén disponibles para la programación paralela. Aún existe
otro obstáculo, la Ley de Amdahl, que nos recuerda que incluso las partes pequeñas de un programa deben
paralelizarse si se quiere hacer un buen uso de los muchos núcleos disponibles. Aun así hay aplicaciones con
abundante paralelismo.
70
Miguel Ángel Cifredo
macifredo@gmail.com
Suponga que queremos que un programa se ejecute 90 veces más rápido con 100 procesadores. ¿Qué
parte del programa original puede ser secuencial?
Esta ecuación se reescribe considerando que el tiempo de ejecución antes es 1 para alguna unidad de
tiempo y expresando el tiempo de ejecución afectado por la mejora como un porcentaje del tiempo de
ejecución original:
1
Aceleración =
Tiempo de ejecución afectado
(1 - Porcentaje tiempo afectado) +
100
Es decir, para obtener una aceleración igual a 90 con 100 procesadores, el porcentaje secuencial debe
ser sólo del 0’1%
71
Ejemplo: El reto de la aceleración → El mayor problema.
Suponga que queremos hacer dos sumas: una suma de 10 variables escalares y una suma matricial de
dos matrices bidimensionales de 10 x 10. ¿Qué aceleración se obtendrá con 10 y 100 procesadores?
Ahora calcule la aceleración suponiendo que las matrices son de 100 x 100.
Si aceptamos que las prestaciones son función del tiempo de suma, t, entonces hay 10 sumas que no se
benefician de la utilización de procesadores paralelos y 100 que sí. Si el tiempo en un procesador es 110t,
el tiempo de ejecución con 10 procesadores es:
Tiempo de ejecución afectado por la mejora
Tiempo de ejecución después de la mejora = + Tiempo de ejecución no afectado
Total de la mejora
100t
Tiempo de ejecución afectado de la mejora = + 20t = 20t
10
Entonces la aceleración con 10 procesadores es 110t / 20t = 5’5. El tiempo de ejecución con 100
procesadores es:
100t
Tiempo de ejecución después de la mejora = + 10t = 11t
100
Así, para este tamaño de problema, se obtiene el 55% de la máxima aceleración posible con 10
procesadores, pero sólo el 10% con 100.
Veamos qué ocurre cuando aumentamos las dimensiones de la matriz. El tiempo de ejecución del
programa secuencial ahora es 10t + 10000t = 10010t. El tiempo de ejecución con 10 procesadores es:
10000t
Tiempo de ejecución después de la mejora = + 10t = 1010t
10
Entonces la aceleración con 10 procesadores es 10010t / 1010t = 9’9. El tiempo de ejecución con 100
procesadores es:
10000t
Tiempo de ejecución después de la mejora = + 10t = 110t
100
Entonces la aceleración con 100 procesadores es 10010t / 110t = 91. Así, para un tamaño de problema
mayor, obtenemos aproximadamente el 99% de la máxima aceleración posible con 10 procesadores, y
más del 90% con 100.
72
Miguel Ángel Cifredo
macifredo@gmail.com
En el ejemplo anterior, para obtener una aceleración de 91 con 100 procesadores, se supuso que la carga
estaba perfectamente equilibrada. Es decir, cada procesador hacía el 1% del trabajo total. Muestre qué
impacto tiene sobre la aceleración que un procesador tenga más carga que el resto. Calcule con el 2% y el 5%.
Si un procesador tiene el 2% de la carga, entonces tiene que hacer 2% x 10000 = 200 sumas, y los otros
99 procesadores se reparten las 9800 sumas restantes. Como están trabajando simultáneamente,
podemos calcular el tiempo de ejecución como un máximo.
9800t 200t
Tiempo de ejecución después de la mejora = max ( , ) + 10t = 210t
99 1
La aceleración cae hasta 10101t / 210t = 48. Si un procesador tiene el 5% de la carga, debe hacer 500
sumas:
9500t 500t
Tiempo de ejecución después de la mejora = max ( , ) + 10t = 510t
99 1
Este ejemplo pone de manifiesto la importancia del equilibrio de la carga; un procesador con el doble de
carga que el resto hace caer la aceleración casi a la mitad, y con una carga 5 veces la del resto reduce la
aceleración casi en un factor 5.
Difícil determinar un proceso paralelizable. Se trata de un proceso artesanal que se podrá automatizar
sólo si es muy evidente, ya que resulta complejo determinar si una aplicación es o no paralelizable.
Lentitud en las comunicaciones: Se trata de sistemas con alta latencia, del orden de microsegundo,
cuando tenemos que tener en cuenta que la latencia a memoria local es del orden de pocos
nanosegundos. Si un procesador necesita un dato remoto el rendimiento decrece drásticamente.
73
Para comprender el impacto de los diferentes factores de rendimiento en un programa paralelo sobre una
arquitectura paralela, es habitual mirarlo desde un punto de vista de procesador individual con todos su
diferentes componentes empleando tiempo de ejecución en el programa, esto es, cuánto tiempo de procesador
emplea en las diferentes actividades para la ejecución de las instrucciones, acceso a los datos en la jerarquía de
la memoria extendida, y coordinar estas actividades con otros procesos. Estos componentes de tiempo pueden
estar relacionados muy directamente con las capacidades de rendimiento del software estudiado en los
apartados anteriores, que nos han ayudado a entender las técnicas de rendimiento software y de hardware. Esta
visión también nos ayuda a comprender cómo es una ejecución paralela.
En la figura (a) se muestra un perfil de un hipotético programa secuencial. En este caso, cerca del 80% del tiempo
de ejecución se emplea en instrucciones de rendimiento, que puede reducirse sólo mediante la mejora ya sea
del algoritmo o del procesador. El otro 20% se emplea bloqueando la memoria del sistema, que puede ser
mejorado mediante la mejora de localidad de datos o el sistema de memoria.
En multiprocesadores, podemos tener una vista similar, en la que no hay tantos factores no ideales. Este punto
de vista es transversal a los modelos de programación: por ejemplo, hay bloqueos de espera para una recepción
completa que es muy similar a que se bloquee a la espera de una lectura remota completa o de que ocurra un
evento de sincronización. Si el mismo programa se paraleliza y se ejecuta en una máquina con cuatro
procesadores, el perfil del tiempo de ejecución para los cuatro procesadores sería el mostrado en la figura (b).
La figura asume un punto de sincronización global al final del programa que hace que los procesos terminen al
mismo tiempo. Nótese que el tiempo de ejecución paralela (55 s) es mayor que un cuarto del tiempo de
ejecución secuencial (100 s); esto es, hemos obtenido una aceleración de sólo 100/55 o 1’8, en lugar de una
aceleración cuádruple que es lo que podríamos esperar. ¿Por qué ocurre esto y qué factores del software o de
programación específicos contribuyen a que se pueda determinar mediante el examen de los componentes de
tiempo de ejecución paralela desde la perspectiva de un procesador individual?
1) Busy-useful. Tiempo que el procesador emplea ejecutando instrucciones que habrían sido ejecutadas
igualmente en el programa secuencial. Asumimos un programa paralelo determinista que deriva
directamente de un algoritmo secuencial, la suma de los tiempos de ocupación útil para todos los
procesadores es el mismo que el tiempo de ocupación útil para una ejecución secuencial.
2) Busy-overhead. Tiempo que el procesador emplea en ejecutar instrucciones que no son necesarias en el
programa secuencial pero sí en el programa paralelo. Esto se corresponde directamente en un trabajo
extra que realiza el programa paralelo.
3) Data-local. Tiempo que el procesador está bloqueado esperando el dato referenciado para ser satisfecho
en la memoria del sistema o para su propio nodo de procesado, esto es, espera para una referencia que
no genera comunicaciones con otros nodos.
4) Data-remote. Tiempo que el procesador está bloqueado esperando por un dato que debe ser
comunicado hacia o desde otros nodos de procesamiento (remotos), ya sea por elementos inherentes o
impropios a la comunicación. Esto representa el coste de la comunicación visto por el procesador.
74
Miguel Ángel Cifredo
macifredo@gmail.com
Flujos de Datos
Simple Múltiple
Categorización del hardware, con ejemplos, según en el número de flujos de instrucciones y de datos según la taxonomía de Flynn.
SISD (Single Instruction stream, Single Data stream) Flujo único de instrucciones y Flujo único de datos. Este es
el concepto de arquitectura serie de Von Neumann donde, en cualquier momento, sólo se está ejecutando una
única instrucción. A menudo a los SISD se les conoce como computadores serie escalares. Todas las máquinas
SISD poseen un registro simple que se llama contador de programa (Program Counter, PC) que asegura la
ejecución secuencial (en serie) de las instrucciones del programa. Conforme se van leyendo las instrucciones de
la memoria, el contador de programa se actualiza para que apunte a la siguiente instrucción a procesar en serie.
Prácticamente ningún computador puramente SISD se fabrica hoy en día ya que la mayoría de procesadores
modernos incorporan algún grado de paralelización como es la segmentación de instrucciones o la posibilidad
de lanzar dos instrucciones a un tiempo (superescalares).
MISD (Multiple Instruction stream, Single Data stream) Flujo múltiple de instrucciones y Flujo único de datos.
Esto significa que varias instrucciones actúan sobre el mismo y único flujo de datos. Este tipo de máquinas se
pueden interpretar de dos maneras. Una es considerar la clase de máquinas que requerirían que unidades de
procesamiento diferentes recibieran instrucciones distintas operando sobre los mismos datos. Esta clase de
arquitectura ha sido clasificada por numerosos arquitectos de computadores como impracticable o imposible, y
en estos momentos no existen ejemplos que funcionen siguiendo este modelo. Otra forma de interpretar los
MISD sería como una clase de máquinas donde un mismo flujo de datos fluye a través de numerosas unidades
procesadoras. Arquitecturas altamente segmentadas, como los arrays sistólicos o los procesadores vectoriales,
son clasificadas a menudo bajo este tipo de máquinas. Las arquitecturas segmentadas, realizan el procesamiento
vectorial a través de una serie de etapas, cada una ejecutando una función particular produciendo un resultado
intermedio. La razón por la cual dichas arquitecturas son clasificadas como MISD es que los elementos de un
vector pueden ser considerados como pertenecientes al mismo dato, y todas las etapas segmentadas
representan múltiples instrucciones que son aplicadas sobre ese vector.
SIMD (Single Instruction stream, Multiple Data stream) Flujo de instrucción simple y Flujo de datos múltiple. Esto
significa que una única instrucción es aplicada sobre diferentes datos al mismo tiempo. En las máquinas de este
tipo, las diferentes unidades de procesado son invocadas por una única Unidad de Control. Al igual que las MISD,
las SIMD soportan procesamiento vectorial (matricial) asignando cada elemento del vector a una unidad
funcional diferente para procesamiento concurrente. Por ejemplo, el cálculo del salario para cada trabajador en
una empresa, es repetir la misma operación sencilla para cada trabajador; si se dispone de una arquitectura
SIMD esto se puede calcular en paralelo para cada trabajador. Debido a esta facilidad en la paralelización de
vectores de datos (los trabajadores formarían un vector) se les llama también procesadores matriciales.
75
MIMD (Multiple Instruction stream, Multiple Data stream) Flujo de instrucciones múltiple y Flujo de datos
múltiple. Son máquinas que poseen varias unidades procesadoras en las cuales se pueden realizar múltiples
instrucciones sobre datos diferentes de forma simultánea. Las MIMD son las más complejas, pero son también
las que potencialmente ofrecen una mayor eficiencia en la ejecución concurrente o paralela. Aquí la concurrencia
implica que no sólo hay varios procesadores operando simultáneamente, sino que además hay varios programas
(procesos) ejecutándose también al mismo tiempo, conocido como multiComputador (como es el clúster). Es
posible escribir programas separados que se ejecuten en los diferentes procesadores de un computador MIMD
y conseguir que colaboren para la consecución de un objetivo coordinado más ambicioso. Sin embargo, lo más
habitual es que los programadores escriban un único programa que se ejecute en todos los procesadores del
computador MIMD, introduciendo secuencias condicionales para conseguir que procesadores diferentes
ejecuten secciones de código diferentes. Este estilo de programación se llama Programa Único Datos Múltiples
(Single Program Multiple Data, SPMD), y es el estilo habitualmente utilizado en los computadores MIMD.
Esquemas funcionales de los cuatro tipos de máquinas clasificadas por flujos de instrucciones y de datos:
76
Miguel Ángel Cifredo
macifredo@gmail.com
Modelo de comunicación
(Disposición lógica – Espacio de direcciones)
Espacio de direcciones Espacio de direcciones
compartido separado / privado
Centralizada
Conexión física UMA - - Poco escalables
en Bus
de la
memoria principal - Distinto software.
Distribuida
(Disposición física) NUMA MPM - Casi igual hardware.
en Red
- Muy escalables.
multiProcesadores multiComputadores
- Comunicación por - Comunicación por paso
variables compartidas. de mensajes.
- Igual software. - Procesos en paralelo.
- Distinto hardware.
- Hilos en paralelo.
Los factores que definen el diseño de un MIMD vienen determinados por el número de procesadores, la conexión
física de la memoria interconectada entre ellos y el modelo de comunicación y compartición de los datos.
Ventajas:
- Tanto los datos, como la sincronización y la coordinación usan variables globales.
- Utiliza un modelo simple de programación.
- Cuenta con un espacio único de memoria.
- Existe una sola copia del sistema operativo (con planificador adecuado).
- Se recurre al uso de hilos de ejecución (threads).
- Los sistemas operativos modernos coordinan la distribución de los recursos.
- Es fácil mover procesos entre procesadores.
- Requieren menos espacio.
- Consumen menos potencia.
- Son más estables.
Inconvenientes:
- Peor rendimiento (performance) de la memoria, que se soluciona con las memorias cachés, pero aparece
el problema de coherencia entre las memorias cachés.
- La red de interconexión (acceso a memoria) es complicada apareciendo modelos dinámicos: bus,
crossbar o multistage, que se saturan rápidamente.
- Soportan pocos procesadores (2-32).
- Resultan ser poco escalables.
77
3.3.1 Arquitectura MultiProcesadores UMA.
Dada la dificultad de reescribir los programas antiguos para que se ejecuten correctamente en un hardware
paralelo, la pregunta natural que cualquiera podría hacerse es qué hacen los diseñadores de computadores para
facilitar esta tarea. Una respuesta a esta pregunta es que, en algunos sistemas, proporcionan un espacio de
direcciones físicas único compartido por todos los procesadores. Así, los programas no tienen que preocuparse
de dónde se van a ejecutar, ya que lo único que necesitan saber es que se van a ejecutar en paralelo. Con este
enfoque, podemos hacer que todas las variables de un programa estén disponibles para todos los procesadores
en cualquier momento. La alternativa es tener espacios de direcciones separados para cada procesador, donde
la compartición debe hacerse en modo explícito. Cuando el espacio de direcciones es común (lo usual en los
chips multinúcleo) el hardware proporciona coherencia de caché para tener una visión coherente de la memoria
compartida.
Se trata de procesadores con su caché propia que se conectan a un bus común para acceder a los recursos
compartidos (memoria, E/S, etc.), por lo que la memoria está físicamente centralizada. Estos sistemas se llaman
multiprocesadores con acceso a memoria uniforme (Uniform Memory Access, UMA) o bien, multiprocesadores
simétricos (Symetric MultiProcessor, SMP).
El tiempo en acceder a la memoria principal es siempre el mismo ya que se encuentra situada en el mismo punto
del sistema, este acceso es el cuello de botella. Cada acceso requiere disponer del bus por lo que se genera
mucho tráfico. Por este motivo los sistemas que emplean la conexión en bus suelen ser poco escalables (de 2 a
32 procesadores). Un mayor número exigiría un elevado ancho de banda en el bus, esto es, el ancho de banda
solicitado por cada procesador crece cada vez más. Sin embargo, este elevado tráfico se reduce gracias a las
memorias cachés que cada procesador posee. Puesto que comparten la memoria principal, pero cada uno posee
su propia memoria caché, es necesario disponer de mecanismos de coherencia de memorias cachés que
mantenga la coherencia de los datos. Para ello se utilizan protocolos de husmeo (snooping) como ISX (Invalid -
Línea inválida, Shared – Línea compartida, eXclusive – Línea exclusiva) similar al de Copy Back. Existe una
comunicación implícita (variables compartidas) en una misma línea replicada en varios procesadores, por lo que
se requiere de hardware adicional para solucionarlo.
Como normalmente los procesadores que operan en paralelo comparten datos, es necesario introducir una
coordinación en la operación sobre los datos compartidos; de lo contrario, un procesador podría comenzar a
operar sobre un dato antes de que otro procesador terminase de modificar ese mismo dato. Esta coordinación
se llama sincronización. Cuando la compartición se lleva a cabo en un espacio de direcciones único, es necesario
disponer de un mecanismo separado de sincronización. Una de las alternativas es el uso de bloqueos (lock) para
una variable compartida. En cada instante solo un procesador puede hacerse con el bloqueo, y el resto de los
procesadores interesados en acceder a la variable compartida deben esperar hasta que el primero desbloquee
la variable.
78
Miguel Ángel Cifredo
macifredo@gmail.com
Estos sistemas se denominan multiprocesadores con acceso a memoria no uniforme (NonUniform Memory
Access, NUMA), en los que, desde un punto de vista lógico, todas las memorias forman un espacio de direcciones
común a todos los procesadores.
La siguiente figura muestra la organización clásica de un multiprocesador con múltiples espacios de direcciones
privados.
Red de interconexión
Organización clásica de un multiprocesador con múltiples espacios de direcciones privados. Observe que la red de
interconexión no se encuentra entre las cachés y la memoria, sino entre nodos procesador-memoria.
Cada procesador posee una memoria física propia, por lo que la terna procesador-caché-memoria forma un nodo
que se conecta a otros procesadores a través de la red. La memoria está lógicamente distribuida entre los
diversos nodos.
Los direccionamientos a memoria sólo acceden a la red de interconexión si el dato no está en su propia memoria
física, lo que supone aportar menos tráfico por procesador a la misma. Consecuentemente, este tipo de
arquitectura es más escalable admitiendo un mayor número de procesadores.
Además, los accesos a memoria son más rápidos (menor latencia), ya que los direccionamientos a su propia
memoria física no suponen acceder a la red de interconexión. Sin embargo, en términos generales, el tiempo de
acceso a memoria es variable (no uniforme), pues dependerá de la memoria a la que deba dirigirse.
Como era de esperar, es más difícil programar un multiprocesador NUMA que un multiprocesador UMA, y como
el código es compatible, puede aprovecharse. Pero los sistemas NUMA escalan mejor a tamaños (número de
procesadores) más grandes y tienen latencias de acceso a memoria menores que el acceso a la memoria cercana
al procesador.
La coherencia de cachés es más difícil de mantener en hardware, por lo que se utilizan directorios. Obliga a estar
atentos con las falsas comparticiones (false sharing).
Hace 10 años eran sistemas poco habituales, se utilizaban como supercomputadores para la investigación. Sin
embargo, hoy en día contamos con Intel Core con QuickPath Interconnect y AMD Opteron con HyperTransport.
79
3.3.3 Arquitectura MultiComputadores MPM.
La alternativa a la compartición del espacio de direcciones es que cada procesador disponga de su propio espacio
de direcciones físicas privado. Las comunicaciones se tienen que hacer mediante un explícito paso de mensajes
(Message Passing Machines, MPM) que tradicionalmente es el nombre que recibe este tipo de computadores.
Dado que se dispone de rutinas para enviar y recibir mensajes, la coordinación se realiza con el paso de mensajes,
ya que un procesador sabe cuándo se ha enviado un mensaje y el procesador que lo recibe sabe cuándo le llega
un mensaje. Si el procesador que envía un mensaje necesita confirmación de que el mensaje ha llegado a su
destinatario, el procesador que lo recibe debe enviar un mensaje de confirmación al remitente.
Ha habido varios intentos de construir computadores de altas prestaciones basados en redes de paso de
mensajes de altas prestaciones, que ofrecían comunicaciones con mejores prestaciones que los clústeres con
redes de área local. El problema fue que eran mucho más caros. Dado este elevado coste, pocas aplicaciones
podrían justificar la necesidad de unas comunicaciones con mejores prestaciones. De este modo, los clústeres se
convirtieron en el ejemplo de computador paralelo de paso de mensajes más extendido en la actualidad. Los
clústeres son generalmente un conjunto de computadores convencionales conectados a través de sus puertos
de E/S utilizando conmutadores de red y cables estándar. Cada uno ejecuta una copia diferente del sistema
operativo. Los servicios de internet actuales confían en clústeres de servidores y conmutadores convencionales.
Una de las desventajas de los clústeres ha sido que el coste de administrar un clúster con n computadores es
prácticamente el mismo que el de administrar n computadores independientes, mientras que el coste de
administrar un multiprocesador de memoria compartida con n procesadores es aproximadamente el mismo que
el de administrar un único computador.
Esta debilidad es una de las razones de la popularidad de las máquinas virtuales, que facilitan la administración
de los clústeres. Por ejemplo, las máquinas virtuales hacen imposible parar y comenzar programas
atómicamente, lo que simplifica las actualizaciones de software. Las máquinas virtuales pueden incluso trasladar
en un clúster un programa de un computador a otro sin detener el programa en caso de un fallo del hardware.
80
Miguel Ángel Cifredo
macifredo@gmail.com
Otra desventaja de los clústeres es que los procesadores están normalmente conectados a través de los puertos
de E/S de cada computador, mientras que los núcleos de un multiprocesador están conectados a través de los
puertos de conexión con memoria, que tienen mayor ancho de banda y menor latencia, que permiten por tanto,
una comunicación con prestaciones mucho más elevadas.
Una debilidad final es el sobrecoste de la división de la memoria: un clúster con n máquinas tiene n memorias
independientes y n copias del sistema operativo, mientras que un multiprocesador de memoria compartida
permite que un único programa utilice casi toda la memoria del computador y sólo necesita una copia del sistema
operativo.
Uno de los puntos débiles del clúster, es tener memorias separadas para la memoria principal de usuario, que se
convierte en una ventaja para la disponibilidad del sistema. Como un clúster es un conjunto de computadores
independientes conectados a través de una red de área local, es mucho más fácil remplazar una máquina sin
apagar el sistema. Fundamentalmente, las direcciones compartidas implican que es difícil aislar un procesador y
reemplazarlo sin que el sistema operativo tenga que hacer un esfuerzo heroico. Por el contrario, como el
software del clúster es una capa que se ejecuta sobre el sistema operativo local de cada computador, es mucho
más sencillo desconectar y reemplazar la máquina estropeada.
Puesto que los clústeres se construyen con computadores completos y redes escalables independientes, este
aislamiento facilita además la expansión del sistema sin necesidad de cerrar la aplicación que se está ejecutando
en la capa más elevada del clúster.
Los computadores que realizan la comunicación con paso de mensajes en lugar de memoria son mucho más
fáciles de diseñar. La ventaja, desde el punto de vista del programador, es la comunicación explícita, lo que
significa que hay menos sorpresas en las prestaciones que con los computadores con memoria compartida. El
inconveniente es la portabilidad, es más difícil reescribir un programa secuencial para un sistema con paso de
mensajes porque cada comunicación debe identificarse con anterioridad o el programa no funcionará
correctamente. Los computadores con memoria compartida permiten que el hardware averigüe qué dato debe
compartirse, lo que facilita la portabilidad.
A la vista de los pros y contras de la comunicación implícita, hay diferentes opiniones sobre cuál es el camino
más corto hacia las altas prestaciones Otros aspectos, como menor coste, alta disponibilidad, eficiencia
energética mejorada y posibilidad de expansión rápida e incremental, hacen que los clústeres sean muy
atractivos para los proveedores de servicio en la web. Los motores de búsqueda, que millones de nosotros
utilizamos cada día, dependen de esta tecnología; Google, eBay, Microsoft, Yahoo! y otros, tienen varios centros
de datos con clústeres de decenas de miles de procesadores. Claramente, el uso de múltiples procesadores en
las compañías de servicios en internet ha sido altamente exitoso.
81
Podríamos decir que los clústeres están compuestos por una serie de computadores independientes, con su
propia memoria (arquitectura NUMA), conectados a través de algún sistema de entrada/salida (Ej. Ethernet).
Donde a cada uno de dichos computadores se denomina nodo, que ejecuta su propio sistema operativo, aunque
en conjunto, se muestran como un sólo sistema.
A la interfaz entre el sistema operativo y los procesos se le llama Middleware, encargado del lanzamiento de
tareas, de la migración de procesos, del balanceo de carga, detección de fallos y del descubrimiento de nodos.
82
Miguel Ángel Cifredo
macifredo@gmail.com
En sistemas con memoria compartida (UMA y NUMA), se hace uso del API OpenMP para la programación de
multiprocesadores con memoria compartida y multihilos.
OpenMP se nos presenta como un modelo de programación paralela, a modo de una pequeña API. Resuelve el
paralelismo de memoria compartida, lo que resulta fácil para los bucles (paralelizables directa o indirectamente).
Es en sí mismo un conjunto de extensiones a lenguajes de programación existentes (Fortran, C, C++).
OpenMP proporciona una serie de directivas de compilación con las que se indican si un determinado bloque de
código ha de ejecutarse de forma secuencial o de forma paralela. Además, nos ofrece una colección de funciones
para la identificación y la sincronización de procesos.
Sintaxis de OpenMP:
- Pragma en C y C++:
#pragma omp construct [clause [clause]…]
- En Fortran, directivas :
C$OMP construct [clause [clause]…]
!$OMP construct [clause [clause]…]
*$OMP construct [clause [clause]…]
No obstante, un programa puede ser compilado por compiladores que no soportan OpenMP. En caso de hacerlo
no olvidar verificar “Compatiblidad OpenMP” en el compilador.
Ejemplo:
Cuando se paraleliza un bucle for, automáticamente se distribuyen las iteraciones del bucle a cada uno de los
hilos. Al estar los datos en la memoria principal compartida, no hace transferirlos explícitamente a cada
procesador. Cuando comienzan los accesos, los datos acaban en la memoria caché.
83
Ejemplo de cómo paralelizar un bucle sencillo:
Programa secuencial:
void main() {
double a[1000], b[1000], c[1000];
for (int i = 0; i<1000; i++) {
a[i] = b[i] + c[i];
}
}
Programa paralelo:
void main() {
double a[1000], b[1000], c[1000];
#pragma omp parallel for
for (int i = 0; i<1000; i++) {
a[i] = b[i] + c[i];
}
}
OpenMP no sólo permite paralelizar bucles, sino también bloques completos de instrucciones.
Control de tareas en
f(thread_id)
Se basa en el uso de un hilo maestro que ejecuta el código secuencial y cuando se encuentra una sección paralela,
la ejecución del código se distribuye entre todos los procesadores disponibles.
Los hilos (threads) se comunican utilizando variables compartidas. De esta forma el uso inadecuado de variables
compartidas origina carreras, por lo que se debe usar sincronización o exclusión para evitarlas. Hay que tener en
cuenta que la sincronización es muy costosa en tiempo, por los que se usará lo menos posible.
OpenMP proporciona funciones para conocer el identificador de cada hilo, así como el número total de hilos que
están ejecutándose en una región paralela.
84
Miguel Ángel Cifredo
macifredo@gmail.com
shared: Variable es compartida por todos los procesos. Ej: vectores del proceso.
private: Cada proceso tiene una copia.
Hay reglas para decidir por defecto el ámbito, pero es mejor no arriesgarse.
Cuidado con el false sharing: ¿Qué pasa si n=2 y tenemos definido float a[2], b[2], c[2]; ?
Ejemplo:
#include <omp.h>
main() {
int nth, tid;
o Barreras: Una vez que se detecta la barrera la ejecución de los hilos se para hasta que todos los hilos
lleguen a la barrera.
#pragma omp barrier
o Exclusión de las secciones críticas: Sólo acede un hilo al recurso al mismo tiempo, obligando a la
ejecución secuencial de todos los hilos
#pragma omp critical
{
//acceso a un recurso compartido. Por ejemplo, la consola.
}
Exclusión Mutua:
#pragma omp parallel shared(x, y)
{ …
#pragma omp critical (section1)
actualiza(x); //sólo un thread
…
usa(x);
…
#pragma omp critical(section2)
actualiza(y); // sólo un thread
…
usa(y);
}
86
Miguel Ángel Cifredo
macifredo@gmail.com
schedule (static [,chunk]) “chunk” iteraciones se asignan de manera estática a los threads en round-robin.
schedule (dynamic [,chunk]) Cada thread toma “chunk” iteraciones cada vez que está sin trabajo.
schedule (guided [,chunk]) Cada thread toma progresivamente menos iteraciones (dinámicamente).
Existe un igual número de iteraciones para static como para dynamic, y resulta exponencialmente decreciente
para guided. Teniendo en cuenta que static es para bucles simples y que dynamic es si la carga varía.
87
Variables reduction: Operaciones colectivas sobre elementos de un array:
asum = 0.0;
aprod = 1.0;
#pragma omp parallel for reduction(+:asum)
reduction(*:aprod)
for (i=0; i<n; i++ ) {
asum = asum + a[i];
aprod = aprod * a[i];
}
Ejemplo: Cálculo de PI
[ secuencial ] 1
𝐴𝑟𝑐𝑇𝑔′ (𝑥) =
1 + 𝑥2 1
1 1 𝜋
static long num_steps = 100000; 𝜋 ⇒∫ = 𝐴𝑟𝑐𝑇𝑔(𝑥)| = −0
double step; 𝐴𝑟𝑐𝑇𝑔(1) = 1 + 𝑥 2 0 4
void main () { 4 0
int i; 𝐴𝑟𝑐𝑇𝑔(0) =0 }
double x, pi, sum = 0.0;
step = 1.0/(double) num_steps;
for (i=1; i<= num_steps; i++) {
x = (i - 0.5) * step;
sum = sum + 4.0 / (1.0 + x * x);
}
pi = step * sum;
}
[ reduction ]
#include <omp.h>
static long num_steps = 100000;
double step;
void main() {
int i;
double x, pi, sum = 0.0;
step = 1.0 / (double) num_steps;
omp_set_num_threads(2);
#pragma omp parallel for reduction(+:sum) private(x)
for (i=1; i<=num_steps; i++) {
x = (i - 0.5) * step;
sum = sum + 4.0 / (1.0 + x * x);
}
pi = step * sum;
}
[ reducción artesanal ]
#include <omp.h>
static long num_steps = 100000;
double step;
void main() {
int i;
double x, pi, sum[2];
step = 1.0 / (double) num_steps;
omp_set_num_threads(2);
#pragma omp parallel
{
double x;
int id;
id = omp_get_thread_num();
for (i = id * num_steps / 2, sum[id] = 0.0; i <= (id + 1) * num_steps / 2; i++) {
x = (i + 0.5) * step;
sum[id] += 4.0 / (1.0 + x * x);
}
}
for (i = 0, pi = 0.0; i < 2; i++)
pi += sum[i] * step;
}
88
Miguel Ángel Cifredo
macifredo@gmail.com
Ejemplo 1.
Enunciado:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
Solución:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
89
Ejemplo 2.
Enunciado:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
Solución:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
sum = 0.0;
#pragma omp parallel for reduction(+:sum)
for (i=0; i < n; i++)
sum = sum + (a[i] * b[i]);
90
Miguel Ángel Cifredo
macifredo@gmail.com
Ejemplo 3.
Enunciado:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define CHUNKSIZE 10
#define N 100
91
Solución:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define CHUNKSIZE 10
#define N 100
92
Miguel Ángel Cifredo
macifredo@gmail.com
Ejemplo 4.
Enunciado:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define N 50
93
Solución:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define N 50
94
Miguel Ángel Cifredo
macifredo@gmail.com
Ejemplo 5.
Enunciado:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
95
Solución:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
96
Miguel Ángel Cifredo
macifredo@gmail.com
Ejemplo 6.
Enunciado:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define VECLEN 100
float a[VECLEN], b[VECLEN], sum;
float dotprod () {
int i,tid;
tid = omp_get_thread_num();
for (i=0; i < VECLEN; i++) {
sum = sum + (a[i]*b[i]);
printf(" tid= %d i=%d\n",tid,i);
}
return(sum);
}
Solución:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define VECLEN 100
float a[VECLEN], b[VECLEN], sum;
float dotprod () {
int i,tid;
tid = omp_get_thread_num();
#pragma omp for reduction(+:sum)
for (i=0; i < VECLEN; i++) {
sum = sum + (a[i]*b[i]);
printf(" tid= %d i=%d\n",tid,i);
}
return(sum);
}
Las plataformas de destino donde usar MPI (Message Passing Interface) son los sistemas con memoria
distribuida son, incluyendo MPM, los clústeres SMP, clústers de estaciones de trabajo y redes heterogéneas. Para
realizar comunicaciones de cierta información en este tipo de sistemas, se hace uso de la librería MPI, que
permite mandar y recibir información entre diferentes espacios de memoria. Esta librería implementa una serie
de protocolos de comunicación para transmitir información entre los nodos del clúster.
A diferencia del API OpenMP del Api MPI, se ejecuta sobre un modelo de memoria distribuida y no uniforme
(NUMA), lo que obliga a hacer un uso extenso de transferencia de datos entre los nodos, lo que conlleva latencia,
overhead, así como de disponer de mecanismos de sincronización entre dichos nodos.
Se trata de una especificación para librerías de paso de mensajes, diseñada para ser un estándar para
computación paralela en sistemas con memoria distribuida y que use para comunicarse paso de mensajes. Las
comunicaciones entre los nodos pueden ser punto a punto o bien multi-punto. Este estándar pretende ser
práctico, eficiente y flexible.
Sabemos entonces que MPI, es una especificación para librerías de paso de mensajes, un estándar mantenido
por el MPI-Forum y muchas otras organizaciones; una biblioteca de funciones que implementan la ejecución
distribuida de un programa mediante paso de mensajes.
Por tanto, las razones para usar MPI son principalmente las siguientes:
Estandarización: MPI es la única librería para paso de mensajes que se considera un estándar. Es
soportado por prácticamente todas las plataformas HPC.
Portabilidad: No hay necesidad de modificar el código fuente cuando portamos nuestra aplicación a una
plataforma distinta que soporte MPI, aunque sí requiere una nueva recompilación.
Prestaciones. Ciertas implementaciones propietarias podrían explotar las características del hardware
para optimizar las prestaciones.
Disponibilidad: Están disponibles una gran variedad de implementaciones, tanto públicas como privadas.
La versión MPI-2 está disponible para los lenguajes C++ y Fortran 90.
Existen múltiples implementaciones reales del estándar MPI como: MPICH, LAM/MPI, OpenMPI, MPI-CH, …
Se puede desarrollar un código híbrido entre OpenMP y MPI, donde cada proceso MPI podrá estar en varios hilos
(threads), esto es, podrá tener un número determinado de hilos asociados. Se crea por tanto un Simple Program
Multiple Data, que no hay que confundirlo con Núcleo Vectorial SIMD.
- Con memoria compartida: [OpenMP] Distintas CPU/núcleos pueden acceder directamente a las
variables del programa en memoria.
- Con memoria distribuida: [MPI] Asume que cada CPU sólo tiene acceso a su propio espacio de memoria.
Especificar este paralelismo explícito es responsabilidad del programador, de la correcta identificación del
paralelismo, así como de la implementación del algoritmo resultante usando funciones MPI; mientras que en un
programa paralelo, el número de procesos dedicados a correr es estático, no se pueden añadir nuevas tareas
dinámicamente durante el tiempo de ejecución. Lo que MPI-2 viene a solucionar.
98
Miguel Ángel Cifredo
macifredo@gmail.com
A diferencia de OpenMP, que sólo puede trabajar en un sistema de memoria compartida (p. ej. un procesador
multinúcleo), con MPI se trata de un único espacio compartido de memoria, lo que permite:
Una ventaja de un modelo de programación distribuida (como MPI) es que le permite trabajar en cualquier tipo
de entorno paralelo:
- Procesador multinúcleo.
- Colección de servidores distribuidos (clúster).
- Entorno grid o cloud.
Hay que indicar al compilador que enlace el código objeto del programa C con la biblioteca de funciones
de MPI: #include <mpi.h>
- Nomenclatura:
MPI_Nombre-de-la-función
- Todas las funciones devuelven un código de error que indica si se han ejecutado con éxito:
if (err == MPI_SUCCESS)
{
... /* la función se ejecutó correctamente */
}
else {
... /* la función ocasionó un error */
}
Comunicadores:
- Todas las funciones de comunicación de MPI tienen como uno de sus parámetros un comunicador,
que define el contexto en el que la comunicación tendrá lugar.
Se crea un único fichero de código y cada nodo tiene su propia copia del ejecutable, que incluye bifurcaciones
según el identificador (rango) del propio proceso, así, cada proceso ejecuta algo diferente.
if (id_proceso == 0) {
// Proceso maestro
}
else {
// Proceso esclavo
}
También puede existir un mpd daemon, que se ejecuta en cada nodo del clúster en espera de envío de procesos
para levantarlos.
Con la orden, desde consola, mpirun –np 4 nombre_archivo indicamos al sistema operativo que ejecute el
programa nombre_archivo utilizando, en este caso, 4 procesadores.
Por razones de portabilidad, MPI tiene una serie de tipos de datos predefinidos. Los programadores pueden
también crear sus propios tipos de datos. Notar que los tipos MPI_BYTE and MPI_PACKED no corresponden a
tipos estándar de C.
Así pues, MPI tiene constantes que definen los tipos de datos básicos. Cada tipo de dato básico de C tiene su
equivalente MPI, de la forma MPI_tipo, que debe ser usado en las llamadas de las funciones en C.
Aunque los programadores pueden crear sus propios tipos, los más más utilizados, con su correspondencia a los
tipos de datos en C, son los siguientes:
100
Miguel Ángel Cifredo
macifredo@gmail.com
- Debe ser la primera función llamada y sólo se indica una vez, ya que inicializa el entorno MPI.
- Lanza los procesos MPI en cada nodo.
- Sus parámetros son punteros a los parámetros de la función main del programa: argc y argv. Es decir,
toma como parámetros la dirección de argc y la de argv.
- Pasa los argumentos recibidos en la línea de comando al proceso lanzador de MPI.
int MPI_Finalize(void)
- Función que debe ser llamada al finalizar el programa, cierra adecuadamente el entorno MPI.
- El usuario debe asegurarse que todas las comunicaciones han sido completadas antes de llamar a la
función MPI_Finalize.
- Después de ella, ninguna otra función MPI puede ser llamada (incluyendo MPI_Init).
- Devuelve el número de procesos en el grupo del comunicador MPI en totTasks, suele ser NCPUs - 1;
esto es, el número de procesos que participan en un comunicador. Si éste es el comunicador global
predefinido MPI_COMM_WORLD, indica el número total de procesos involucrados en el programa.
- [ IN comm ] : Comunicador.
- [ OUT size ] : Número de procesos en el grupo del comunicador comm.
- Devuelve el rango (el identificador) del proceso actual (de 0 a totTasks-1) en un comunicador
(*task_id); esto es, un número de proceso (“rank”) del proceso maestro MPI, que tiene rank = 0.
- Cada comunicador contiene un grupo de procesos, cada uno de los cuales tiene un identificador
único, un rango (task_id), que es un número entero que comienza en 0: (0, 1, 2, 3, ...).
- [ IN comm ] : Comunicador.
- [ OUT rank ] : Rango del proceso que llama en el grupo del comunicador comm.
int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
- Envío básico bloqueante. La función retorna cuando el buffer de la aplicación está libre. A saber que
existe una versión no bloqueante: Isend.
int MPI_Recv(void *rbuf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm,
MPI_Status *status)
- Recibe un mensaje (bloqueante). A saber que existe una versión no bloqueante: Irecv.
101
Ejemplo básico:
#include <mpi.h>
- count : El número de variables que se envían. Si se envía una única variable, valdrá 1 pero, si estamos
pasando un vector, valdrá el número total de elementos de dicho vector. Por ejemplo, para enviar un
vector de 4x5 especificaremos un valor de 20.
- tag : Etiqueta del mensaje. Sirve para verificar que el mensaje que se recibe es el que estaba esperando.
Se trata de un número entero al que le podemos asignar cualquier valor.
- comm : Este es el grupo al cual va el mensaje. Es típico para programas pequeños usar el grupo
MPI_COMM_WORLD. En programas grandes y complejos, los procesos pueden ser divididos en diferentes
grupos para mejorar la velocidad de las conexiones y las transferencias.
102
Miguel Ángel Cifredo
macifredo@gmail.com
Incluyen funciones para realizar llamadas de envío y recepción entre dos procesos. Existen dos categorías básicas
de estas funciones.
No obstante, para verificar si las llamadas no bloqueantes se han completado se emplean los test: MPI_TEST,
MPI_WAIT:
int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
Se trata de una operación de envío básico bloqueante. La rutina retorna cuando el buffer de la aplicación está
libre y puede ser reutilizado.
Este comando permite el paso de cualquier tipo de variable, incluso un vector grande, a cualquier grupo de
procesos. El mensaje contiene count elementos de un tipo de dato especificado por el datatype, comienza en la
dirección de memoria buf, usa la etiqueta de mensaje tag, lo envía al proceso cuyo identificador es dest, en el
comunicador comm.
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm,
MPI_Status *status)
Bloquea un proceso hasta que recibe un mensaje del proceso cuyo rango es source, en el comunicador comm, con
etiqueta de mensaje tag.
Se pueden usar los comodines MPI_ANY_SOURCE y MPI_ANY_TAG para recibir mensajes. En ese caso, el status de
salida puede ser usado para conocer la fuente y etiqueta del mensaje recibido.
El mensaje recibido se guarda en un buffer consistente en un espacio de memoria que contiene count elementos
consecutivos del tipo especificado por datatype, comenzando en la dirección de memoria buf.
La longitud del mensaje recibido debe ser menor o igual que la longitud del buffer de recepción disponible.
103
Ejemplo de paso de mensajes (saludo en anillo):
#include <stdio.h>
#include <string.h>
#include <mpi.h>
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &idProceso);
MPI_Comm_size(MPI_COMM_WORLD, &nProcesos);
if (idProceso != 0)
{ // Esclavo: Genera un mensaje y lo envía al maestro (0)
sprintf(mensaje, "Saludos desde el proceso esclavo %d!", idProceso);
dest = 0;
MPI_Send(mensaje, strlen(mensaje) + 1, MPI_CHAR, dest, etiqueta, MPI_COMM_WORLD);
}
else
{ // Maestro: Recibe un mensaje desde cada esclavo.
for (fuente = 1; fuente < nProcesos; fuente++) {
MPI_Recv(mensaje, 100, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &status);
printf("%s\n", mensaje);
}
}
MPI_Finalize();
Nota 1: En la función de envío MPI_Send se ha indicado strlen(mensaje) + 1 para que el carácter de fin de
cadena ’\0’ pueda también ser enviado.
Nota 2: Hay que tener cuidado con los “Deadlock”, esto es, ejecutar una función de recibir sin haber enviado.
104
Miguel Ángel Cifredo
macifredo@gmail.com
Comunicación colectiva.
Las comunicaciones colectivas involucran a todos los procesos dentro del foco del comunicador. Todos los
procesos son por defecto miembros de un comunicador. El comunicador por defecto es: MPI_COMM_WORLD.
- Sincronización. Los procesos esperan hasta que todos los miembros del grupo han alcanzado el punto
de sincronización.
- Computación colectiva (reduction). Es similar que en OpenMP pero soporta más operaciones (min, max,
add, multiply, etc.). Un miembro del grupo recoge los datos del resto de miembros y realiza la operación
especificada sobre los datos.
Es importante destacar que las operaciones colectivas son bloqueantes por sí mismas, así como que se trabaja
exclusivamente con tipos de datos definidos para MPI y no con los tipos propios de C.
o Barrier
int MPI_Barrier(MPI_Comm comm)
Establece una barrera de sincronización en un grupo. Cada proceso, cuando ejecuta la función MPI_Barrier,
se bloquea hasta que todos los procesos del grupo ejecuten la misma función. No se usa mucho, aunque sí
en mensajes bloqueantes.
105
o Comunicación colectiva: Scatter
int MPI_Scatter ( void *sendbuf
, int sendcnt
, MPI_Datatype sendtype
, void *recvbuf
, int recvcnt
, MPI_Datatype recvtype
, int root
, MPI_Comm comm
)
106
Miguel Ángel Cifredo
macifredo@gmail.com
107
o Comunicación colectiva: Reduce
Frecuentemente hacemos cálculos en los procesos que necesitamos recoger o reducir los datos obtenidos
sobre uno o todos los procesos. MPI dispone de rutinas que permiten realizar estas reducciones.
int MPI_Reduce ( void *sendbuf
, void *recvbuf
, int recvcnt
, int count
, MPI_Datatype datatype
, MPI_Op op
, int root
, MPI_Comm comm
)
Donde:
Si cada proceso en nuestro trabajo ha calculado un valor privado para alguna variable, var, con el mismo
nombre en todos los procesos, la rutina MPI_REDUCE reducirá todos estos valores, de acuerdo a alguna
operación, y a continuación guardará el resultado en una variable sobre uno de los procesos.
Podemos realizar una reducción y enviar el resultado a todos los procesos. Esta operación es equivalente a
un MPI_Reduce seguido de un MPI_Bcast. La única diferencia entre esta llamada y MPI_REDUCE es que no existe
el campo root.
int MPI_Allreduce( void *sendbuf
, void *recvbuf
, int count
, MPI_Datatype datatype
, MPI_Op op
, MPI_Comm comm
)
108
Miguel Ángel Cifredo
macifredo@gmail.com
Código completo:
#include <stdio.h>
#include <mpi.h>
MPI_Init(&argc, &argv);
MPI_Comm_idProceso(MPI_COMM_WORLD, &idProceso);
MPI_Comm_nProcesos(MPI_COMM_WORLD, &nProcesos);
/* Ahora puede empezar el bucle en cada proceso, accediendo sólo a su sección de datos */
for(i = idProceso*(N/nProcesos); i < (idProceso+1)*(N/nProcesos); i++){
ybuff[i] = a * xbuff[i] + ybuff[i];
}
if (idProceso == 0){
for (i = 0; i < N; i++)
printf("[%d] %f\n", idProceso, y[i]);
MPI_Finalize();
}
109
Ejemplo cálculo del número π:
Cálculo del número π mediante integración numérica, teniendo en cuenta que la integral en el intervalo [0, 1]
de la derivada del arco tangente de x es π/4.
1
𝑎𝑟𝑐𝑡𝑔′(𝑥) =
1 + 𝑥2 1
1 𝜋
𝜋 ⇒ ∫ = 𝑎𝑟𝑐𝑡𝑔(𝑥)10 = −0
𝑎𝑟𝑐𝑡𝑔(1) = 0 1+𝑥 2 4
4
𝑎𝑟𝑐𝑡𝑔(0) =0 }
Vamos a basarnos en una versión secuencial. En ella se aproxima el área en cada subintervalo utilizando
rectángulos en los que la altura es el valor de la derivada del arco tangente en el punto medio.
Suponemos que tenemos un clúster Beowulf, formado por 8 PCs. En cada procesador se ejecutará un
proceso y, la carga de trabajo se distribuirá por igual entre los procesadores. Realizaremos una
planificación estática de las tareas.
110
Miguel Ángel Cifredo
macifredo@gmail.com
MPI_Init(): Crea el proceso que lo ejecuta dentro del mundo MPI, es decir, dentro del grupo de procesos
denominado MPI_COMM_WORLD. Una vez que se ejecuta esta función se pueden utilizar el resto de
funciones MPI.
MPI_Finalice(): Se debe llamar antes de que un proceso creado en MPI acabe su ejecución.
- Se reparten las iteraciones del bucle entre los diferentes procesos MPI de forma explícita.
Comunicación y sincronización:
- Los procesos se comunican y sincronizan para sumar las áreas parciales calculadas por los procesos.
- Usamos MPI_Reduce. Esta es una operación cooperativa realizada entre todos los miembros del
comunicador y se obtiene un único resultado final.
- Los procesos envían al proceso 0 el contenido de su variable local sum, los contenidos de sum se suman
y se guardan en el proceso 0 en la variable tsum.
El comando de ejecución depende de la implementación concreta de MPI que se use, pero la esencia es la misma:
La programación SPMD, que no hay que confundirla con la programación SIMD, es la forma más general de
programación MIMD, cada proceso ejecuta un programa diferente, pero puede conseguirse lo mismo con un
único programa que incluya bifurcaciones según el identificador (rango) del proceso.
111
Caracterización de las aplicaciones.
Resulta difícil analizar si una aplicación es paralelizable o no puesto que depende de muchos factores:
Tiempo de comunicación.
𝑇
𝑡𝑚𝑒𝑛𝑠 = 𝑡𝑠 + 𝑡𝑡𝑟𝑎𝑛𝑠𝑓𝑒𝑟 = 𝑡𝑠 + (𝐿 ∙ 𝑡𝑙𝑎𝑡𝑒𝑛𝑐𝑖𝑎 + )
𝐴𝐵𝑡𝑟𝑎𝑛𝑠𝑚𝑖𝑠𝑖ó𝑛
MPI dispone de una función para medir los tiempos de ejecución con la que se puede evaluar el rendimiento de
los programas. Al ser una función del API estándar MPI, tiene como ventaja su portabilidad.
double MPI_Wtime(void): Devuelve un número de segundos en punto flotante, que representan una referencia
de tiempo (wall-clock time).
Para medir la duración de un proceso, se puede llamar a MPI_Wtime justo antes de que comience y justo después
de que termine. Posteriormente se calcula la diferencia entre los dos tiempos obtenidos.
112
Miguel Ángel Cifredo
macifredo@gmail.com
3.5 Ejercicios.
Desde un punto de visto lógico, se refiere a las comunicaciones que se establecen en la red y es
independiente de la aplicación. Por tanto se refiere al conexionado lógico de los nodos, independientemente
de la propia topología de la red.
Desde el punto de vista de la topología de una red, es la implementación física de la red (Anillo, Árbol,
Estrella,…), por lo que el diseño de la aplicación es independiente del diseño físico de la red, pero algunas
topologías favorecen dichas aplicaciones.
El diseño lo estudiaremos en el centro (bisectriz) de la red; esto es, dividimos físicamente la red y veremos cuál
es el comportamiento de la aplicación, si el ancho de banda en dicha bisectriz física es mayor o igual al ancho de
banda del programa.
Empecemos por el ancho de banda física partiendo de que todos los enlaces son iguales, tendremos entonces:
𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑐𝑖ó𝑛 = 𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑐𝑖ó𝑛 ∙ 𝐴𝐵𝐸𝑛𝑙𝑎𝑐𝑒
𝐹í𝑠𝑖𝑐𝑎
donde AB Bisección se corresponde con el número de enlaces que tenemos que cortar para obtener dos subredes
lo más parecidas posible. Estamos suponiendo que todos los anchos de banda son iguales. En caso de que los
AB Bisección sean diferentes, entonces tomaríamos:
𝑁ú𝑚𝑒𝑟𝑜 𝐴𝐵−1
∑ 𝐴𝐵𝐸𝑛𝑙𝑎𝑐𝑒𝑖
𝑖=0
Posteriormente, estudiaremos qué ancho de banda requiere nuestro programa de la siguiente forma:
113
Ejemplo.
Supongamos 6 nodos conectados en anillo, donde cada uno de ellos emite 3 mensajes, dos de ellos a los dos
siguientes y uno al anterior.
Veamos gráficamente el caso del nodo 6 enviando sus 3 mensajes, de los cuales sólo 1 atraviesa la bisectriz.
6 1
5 2
4 3
Tenemos entonces:
𝑁ú𝑚𝑒𝑟𝑜 𝑑𝑒 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠 𝑞𝑢𝑒 𝑎𝑡𝑟𝑎𝑣𝑖𝑒𝑠𝑎𝑛 𝑙𝑎 𝑏𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 1 𝑚𝑠𝑔 [𝑛𝑜𝑑𝑜 6 → 𝑛𝑜𝑑𝑜 5] + 1 𝑚𝑠𝑔 [𝑛𝑜𝑑𝑜 2 → 𝑛𝑜𝑑𝑜 3] 2
(b) 𝑁ú𝑚𝑒𝑟𝑜 𝑡𝑜𝑡𝑎𝑙 𝑑𝑒 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠 𝑔𝑒𝑛𝑒𝑟𝑎𝑑𝑜𝑠 𝑝𝑜𝑟 𝑐𝑎𝑑𝑎 𝑛𝑜𝑑𝑜
= 𝑐𝑎𝑑𝑎 𝑢𝑛𝑜 𝑑𝑒 𝑙𝑜𝑠 6 𝑛𝑜𝑑𝑜𝑠 𝑔𝑒𝑛𝑒𝑟𝑎 3 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠
= 18
(c) Ancho de banda requerido por la ejecución en 1 nodo se calcula de la siguiente forma:
𝐹𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎𝐶𝑃𝑈 𝐿𝑜𝑎𝑑𝑠 ] 𝐴𝑐𝑐𝑒𝑠𝑜𝑠
𝐴𝐵𝐺𝑒𝑛𝑒𝑟𝑎𝑑𝑜 𝑝𝑜𝑟 = ∙ %[ ∙ %[ ] ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ∙ 𝑇𝑎𝑚𝑎ñ𝑜𝑀𝑒𝑛𝑠𝑎𝑗𝑒
𝑐𝑎𝑑𝑎 𝑛𝑜𝑑𝑜 𝐶𝑃𝐼𝑇𝑂𝑇𝐴𝐿 𝑆𝑡𝑜𝑟𝑒𝑠 𝑟𝑒𝑚𝑜𝑡𝑜𝑠
Puede ocurrir que cuando el microprocesador acceda a la red se bloquee, por lo que:
CPI TOTAL = CPIT TOTAL + CPI Bloqueo . Pero si el microprocesador no se bloqueara, entonces CPI TOTAL = CPI IDEAL.
𝐿𝑜𝑎𝑑𝑠 ] 𝐴𝑐𝑐𝑒𝑠𝑜𝑠
𝐶𝑃𝐼𝐵𝑙𝑜𝑞𝑢𝑒𝑜 = % [ ∙ %[ ] ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ∙ ⏟
𝑇𝑖𝑒𝑚𝑝𝑜𝐴𝑐𝑐𝑒𝑠𝑜𝑅𝑒𝑑
⏟ 𝑆𝑡𝑜𝑟𝑒𝑠 𝑟𝑒𝑚𝑜𝑡𝑜𝑠
𝑇𝑖𝑒𝑚𝑝𝑜 𝑑𝑒 𝑎𝑐𝑐𝑒𝑠𝑜 𝑟𝑒𝑎𝑙
𝐹𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎 𝑑𝑒 𝑎𝑐𝑐𝑒𝑠𝑜 𝑎 𝑙𝑎 𝑟𝑒𝑑
Los microprocesadores RISC que se usarán tienen una frecuencia de CPU de 300 MHz. Tras simular los programas
en tales microprocesadores, se obtienen las siguientes estadísticas: %Accesos remotos = 10%, %LD/ST = 25%.
El código siempre permanece en el caché de instrucciones. Tamaño medio de un mensaje = 100 Bytes.
Se está barajando la posibilidad de conectar los nodos con alguna de las topologías de switches distribuidos
siguientes: Cubo de 8 nodos y Malla 2D rectangular de 4x2 nodos.
Ambas redes usan enlaces de 125 MBytes/segundo cada una. El mapeo de los nodos con los elementos finitos
se muestra en la siguiente figura:
Se pide:
a) Calcular el Miss Rate máximo del caché de datos para cada topología, para que se soporte el tráfico en la
bisectriz de la aplicación. Suponer que el CPI (con todo tipo de bloqueos incluidos) = 0’8 ciclos/instrucción
para ambas topologías.
b) Tómese ahora Miss Rate (Caché datos) = 6% y CPI (suponiendo red ideal) = 0’6 ciclos/instrucción para ambas
topologías. Calcular la relación de tiempos de ejecución entre el nodo 7 del Cubo y el mismo nodo de la
Malla al ejecutar N instrucciones. Suponer que el tiempo de acceso medio a la red se puede aproximar por
la fórmula (2 ∙ l ∙ th ) + ttransf , donde l es el número medio de enlaces exteriores entre el procesador origen y
el de destino, th es 0’5 microsegundos y ttransf es el tiempo en transferir los 100 Bytes del mensaje.
115
Solución:
Apartado a)
Sabiendo que:
Caso : Cubo 3D
𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑐𝑖ó𝑛 = 𝑁ú𝑚𝑒𝑟𝑜 𝑑𝑒 𝑡𝑟𝑎𝑚𝑜𝑠 𝑐𝑜𝑟𝑡𝑎𝑑𝑜𝑠 𝑝𝑜𝑟 𝑙𝑎 𝑏𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 = 4
𝑀𝐵𝑦𝑡𝑒𝑠
𝐴𝐵𝐸𝑛𝑙𝑎𝑐𝑒 = 125
𝑠𝑒𝑔𝑢𝑛𝑑𝑜
Caso : Malla 2D
116
Miguel Ángel Cifredo
macifredo@gmail.com
Cálculo del ancho de banda del programa en la bisectriz (es el mismo para ambos casos):
Sabemos que:
Nº Nodos = 8
300 𝑀𝐻𝑧
= ∙ 0′ 25 ∙ 0′ 10 ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ∙ 100 𝐵𝑦𝑡𝑒𝑠
0′ 8 𝑐/𝑖
𝑀𝐵𝑦𝑡𝑒𝑠
= 937′ 5 ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒
𝑠𝑒𝑔𝑢𝑛𝑑𝑜
Para poder determinar el valor de 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 podemos igualar las fórmulas: 𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 = 𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧
𝐹í𝑠𝑖𝑐𝑎 𝑃𝑟𝑜𝑔𝑟𝑎𝑚𝑎
𝑀𝐵𝑦𝑡𝑒𝑠 𝑀𝐵𝑦𝑡𝑒𝑠 8
𝐶𝑎𝑠𝑜 𝐶𝑢𝑏𝑜 ∶ (500 = 937′ 5 ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ) ⇒ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 =
𝑠𝑒𝑔𝑢𝑛𝑑𝑜 𝑠𝑒𝑔𝑢𝑛𝑑𝑜 15
𝑀𝐵𝑦𝑡𝑒𝑠 𝑀𝐵𝑦𝑡𝑒𝑠 4
𝐶𝑎𝑠𝑜 𝑀𝑎𝑙𝑙𝑎 ∶ (250 = 937′ 5 ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ) ⇒ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 =
𝑠𝑒𝑔𝑢𝑛𝑑𝑜 𝑠𝑒𝑔𝑢𝑛𝑑𝑜 15
117
Apartado b)
Vamos a obtener el valor de 𝑙 , el número de enlaces medio para ir desde el nodo emisor de los
mensajes al nodo receptor de ellos, estudiando qué sucede en algún nodo de cada topología:
(1 𝑡𝑟𝑎𝑚𝑜 ∙ 3 𝑚𝑠𝑔)
𝐶𝑎𝑠𝑜 𝐶𝑢𝑏𝑜: 𝑙= =1
3 𝑚𝑠𝑔
(1 𝑡𝑟𝑎𝑚𝑜 ∙ 2 𝑚𝑠𝑔) + (3 𝑡𝑟𝑎𝑚𝑜𝑠 ∙ 1 𝑚𝑠𝑔) 5
𝐶𝑎𝑠𝑜 𝑀𝑎𝑙𝑙𝑎: 𝑙= =
3 𝑚𝑠𝑔 3
Ahora podremos determinar el tiempo de acceso a la red de cualquier nodo de cada topología:
𝑀𝐻𝑧
𝑇ℎ𝑒𝑎𝑑 = 0′ 5 𝜇𝑠 → 0′ 5 𝜇𝑠 ∙ 300 = 150 𝑐𝑖𝑐𝑙𝑜𝑠𝐶𝑃𝑈
𝑠𝑒𝑔𝑢𝑛𝑑𝑜𝑠
100 𝐵𝑦𝑡𝑒𝑠 𝑀𝐻𝑧
𝑇𝑡𝑟𝑎𝑛𝑠𝑓 = = 0′ 8 𝜇𝑠 → 0′ 8 𝜇𝑠 ∙ 300 = 240 𝑐𝑖𝑐𝑙𝑜𝑠𝐶𝑃𝑈
𝑀𝐵𝑦𝑡𝑒𝑠 𝑠𝑒𝑔𝑢𝑛𝑑𝑜𝑠
125 𝑠𝑒𝑔𝑢𝑛𝑑𝑜
Determinemos ahora cuál es el tiempo de ejecución del programa en el algún nodo de cada topología:
𝑇𝐸𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛 = 𝑁º𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 ∙ 𝐶𝑃𝐼 ∙ 𝑇
Si relacionamos los tiempos de ejecución de cualquier nodo para ambas topologías tendremos:
𝑇𝐸𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛(𝑀𝑎𝑙𝑙𝑎) 𝑁º𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 ∙ 𝐶𝑃𝐼(𝑀𝑎𝑙𝑙𝑎) ∙ 𝑇
𝑅𝑒𝑙𝑎𝑐𝑖ó𝑛 = =
𝑇𝐸𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛(𝐶𝑢𝑏𝑜) 𝑁º𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 ∙ 𝐶𝑃𝐼(𝐶𝑢𝑏𝑜) ∙ 𝑇
𝐶𝑃𝐼(𝑀𝑎𝑙𝑙𝑎) 1′71
𝑅𝑒𝑙𝑎𝑐𝑖ó𝑛 = = = 𝟏′𝟐𝟏
𝐶𝑃𝐼(𝐶𝑢𝑏𝑜) 1′41
Concluimos con que la topología Cubo es un 21% más rápido que la topología Malla. Si hubiéramos
considerado los nodos centrales de la Malla hubiera arrojado los mismos resultados, pues se atraviesa
el mismo número de nodos.
118
Miguel Ángel Cifredo
macifredo@gmail.com
Disponemos de un sistema multiprocesador tipo NUMA, con 64 procesadores (con una CPU = 500 MHz y con
CPIBase = 0’4 ciclos/instc.). La red de interconexión es una malla 2D cuadrada, siendo su frecuencia de 100 MHz y
el ancho del enlace de 16 bits. Pretendemos ejecutar un programa cuyo patrón de comunicaciones es, adyacente
con todos los nodos de su entorno (incluidos los que se encuentran en su diagonal). De la ejecución de dicho
programa hemos extraído los siguientes datos: %LD/ST = 20%, %Accesos locales = 85%, Missrate = 3%, Tamaño de
la línea = 128 Bytes, Tamaño cabecera = 32Bytes.
a) Calcular el 𝑡𝑎𝑐𝑐.𝑅𝑒𝑑 (en ciclos de CPU), si este se puede aproximar a 2 ∙ 𝑙 ∙ th , donde 𝑙 es el número medio
de nodos entre el procesador origen y el destino, y th = 20 ciclos de red.
Solución:
Datos conocidos:
𝑁º𝑃𝑟𝑜𝑐𝑒𝑠𝑎𝑑𝑜𝑟𝑒𝑠 = 64 𝐸𝑛𝑙𝑎𝑐𝑒 = 16 𝑏𝑖𝑡𝑠 (2 𝐵𝑦𝑡𝑒𝑠)
𝐶𝑃𝑈𝑝𝑟𝑜𝑐𝑒𝑠𝑎𝑑𝑜𝑟 = 500 𝑀𝐻𝑧 𝐹𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎 = 100 𝑀𝐻𝑧
𝐶𝑃𝐼𝐼𝑑𝑒𝑎𝑙 = 0′ 4 𝑐/𝑖 𝑇𝑜𝑝𝑜𝑙𝑜𝑔í𝑎 = 𝑀𝑎𝑙𝑙𝑎 2𝐷 𝑐𝑢𝑎𝑑𝑟𝑎𝑑𝑎
% [𝐿𝑜𝑎𝑑/𝑆𝑡𝑜𝑟𝑒] = 20 % 𝑇𝑎𝑚𝑎ñ𝑜𝐿í𝑛𝑒𝑎 = 128 𝐵𝑦𝑡𝑒𝑠
% 𝐴𝑐𝑐𝑒𝑠𝑜𝑠𝑙𝑜𝑐𝑎𝑙𝑒𝑠 = 85 % 𝑇𝑎𝑚𝑎ñ𝑜𝐶𝑎𝑏𝑒𝑐𝑒𝑟𝑎 = 32 𝐵𝑦𝑡𝑒𝑠
𝑀𝑖𝑠𝑠𝑅𝑎𝑡𝑒 = 3%
119
Apartado a)
𝑇𝑎𝑐𝑐.𝑅𝑒𝑑 ≅ 2 ∙ 𝑙 ∙ 𝑇ℎ𝑒𝑎𝑑
Donde:
Como hay 3 tipos de nodos diferentes (4 esquenas, 24 laterales y 36 centrales), calculamos los 𝑙
de cada uno de estos tipos y determinamos, posteriormente, su valor medio; esto es:
Hemos obtenido el tiempo medio de acceso a la red expresado en ciclos de red, pero nos piden indicarlo
en ciclos de CPU, luego, sabiendo que Frecuencia red = 100 MHz y que Frecuencia CPU = 500 MHz :
Concluimos indicando que el tiempo medio de acceso a la red es de 290 ciclos de CPU.
120
Miguel Ángel Cifredo
macifredo@gmail.com
Apartado b)
Veamos si soportaría la configuración dada el ancho de banda generado en la bisectriz por el programa.
𝑀𝐵𝑦𝑡𝑒𝑠 𝑀𝐵𝑦𝑡𝑒𝑠
𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 = 8 ∙ 200 = 1.600
𝐹í𝑠𝑖𝑐𝑜 𝑠𝑒𝑔𝑢𝑛𝑑𝑜 𝑠𝑒𝑔𝑢𝑛𝑑𝑜
(4 ∙ 2) + (12 ∙ 3) 44
= = 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠
(4 ∙ 3) + (24 ∙ 5) + (36 ∙ 8) 420
121
(c) El ancho de banda generado por cada nodo es de:
Donde:
- Nos aportan el %Accesos locales, pero necesitamos conocer el %Accesos remotos, luego:
𝐴𝑐𝑐𝑒𝑠𝑜𝑠 𝐴𝑐𝑐𝑒𝑠𝑜𝑠
%[ ] = 100% − % [ ] = 100% − 85% = 15% → 0′15
𝑟𝑒𝑚𝑜𝑡𝑜𝑠 𝑙𝑜𝑐𝑎𝑙𝑒𝑠
Observamos que:
Como el ancho de banda físico soporta más carga que el requerido por la aplicación, podemos concluir
indicando que la red soporta la demanda de la aplicación.
122
Miguel Ángel Cifredo
macifredo@gmail.com
Pretendemos estudiar cómo se adapta un sistema multiprocesador NUMA con 16 nodos (procesadores RISC de
500 MHz) y con una topología en forma de Hipercubo a dos patrones de comunicación distintos. El primer en un
patrón Adyacente-2D, teniendo en cuenta que todos los nodos se comunican con aquellos con los cuales tienen
conexión directa. Por otra parte, tenemos un patrón de comunicaciones del tipo Todos con Todos. Suponiendo
que la CPU se bloquea en los accesos a la red y que el tiempo de acceso lo podemos aproximar por
t acc.Red ≈ 2 ∗ 𝑙 ∗ 𝑡ℎ . ¿Soportaría la red en su bisectriz el tráfico generado por ambos patrones de comunicación?
Datos: th = 15 ciclos de red, CPI base = 0’4 ciclos/instrucción, %LD/ST = 18%, %Accesos remotos = 18%, Mr = 5%,
tamaño de línea = 128 Bytes, tamaño de cabecera = 8 Bytes. Enlaces de frecuencia 125 MHz y 16 bits de ancho.
Solución:
13 9 8 12
15 11 10 14
5 1 0 4
7 3 2 6
Calculamos el ancho de banda físico en la bisectriz que, para ambos patrones, es el mismo:
𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 = 𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑐𝑖ó𝑛 ∙ 𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧
𝐹í𝑠𝑖𝑐𝑜 𝐸𝑛𝑙𝑎𝑐𝑒
𝑴𝑩𝒚𝒕𝒆𝒔
= 𝟐. 𝟎𝟎𝟎
𝒔𝒆𝒈𝒖𝒏𝒅𝒐
Calculamos ahora el ancho de banda requerido por el programa para cada patrón de comunicaciones:
123
Caso: Patrón adyacentes (conexión directa)
𝐿𝑜𝑎𝑑𝑠 ] 𝐴𝑐𝑐𝑒𝑠𝑜𝑠
𝐶𝑃𝐼𝐵𝑙𝑜𝑞𝑢𝑒𝑜 = %[ ∙%[ ] ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ∙ 𝑇𝑖𝑒𝑚𝑝𝑜𝐴𝑐𝑐𝑒𝑠𝑜𝑅𝑒𝑑
𝑆𝑡𝑜𝑟𝑒𝑠 𝑟𝑒𝑚𝑜𝑡𝑜𝑠
𝑐𝑖𝑐𝑙𝑜𝑠
= 0′ 18 ∙ 0′ 18 ∙ 0′ 05 ∙ 120 𝑐.𝐶𝑃𝑈 = 0′ 1944
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖ó𝑛
4 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠 𝑎 1 𝑡𝑟𝑎𝑚𝑜
𝑙 = =1
4 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠 𝑔𝑒𝑛𝑒𝑟𝑎𝑑𝑜𝑠 𝑝𝑜𝑟 𝑐𝑎𝑑𝑎 𝑛𝑜𝑑𝑜
500 𝑀𝐻𝑧
𝑡ℎ = 15 𝑐𝑖𝑐𝑙𝑜𝑠𝑅𝑒𝑑 ∙ = 60 𝑐𝑖𝑐𝑙𝑜𝑠𝐶𝑃𝑈
125 𝑀𝐻𝑧
Por tanto:
1 𝑀𝐵𝑦𝑡𝑒𝑠 𝑴𝑩𝒚𝒕𝒆𝒔
𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 = 16 𝑛𝑜𝑑𝑜𝑠 ∙ ∙ 196′23 = 𝟕𝟖𝟒′𝟗𝟑
𝑃𝑟𝑜𝑔𝑟𝑎𝑚𝑎 4 𝑠𝑒𝑔𝑢𝑛𝑑𝑜 𝒔𝒆𝒈𝒖𝒏𝒅𝒐
124
Miguel Ángel Cifredo
macifredo@gmail.com
4m1t
𝐿𝑜𝑎𝑑𝑠 ] 𝐴𝑐𝑐𝑒𝑠𝑜𝑠
𝐶𝑃𝐼𝐵𝑙𝑜𝑞𝑢𝑒𝑜 = %[ ∙%[ ] ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ∙ 𝑇𝑖𝑒𝑚𝑝𝑜𝐴𝑐𝑐𝑒𝑠𝑜𝑅𝑒𝑑
𝑆𝑡𝑜𝑟𝑒𝑠 𝑟𝑒𝑚𝑜𝑡𝑜𝑠
𝑐𝑖𝑐𝑙𝑜𝑠
= 0′ 18 ∙ 0′ 18 ∙ 0′ 05 ∙ 256 𝑐.𝐶𝑃𝑈 = 0′ 41472
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖ó𝑛
6m2t
32
𝑇𝑖𝑒𝑚𝑝𝑜𝐴𝑐𝑐𝑒𝑠𝑜𝑅𝑒𝑑 ≅ 2 ∙ 𝑙 ∙ 𝑡ℎ = 2 ∙ ∙ 60 𝑐𝑖𝑐𝑙𝑜𝑠𝐶𝑃𝑈 = 256 𝑐𝑖𝑐𝑙𝑜𝑠𝐶𝑃𝑈
15
4m3t (4 𝑚 ∙ 1 𝑡) + (6 𝑚 ∙ 2 𝑡) + (4 𝑚 ∙ 3 𝑡) + (1 𝑚 ∙ 4 𝑡) 32
𝑙 = =
15 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠 𝑔𝑒𝑛𝑒𝑟𝑎𝑑𝑜𝑠 𝑝𝑜𝑟 𝑐𝑎𝑑𝑎 𝑛𝑜𝑑𝑜 15
500 𝑀𝐻𝑧
𝑡ℎ = 15 𝑐𝑖𝑐𝑙𝑜𝑠𝑅𝑒𝑑 ∙ = 60 𝑐𝑖𝑐𝑙𝑜𝑠𝐶𝑃𝑈
125 𝑀𝐻𝑧
1m4t
Por tanto:
8 𝑀𝐵𝑦𝑡𝑒𝑠 𝑴𝑩𝒚𝒕𝒆𝒔
𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 = 16 𝑛𝑜𝑑𝑜𝑠 ∙ ∙ 143′ 16 = 𝟏. 𝟐𝟐𝟏′𝟔𝟖
𝑃𝑟𝑜𝑔𝑟𝑎𝑚𝑎 15 𝑠𝑒𝑔𝑢𝑛𝑑𝑜 𝒔𝒆𝒈𝒖𝒏𝒅𝒐
125
Conclusión:
Para ambos patrones de comunicación tenemos que el ancho de banda requerido por la aplicación, 784’93 MB/s
y 1.221’68 MB/s, son inferiores al ancho de banda máximo físico soportado por la red, 2.000 MB/s.
Por tanto, el sistema soportará cualquiera de las dos topologías propuestas, aunque irá con más holgura el patrón
adyacente, conexión directa.
126
Miguel Ángel Cifredo
macifredo@gmail.com
Datos: %ld/st = 32%; %Acc. Remotos = 18%; Mr = 2’5%; Tamaño Medio Mensaje = 128 Bytes; CPI ideal = 0’5
ciclos/instrucción; El tiempo medio de acceso a la red lo podemos aproximar por: t acc,red = 2 ∗ 𝑙 ∗ 𝑡ℎ donde
th = 25 ciclos CPU.
Solución:
1
12 2
11 3
10 4
9 5
8 6
Calculamos ahora el ancho de banda requerido por el programa para cada patrón de comunicaciones:
127
Caso optimizado:
Luego,
128
Miguel Ángel Cifredo
macifredo@gmail.com
Caso no optimizado:
𝐿𝑜𝑎𝑑𝑠 ] 𝐴𝑐𝑐𝑒𝑠𝑜𝑠
𝐶𝑃𝐼𝐵𝑙𝑜𝑞𝑢𝑒𝑜 = %[ ∙%[ ] ∙ 𝑀𝑖𝑠𝑠𝑟𝑎𝑡𝑒 ∙ 𝑇𝑖𝑒𝑚𝑝𝑜𝐴𝑐𝑐𝑒𝑠𝑜𝑅𝑒𝑑
𝑆𝑡𝑜𝑟𝑒𝑠 𝑟𝑒𝑚𝑜𝑡𝑜𝑠
𝑐𝑖𝑐𝑙𝑜𝑠
= 0′ 32 ∙ 0′ 18 ∙ 0′ 025 ∙ 150 𝑐.𝐶𝑃𝑈 = 0′ 216
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖ó𝑛
(2 𝑚 ∙ 1 𝑡) + (2 𝑚 ∙ 3 𝑡) + (2 𝑚 ∙ 5 𝑡)
𝑙 = =3
6 𝑚𝑒𝑛𝑠𝑎𝑗𝑒𝑠 𝑔𝑒𝑛𝑒𝑟𝑎𝑑𝑜𝑠 𝑝𝑜𝑟 𝑐𝑎𝑑𝑎 𝑛𝑜𝑑𝑜
𝑡ℎ = 25 𝑐𝑖𝑐𝑙𝑜𝑠𝐶𝑃𝑈
Por tanto:
𝑀𝐵𝑦𝑡𝑒𝑠 𝑀𝐵𝑦𝑡𝑒𝑠
𝐴𝐵𝐵𝑖𝑠𝑒𝑐𝑡𝑟𝑖𝑧 = 12 𝑛𝑜𝑑𝑜𝑠 ∙ 0′5 ∙ 206 = 1.235′6
𝑃𝑟𝑜𝑔𝑟𝑎𝑚𝑎 𝑠𝑒𝑔𝑢𝑛𝑑𝑜 𝑠𝑒𝑔𝑢𝑛𝑑𝑜
Luego,
129
130
Miguel Ángel Cifredo
macifredo@gmail.com
4 Sistemas Distribuidos.
Un sistema distribuido está organizado mediante un middleware. Se trata de una capa que se extiende sobre
múltiples máquinas.
El Middleware determina:
Finance and commerce eCommercee, Amazon, eBay, PayPal, Banca online banking, …
Ingeniería para la información y búsqueda Web, ebooks, Wikipedia; redes sociales:
The information society
Facebook, MySpace, …
Creative industries and Juegos, música y películas online para el hogar, contenido generado por el usuario
entertainment (YouTube, Flickr, …)
Healthcare Informática médica, registro de pacientes online, monitorización de clientes, …
Education e-learning, entornos de aprendizaje virtuales, enseñanza a distancia, …
Transport and logistics Sistemas de búsquedas en ruta GPS, servicios de mapas: Google Maps, Google Earth
Science El Grid como tecnología que posibilita la colaboración entre científicos, …
Environmental
Tecnología de sensores que monitorizan terremotos, inundaciones o tsunamis, …
management
131
4.2 Aspectos en el diseño de un Sistema Distribuido.
Heterogeneidad en:
- Redes.
- Sistemas Operativos.
- Lenguajes de programación.
- Hardware de los dispositivos.
Aun con estas diferencias, se debe asegurar la interacción entre todos los componentes.
Apertura:
- Posibilidad de ampliación.
- Incorporación de nuevos recursos.
- Nuevos servicios compartidos.
Concurrencia.
Seguridad:
Escalabilidad:
Un sistema es escalable si se mantiene efectivo (no se degrada) cuando hay un incremento significativo
en el número de recursos y en el número de usuarios. Para garantizar la escalabilidad es necesario:
Tolerancia a fallos:
Un sistema puede fallar, y seguro que falla. Puede que falle un componente, o una parte del mismo. Pero
el sistema debe seguir funcionando.
132
Miguel Ángel Cifredo
macifredo@gmail.com
Transparencia:
Oculta ciertos aspectos del sistema a las aplicaciones que actúan en el mismo.
Resulta importante encontrar cierto equilibrio entre nivel alto de transparencia y el rendimiento del sistema.
- Transparencia de acceso: Permite acceder a recursos locales y remotos con las mismas operaciones.
- Transparencia de replicación: Permite que haya varias instancias en ejecución de un mismo recurso.
- Transparencia de fallos: Permite que el sistema siga funcionando aunque haya algún fallo en algún
componente o recurso.
- Transparencia de movilidad: Permite que los recursos y los usuarios se puedan mover dentro del sistema
sin que afecte a su funcionamiento.
- Transparencia de rendimiento: Permite al sistema ser reconfigurado para mejorar el rendimiento ante
variaciones en la carga de trabajo.
- Transparencia de escalabilidad: Permite al sistema y a las aplicaciones aumentar la escala sin cambios en
la estructura del sistema o en los algoritmos de las aplicaciones que estos utilizan.
Vamos a clasificar los Sistemas Distribuidos en función de la ubicación, jerarquía o relación entre los
componentes lógicos. Distinguimos entonces:
Arquitecturas centralizadas.
Arquitecturas descentralizadas.
Publicación-Suscripción.
Código Móvil.
133
4.3.1 Arquitecturas centralizadas.
Existe una jerarquía definida de manera que unos componentes requieren información o servicios que ofrecen
otros componentes lógicos. Veamos diferentes modelos de arquitecturas centralizadas.
134
Miguel Ángel Cifredo
macifredo@gmail.com
Muchos servicios de comercio Web de hoy en día están implementados en diferentes servidores. Los
cuales mantienen bases de datos replicadas o distribuidas. Su principal motivación es el desempeño
(ejemplo: cnn.com, servidores para descarga, etc.) y la confiabilidad.
Servidores Proxy:
Código móvil:
Código enviado a un proceso cliente para que realice una tarea específica.
Ejemplos: Applets de Java y los Mensajes Activos conteniendo código de protocolo de comunicación.
Paso 1) El cliente solicita la descarga del Applet Paso 2) El cliente interactúa con el Applet.
Agentes Móviles:
Objeto que puede moverse autónomamente a lo largo de la red desde un host hasta otro, con su código,
datos y estado de ejecución y realizar ciertas tareas en nombre de un usuario (Mercados virtuales,
programas gusano). Sus principales ventajas son la flexibilidad y el ahorro en costo de comunicación.
135
4.3.1.2 Arquitectura multicapa o multiestrato (n-tier).
Los componentes pueden emitir peticiones y responder a estos a cerca de los recursos concretos que gestionan.
Esto es, se convierten en clientes y servidores a la vez.
Características principales:
Está formada por tres componentes distribuidos en dos niveles (nivel cliente y nivel servidor):
Problemas de 2-tier:
- Escasa escalabilidad en servidores con lógica de negocio compleja o con grandes bases de datos,
lo que dificulta, por ejemplo, la replicación.
- Rigidez, modificaciones en la lógica de aplicación, ya que supondrían grandes cambios a realizar
en la totalidad de los clientes.
- Difícil evolución del servidor.
- Alto acoplamiento/dependencia del cliente respecto del servidor.
136
Miguel Ángel Cifredo
macifredo@gmail.com
Extensión del modelo tradicional que pretende aumentar el desacoplamiento entre servidor y clientes,
que introduce una capa intermedia separando el servidor en 2 componentes:
La lógica de aplicación se reparte en diferentes capas/niveles ubicadas entre el cliente y los datos, donde
las capas intermedias proporcionan servicios entre sí. De esta forma, cada nivel se comunica sólo con los
niveles contiguos a través de interfaces bien definidas; esto es, la capa k ofrece servicios a capa k - 1 y
demanda servicios de capa k + 1.
Es una estructura típica de los sistemas basados en componentes distribuidos (objetos distribuidos).
137
4.3.2 Arquitecturas descentralizadas.
Se trata de una distribución horizontal, donde un cliente o un servidor puede estar físicamente dividido en partes
lógicamente equivalentes, pero cada una de dichas partes opera con su propio conjunto integral de datos,
balanceando así la carga del sistema.
Los diferentes componentes del sistema actúan como clientes y como servidores al mismo tiempo.
Ponen parte de sus recursos (procesamiento, disco, ancho de banda, etc.) a disposición directa del resto de
componentes sin la necesidad de una instancia de coordinación central.
Napster:
- Autonomía de los usuarios. Los ficheros disponibles son los que los usuarios deciden aportar.
- Conectividad puntual (ad-hoc). La disponibilidad de un fichero depende si lo tiene algún usuario
conectado.
- Tolerancia a fallos. Muchos usuarios ofrecen el mismo fichero.
- Coste del sistema. Los recursos para almacenar los ficheros son proporcionados por los usuarios.
- Auto organización. El sistema evoluciona y se adapta a medida que los usuarios se conectan o se
desconectan.
- Escalabilidad. El sistema soporta muchos usuarios sin que se degrade.
- Mejora del rendimiento. Al haber muchas réplicas de un mismo fichero, el proceso de
carga/descarga está repartida.
138
Miguel Ángel Cifredo
macifredo@gmail.com
4.3.2.2 No estructuradas.
Se trata de sistemas de iguales (peer) que se conectan sin conocer la topología de la red, que se sobreponen
fácilmente ante las conexiones y desconexiones de los nodos.
Las consultas se envían usando técnicas para epidémicas, esto es, los iguales envían las peticiones a los nodos
vecinos, que contienen la información devuelven los resultados. Estas técnicas, basadas en inundación, son útiles
para localizar objetos altamente replicados.
- No tienen buen comportamiento ante búsquedas de objetos que están poco replicados.
- Cuando tienen que gestionar un ritmo elevado de consultas, o hay crecimientos repentinos del sistema,
se sobrecargan y tienen problemas de escalabilidad.
El protocolo de búsqueda es muy simple: un nodo envía una petición a sus nodos vecinos, y estos reenvían la
petición a sus nodos vecinos respectivamente y, cuando encuentran el objeto buscado, responde al nodo emisor.
- Desventaja: Poca escalabilidad, ya que los costes de la búsqueda crecen exponencialmente a medida
que el número de nodos aumenta. A pesar de esta desventaja, sistemas como Gnutella han demostrado
su viabilidad en redes de tamaño medio.
Caminos aleatorios (random walks). La búsqueda usando un número de nodos vecinos concretos y aleatorios.
Este sistema es más escalable que el método de inundación.
Controla el radio de búsqueda mediante TTL (Time To Live) del mensaje de búsqueda, iniciando la búsqueda con
un TTL de 1, si no encontramos el objeto entonces iniciamos una nueva búsqueda con TTL de 2, y así
sucesivamente hasta encontrar el objeto o cesar el proceso de búsqueda.
Otra mejora es poder seleccionar el siguiente paso en función de valores de ciertos parámetros como puede ser
la popularidad estimada del siguiente nodo, seleccionando así el nodo con más posibilidades de tener el objeto
que buscamos.
139
Mecanismos para el mantenimiento de la información.
Vista parcial. Cada nodo conoce la lista de sus nodos vecinos. Así, para mantenerla se utilizan mecanismos
epidémicos (gossip). Cuando dos nodos se conectan, comparten su información y la que han recibido de otros
nodos, propagándose la información y llegando a “infectar” el sistema completo.
La dirección, así como la proactividad de los componentes, diferencian dos modos de operación:
- Modo de envío inducido (pull mode): Los nodos esperan pasivos a recibir información de otros y, sólo un
nodo actualiza su información cuando otro se pone en contacto con él.
- Modo de envío automático (push mode): Los nodos, de manera periódica, se comunican con algún otro
nodo con el fin de actualizar su información.
Gnutella Freenet
Para localizar un objeto, un nodo pregunta a sus Red descentralizada para el intercambio de
vecinos y estos inundan a sus nodos vecinos hasta información.
un determinado radio. Cuando la petición llega a Su objetivo es mantener el anonimato de sus
un nodo que ofrece el objeto, este contesta usuarios, tanto de los que aportan información como
directamente al nodo que inició la búsqueda. La de los que acceden a ella. Cada nodo guarda
descarga se realiza directamente de los nodos que información y aporta información, de forma que, una
contestan a la búsqueda. vez añadido un fichero a la red, se hace casi imposible
saber de dónde proviene o quién lo ha originado. No
es posible eliminar información de la red, sino que
esta desaparece a medida de que deja de ser
accedida.
140
Miguel Ángel Cifredo
macifredo@gmail.com
4.3.2.3 Estructuradas.
Topología fuertemente controlada donde el contenido va a determinados nodos para optimizar las consultas.
- Los identificadores de los objetos se asignan de manera consistente a los iguales, dentro de un espacio
con muchos identificadores.
- El protocolo mapea las claves a un único nodo entre los que están conectados.
Son la evolución de los modelos peer-to-peer (Napster y Gnutella), que permite la localización eficiente de datos
utilizando un índice descentralizado y uniformemente repartido entre los nodos del sistema. Cada nodo es
análogo a una celda de una tabla hash que permite almacenamiento y recuperación de información de manera
eficiente en un entorno distribuido.
141
4.3.2.4 Sistemas Híbridos.
Super-peers (Super-igual):
Un super-igual es un nodo que actúa como índice o gestor de la información del sistema. Suelen ser nodos que
se mantienen conectados y sufren poco dinamismo durante la vida del sistema. Se organizan en una red tipo
peer-to-peer formando una estructura de dos o más niveles. El problema es la elección de los nodos super-
iguales.
KaZaA
En el proceso de búsqueda, el peer hace una pregunta al super-peer al que está conectado y
este hace un broadcast a los otros super-peers.
eDonkey
Los servidores actúan como concentradores para los clientes y les permite localizar los ficheros.
Controla la bajada concurrente de ficheros desde varias ubicaciones, el uso de funciones
resume para detección de ficheros corruptos, la compartición parcial de ficheros mientras se
bajan y además cuentan con métodos expresivos para realizar búsquedas de ficheros.
Para que un nodo pueda conectarse al sistema, necesita conocer la ubicación de algún peer
que actúe como servidor. Al conectarse, el cliente manda al servidor un listado de los ficheros
que comparte.
Skype
Funciona usando una organización de super-peers similar a la de KaZaA (Skype fue fundada por
los creadores de KaZaA).
142
Miguel Ángel Cifredo
macifredo@gmail.com
Trackers:
Se trata de servidores dedicados a mantener información sobre un conjunto de peers que comparten o
almacenan un determinado contenido.
BitTorrent
El nodo que origina la información no tiene que soportar todo el coste de los recursos para servir
dicha información, sino que los servidores almacenan un fichero que contiene la información
sobre un fichero compartido (longitud, nombre, información de resumen y la URL del tracker).
El tracker conoce todos los peers que tienen el fichero (tanto total como parcialmente) y hace que
los peers se conecten para subir o bajar dichos ficheros.
- El tracker le devuelve una lista aleatoria de nodos que están descargando el mismo fichero
en ese momento.
- BitTorrent parte los ficheros en trozos de 256 KB para poder saber qué tiene cada uno de
los peers.
- Cada peer que está bajando el fichero, anuncia al resto de peers qué trozos tiene.
- El protocolo cuenta con mecanismos para penalizar a los usuarios que no comparten.
143
4.3.3 Publicación-Suscripción.
Paradigma:
Productor de información
- Aplicación que contiene la información que hay que difundir.
- La información se publica sin saber quién la recibirá.
- La información se envía a través de canales.
Consumidor de información
- Aplicación interesada en recibir información.
- El consumidor se suscribe a los canales que diseminan la información que le interesa.
Mediador
- Aplicación que esté entre el productor y el consumidor.
- Recibe información actualizada de los productores y peticiones de suscripción de los consumidores.
- Encamina la información publicada a los destinatarios suscritos al canal.
Canal
Son conectores lógicos entre los productores y los consumidores, y determina:
Localización:
- Para los usuarios es un problema saber dónde está la información que le interesa.
- En los sistemas P/S el usuario se suscribe a unos canales, y ahora es el proveedor de información
quien que se encarga de hacerla llegar a los usuarios interesados.
Focalización:
Personalización:
- El usuario puede especificar ciertos requerimientos, antes de que los datos y sus propiedades se
entreguen, por ejemplo, el formato de los datos, la prioridad, palabras clave, etc.
Actualidad:
Adaptación (tailoring):
- La información fluye sólo hacia los consumidores que desean obtenerla, reduciendo así el tráfico.
145
4.3.4 Código móvil.
Objetivos:
Usar la movilidad para cambiar dinámicamente la distancia entre el procesamiento, la fuente y el destino
de los datos. Para mejorar la proximidad la calidad de las interacciones.
Máquinas virtuales.
Lenguajes de Scripting.
o Programación web:
- En el lado del servidor: PHP, ASP (Active Server Pages), JSP (JavaServerPages), ColdFusion,
IPTSCRAE, Lasso, MIVA Script, SMX, XSLT, …
- En el lado del cliente: JavaScript, JScript, VBScript, Tcl, …
o Perl.
o PostScript.
146
Miguel Ángel Cifredo
macifredo@gmail.com
Frente al paradigma clásico cliente/servidor, surge la idea de movilidad de código. Veamos algunas formas de
llevar a cabo una tarea:
1. Hazla tú mismo.
El cliente tiene el conocimiento para realizar una tarea pero no tiene los recursos necesarios (potencia de
cálculo, datos, etc.) así que los recursos necesarios se encuentran en un ordenador remoto.
Se presupone que el código proporcionado se ejecutará en un entorno protegido, esto es, un cliente no
debe interferir en otros clientes por lo que existe una cierta confianza del servidor en los clientes.
El ordenador que ejecuta el código remoto ofrece un servicio programable, un lenguaje de programación
completo. Ejemplos de estos lenguajes son:
- PostScript: Permite la interacción entre un procesador de textos y una impresora (recurso), la cual
tiene un intérprete del lenguaje PostScript que ejecuta el código (fichero PostScript).
147
4. Pregúntale a otro sistema cómo se realiza: Código bajo demanda.
El cliente tiene acceso a un conjunto de recursos, pero el cliente no tiene el conocimiento necesario para
procesarlos, por lo que envía una petición al servidor remoto y este le envía el código necesario para llevarlo
a cabo. El cliente debe confiar en el servidor que le suministra el código y ejecuta el código localmente.
Ejemplo más conocido: Applet
Los applets permiten añadir funcionalidades a un cliente sin tener que modificarlo.
5. Agente móvil.
Una unidad de computación se mueve a un ordenador remoto, llevándose su estado, parte de código que
necesita y los datos necesarios para llevar a cabo la tarea.
Aporta más dinamismo al sistema, ya que se puede decidir cuándo mover el código de un ordenador a otro
para mejorar el rendimiento y acercar así el código hacia los datos que tiene que procesar.
Son muy útiles cuando el volumen de datos a procesar es muy elevado y se encuentran muy distribuidos.
148
Miguel Ángel Cifredo
macifredo@gmail.com
- Clústeres.
- Grid.
- Desktop Grid.
- Cloud Computing.
El término clúster (del inglés cluster, "grupo" o "raíz") se aplica a los conjuntos de computadoras unidos entre sí
normalmente por una red de alta velocidad y que se comportan como si fuesen una única computadora. La
tecnología de clústeres ha evolucionado en apoyo de actividades que van desde aplicaciones de supercómputo
y software para aplicaciones críticas, servidores web y comercio electrónico, hasta bases de datos de alto
rendimiento, entre otros usos.
El cómputo con clústeres surge como resultado de la convergencia de varias tendencias actuales que incluyen la
disponibilidad de microprocesadores económicos de alto rendimiento y redes de alta velocidad, el desarrollo de
herramientas de software para cómputo distribuido de alto rendimiento, así como la creciente necesidad de
potencia computacional para aplicaciones que la requieran.
Simplemente, un clúster es un grupo de múltiples ordenadores unidos mediante una red de alta velocidad, de
tal forma que el conjunto es visto como un único ordenador, más potente que los comunes de escritorio. Los
clústeres son usualmente empleados para mejorar el rendimiento y/o la disponibilidad por encima de la que es
provista por un solo computador típicamente siendo más económico que computadores individuales de rapidez
y disponibilidad comparables.
Los clústeres determinan una arquitectura distribuida formada por un conjunto de computadores
independientes interconectados que funciona como un único sistema (SSI, Single System Image) que intenta dar
la visión de cara al usuario de un sistema único. Todo el clúster se muestra como un monoprocesador virtual.
149
5.2 Clúster Beowulf.
Fue desarrollado por primera vez en 1994 por Donald Becker y Thomas
Sterling para la NASA. Agruparon 16 procesadores Intel DX4 de 100 MHz, los
interconectaron con tecnología vía channel bonded Ethernet a 10 Mbps, en Un cluster Beowulf casero
equipos viejos con Linux instalado como sistema operativo, pero más tarde
se continuó el desarrollo una comunidad internacional interesados en la computación científica. En 2001 aparece
Locus Supercluster, con 1.416 procesadores Pentium III 1 GHz (708 nodos, cada nodo 2 procesadores) 364 GB de
RAM y 22.240 GB de HDD, SO Linux y tecnología de interconexión Fast Ethernet a 1.416 Gflops (su principal área
de aplicación es el descubrimiento de nuevas sustancias para uso farmacéutico).
Un clúster Beowulf es por lo general un grupo de computadoras compatibles con IBM, sin monitor, ratón ni
tarjeta de video, que utiliza software de código abierto. Para que un grupo de computadoras pueda ser
considerado un Beowulf deben cumplir ciertos requisitos como, contar con un nodo como servidor (front-end),
disponer de un sistema de ficheros compartido y el uso de MPI como herramienta de programación paralela.
En otras palabras, un Beowulf es un conjunto de nodos minimalistas (cada uno de ellos tiene lo mínimo para
poder funcionar), ligados por un medio de comunicaciones barato, en el que la topología de red se ha diseñado
para resolver un tipo de problema específico. En dicho conjunto se ejecutan aplicaciones paralelas.
Cada nodo habitualmente consta de placa madre, CPU, memoria y dispositivo de comunicaciones, y donde uno
de los nodos es el nodo servidor. Se trata de un ordenador completo. Todos los nodos emplean Linux como SO.
La programación es fuertemente dependiente de la arquitectura; se pueden usar sockets o librerías de paso de
mensajes (PVM o MPI). Debido a que los Beowulf emplean memoria distribuida, la comunicación se hace
mediante paso de mensajes. Si un programa está paralelizado usando PVM, MPI o sockets, la adaptación al
Beowulf es trivial. Si no lo está, la paralelización tiene un cierto grado de dificultad.
La mayor parte del software para Beowulf es de dominio público (sistemas operativos, compiladores, librerías,
programas específicos,etc.).
150
Miguel Ángel Cifredo
macifredo@gmail.com
- LSF: http://www.platform.com/
- SGE: http://www.sun.com/grid/
- NQE: http://www.cray.com/
- LL: http://www.ibm.com/systems/clusters/software/loadleveler/
- PBS: http://www.pbsgridworks.com/
En la vida real hay muchos problemas en los que la potencia computacional se queda corta, tales como la
meteorología, la física de la materia condensada, el estudio de las proteínas, el de la generación de imágenes
por computador con modelos realísticos, el secuenciamiento del genoma humano, etc. Así pues, es necesario
recurrir a los supercomputadores.
Sin embargo, no todos los laboratorios se pueden permitir la compra de un supercomputador prefabricado que
cubra sus necesidades, para lo que requieren de una gran inversión, y cuentan con escasos presupuestos.
Entonces, ¿Cómo consiguen la potencia necesaria aun teniendo el problema del presupuesto? Hay dos
alternativas:
Conseguir máquinas viejas o comprar máquinas nuevas de baja potencia y montar Linux en todas ellas
e instalar encima una MOSIX (Cluster Management System).
Con el mismo coste que la alternativa anterior se puede construir un supercomputador de clase Beowulf,
que ofrece un mayor rendimiento.
De las dos maneras que acabamos de describir tenemos un supercomputador de bajo coste.
¿Qué es un COW?
Un Clúster Of Workstations (COW) es un conjunto de nodos de potencia computacional baja o media (es decir
hardware con la mejor relación FLOP/$ posible) que comunicamos entre sí mediante una red para resolver un
determinado problema.
Los nodos del COW son normalmente estaciones de trabajo funcionales (CPU, RAM, HDD, tarjeta gráfica,
monitor, teclado) y, excepcionalmente, son nodos minimalistas.
151
¿Por qué surgen los COW?
Para diseñar un programa paralelo basado en Beowulf, el conocimiento de la topología de red y del número de
procesadores son críticos.
Debemos tener en cuenta que un Beowulf, en principio, no hace reparto automático de carga, por lo que un
programa, teniendo en cuenta la topología del Beowulf, es más rápido que el equivalente en un COW, por la
ausencia de colisiones. Programar un código capaz de adaptarse al Beowulf (topología de red y número de
procesadores) complica su desarrollo del código. Si no tenemos el código fuente de una aplicación y deseamos
adaptarla a un clúster paralelo, emplear una arquitectura Beowulf lleva a un código poco eficiente.
Los COW se ejecutan sobre alguna versión de UNIX, como Linux y tienen instalado MOSIX. MOSIX es un software
diseñado específicamente para incorporar al kernel la capacidad de computación en clúster. En los COW, el kernel
de los distintos nodos ajusta la carga migrando algunos procesos, en caso de que se encuentre que un nodo está
con carga excesiva o con poca memoria. Este ajuste automático de carga entre nodos, con migraciones de
procesos entre nodos, constituye la columna vertebral de los COW y su gran diferencia con los Beowulf.
Los algoritmos de MOSIX son distribuidos, esto es, cada nodo es maestro para los procesos que se crearon
localmente y servidor para los procesos remotos que migraron desde otros nodos.
Cualquier máquina perteneciente al COW, en la que entremos, se comportará como una única máquina SMP
con tantos procesadores como tenga todo el clúster. Esa visión de máquina SMP la comparten tanto el usuario
que lanza la aplicación como el programador que la desarrolla. Nos podemos despreocupar completamente con
la paralelización del código y el reparto de procesadores, porque de eso se encarga MOSIX.
Los COW dan toda su potencia mediante el modelo de programación fork and forget. Si deseamos emplear el
paralelismo de un COW con toda su potencia, nos basta con programar haciendo llamadas a fork.
Beowulf vs COW
Cada nodo de un Beowulf se dedica exclusivamente a procesos del supercomputador y no sirven para
otro propósito. Cada nodo de un COW puede servir para diversos propósitos ya que puede funcionar de
forma independiente, es decir, sin necesidad de participar del sistema distribuido.
Los nodos Beowulf son más baratos que los nodos COW, ya que no necesitan monitor, teclado, ratón.
Por tanto, con igual presupuesto, podemos tener un mayor número de nodos Beowulf y más rápidos.
- La red que une los nodos de un Beowulf es privada y está dedicada exclusivamente al Beowulf
(tenemos más ancho de banda en la red, para nosotros).
- Los nodos de un Beowulf no son accesibles directamente desde fuera, pero en los de un COW sí
que puede darse el caso.
A la hora de programar nos da igual la topología de la red porque MOSIX nos abstrae de ella. En Beowulf
la topología sí es importante a la hora de programar una aplicación.
152
Miguel Ángel Cifredo
macifredo@gmail.com
En un COW el usuario no sabe nada acerca de la máquina, ni cómo configurarla, ni cómo montarla.
Habitualmente, el usuario y el administrador son dos personas distintas. En un Beowulf el equipo de
desarrollo del programa (o el usuario en caso de ser sólo uno) tienen que saber desde diseñar una
topología de red y hacer aplicaciones paralelas hasta cómo montar los ordenadores.
Un COW tiene un número determinado de usuarios heterogéneos, con una cuenta por usuario, un
administrador y un conjunto indeterminado de problemas de cada usuario para resolver. Un Beowulf se
desarrolla para resolver un problema; habitualmente sólo resuelve un mismo tipo de problemas al
mismo tiempo. Tiene un usuario que opera con la máquina y la administra (un gurú en redes y en Linux)
así como un grupo de expertos que le da al gurú los problemas para que los resuelva.
Un COW no necesita que se recompilen los programas, mientras que Beowulf sí.
Un COW permite añadir (mejora automática del rendimiento de aplicaciones paralelas que ya estén en
ejecución, sin necesitar ser reiniciadas) y eliminar nodos en caliente.
Los COW dan mejores respuestas que los Beowulf cuando cae un nodo.
En un COW todos los nodos han de ser de la misma arquitectura, tener CPUs con juegos de instrucciones
compatibles y una misma distribución de UNIX.
Hoy en día un COW sólo corre en plataforma Intel. Necesita como mínimo un 386 con coprocesador (no
funciona con 486SX).
Es mejor un Beowulf si disponemos del código fuente de la aplicación para adaptar el modelo de
paralelismo de la aplicación a la topología de la red y al número de procesadores. Hacer esto es
interesante y está justificado por los beneficios que obtendremos.
Los COW son mejores que los Beowulf en los siguientes casos:
Ambas son tecnologías suelen correr bajo Linux y proporcionan supercomputación a bajo coste.
153
Concluyendo:
Un COW es una solución rápida de configurar, fácil de programar, de una potencia computacional buena
y no exige demasiados conocimientos a los usuarios que operan con las máquinas.
Un Beowulf exige gran cantidad de conocimientos y dedicación para la puesta en marcha, desarrollo y
ejecución; a cambio su rendimiento es realmente impresionante, en comparación al bajo costo
necesario. El usuario-operador se encarga de tener todo optimizado para el problema a resolver.
Ejemplo de PCs completos conectados por una red, aprovechando los tiempos no usados:
En entornos típicos con estaciones de trabajo se desperdicia cerca del 80% de ciclos totales de CPU. Por lo que
se promueve el uso de estas estaciones de trabajo ejecutando procesos de forma totalmente transparente en
máquinas remotas que se encuentran inactivas.
Los usuarios de las estaciones de trabajo inactivas no deberían observar degradación alguna del rendimiento
como consecuencia de la ejecución de los procesos remotos.
154
Miguel Ángel Cifredo
macifredo@gmail.com
Esta supercomputadora de IBM consiguió resolver las ecuaciones del Test de Linpack a una velocidad de 36’01
teraflops (36’01 billones de operaciones en punto flotante por segundo). Superó por poco a la máquina japonesa
que la ostentaba en 35’86 teraflops. Sin embargo hay que tener en cuenta que los FLOPS como métrica de
rendimiento únicamente evalúan los cálculos de operaciones matemáticas con números decimales sin embargo
el rendimiento de un sistema no puede medirse por este único parámetro ya que es claro que hay otras
operaciones vitales tales como las operaciones de transferencia de información entre distintos procesadores y/o
unidades lógicas o funcionales dentro del sistema.
El Blue Gene/L ocupa unos 320 metros cuadrados (100 veces menos que la nipona) y consume tan solo 216
kilovatios, frente a los 6.000 que gasta su rival.
155
5.8 Grid.
Escenario típico, la Agencia Espacial Europea (ESA, European Space Agency). Los datos científicos recibidos de
los satélites son importantes para los científicos. Por ejemplo, la Estación de Seguimiento de Satélites de
Villafranca del Castillo (VILSPA) mantiene un archivo con todos los datos científicos obtenidos de las misiones
completadas y en marcha:
- El observatorio XMM-Newton.
- El observatorio espacial de infrarrojos.
- El explorador internacional de ultravioletas.
Esta información está disponible y se comparte con la comunidad científica para fines de investigación.
La computación Grid es una tecnología innovadora que permite utilizar de forma coordinada todo tipo de
recursos (entre ellos cómputo, almacenamiento y aplicaciones específicas) que no están sujetos a un control
centralizado. En este sentido es una nueva forma de computación distribuida, en la que los recursos pueden ser
heterogéneos (diferentes arquitecturas, supercomputadores, clústeres...) y se encuentran conectados mediante
redes de área extensa (por ejemplo Internet). Desarrollado en ámbitos científicos a principios de 1990, su
entrada al mercado comercial sigue la idea de la llamada Utility computing supone una importante revolución.
El término Grid se refiere a una infraestructura que permite la integración y el uso colectivo de ordenadores de
alto rendimiento, redes y bases de datos que son propiedad y están administrados por diferentes instituciones.
Puesto que la colaboración entre instituciones envuelve un intercambio de datos, o de tiempo de computación,
el propósito del Grid es facilitar la integración de recursos computacionales. Universidades, laboratorios de
investigación o empresas se asocian para formar grids utilizado ciertos tipos de software que implementan este
concepto.
En la computación Grid, las redes pueden ser vistas como una forma de computación distribuida donde un
“supercomputador virtual” está compuesto por una serie de computadores agrupados para realizar grandes
tareas.
156
Miguel Ángel Cifredo
macifredo@gmail.com
Definiciones:
Un Sistema Grid es una colección de recursos distribuidos conectados por una red, ubicados en
diferentes dominios administrativos, accesibles a los usuarios y aplicaciones con el objetivo de reducir el
coste e incrementar el rendimiento.
El middleware del Grid es el software que facilita la escritura de aplicaciones de Grid y la gestión de su
infraestructura.
Principales características:
Gran escala.
Distribución geográfica.
Heterogeneidad.
Compartición de recursos.
Múltiples dominios de administración.
Coordinación de recursos.
Acceso transparente.
Acceso seguro.
Acceso coherente.
El concepto de Grid se relaciona con la nueva generación del protocolo IP. El nuevo protocolo de Internet IPv6
permitirá trabajar con una Internet más rápida y accesible. Una de las ideas clave en la superación de las
limitaciones actuales de Internet IPv4 es la aparición de nuevos niveles de servicio que harán uso de la nueva
capacidad de la red para intercomunicar los ordenadores.
Este avance en la comunicación permitirá el avance de las ideas de Grid Computing al utilizar como soporte la
altísima conectividad de Internet. Es por ello que uno de los campos de mayor innovación en el uso del Grid
Computing, fuera de los conceptos de supercomputación, es el desarrollo de un estándar para definir los Grid
Services frente a los actuales Web Services.
En definitiva, el Grid Computing supone un avance respecto a la World Wide Web, que proporciona un acceso
transparente a la información que está almacenada en millones de ordenadores repartidos por todo el mundo.
Frente a ello, el Grid Computing es una infraestructura nueva que proporciona acceso transparente a potencia
de cálculo y capacidad de almacenamiento distribuida por una organización o por todo el mundo.
Un requisito que cualquier Grid debe cumplir es que los datos deben compartirse entre miles de usuarios con
intereses distintos. Se deben enlazar los centros principales de supercomputación, no sólo los PC. Se debe
asegurar que los datos sean accesibles en cualquier lugar y en cualquier momento. Debe armonizar las distintas
políticas de gestión de muchos centros diferentes. Debe proporcionar seguridad implícita.
157
Beneficios que se obtienen:
Proporciona un mecanismo de colaboración transparente entre grupos dispersos, tales como científicos,
comerciales, etc.
Posibilita el funcionamiento de aplicaciones a gran escala.
Facilita el acceso a recursos distribuidos desde nuestros propios PC.
Todos estos objetivos y beneficios se engloban en la idea de "e-Ciencia".
La tecnología derivada del Grid Computing abre un enorme abanico de posibilidades para el desarrollo de
aplicaciones en muchos sectores, como el desarrollo científico y tecnológico, educación, sanidad, y
administración pública.
Principales utilidades:
- Supercomputación distribuida.
- Computación de alto rendimiento.
- Computación bajo demanda.
- Computación intensiva.
- Computación colaborativa.
La computación distribuida o “Grid”, en general, es un tipo especial de computación paralela que se basa en
computadores completos (con CPUs, almacenamiento, fuentes de alimentación, interfaces de red, etc.)
conectados a una red (privada, pública o Internet) por una interfaz de red convencional aportando un hardware
sencillo, en comparación al diseño y construcción de baja eficiencia de un pequeño número de
supercomputadores personalizados. La principal desventaja en cuanto a rendimiento es que los diversos
procesadores y áreas locales de almacenamiento no tienen conexiones de alta velocidad. Esta composición es
apropiada para aplicaciones donde pueden darse lugar múltiples computaciones paralelas independientes, sin
la necesidad de comunicar resultados inmediatos entre procesadores.
158
Miguel Ángel Cifredo
macifredo@gmail.com
IntraGrids InterGrid
Una misma organización. Múltiples organizaciones.
Recursos heterogéneos. Múltiples dominios de administración.
Conexión LAN. Conexión usando WAN.
Menos problemas de seguridad. La seguridad es un aspecto muy importante.
Menos problemas de fiabilidad. Problemas de fiabilidad.
IntraGrid.
Una topología típica intraGrid, como se ilustra en la figura, puede existir dentro de una única empresa,
proporcionando un conjunto básico de servicios de la red. La única organización podría estar compuesta por una
serie de equipos que comparten un dominio de seguridad común y compartir datos internamente en una red
privada. Las características principales de una intraGrid son un único proveedor de seguridad, ancho de banda
alto en la red privada y alta disponibilidad, y contar con un único entorno dentro de la única red. Dentro de una
intraGrid es más fácil diseñar y operar las redes computacionales y de datos. Una intraGrid proporciona un
conjunto relativamente estable de los recursos informáticos y la capacidad de compartir fácilmente datos entre
sistemas de redes. La empresa podría considerar una intraGrid apropiada si tiene iniciativas para la reducción
económica a gran escala en la gestión interna del trabajo, o quiere empezar a explorar el uso de la red interna
primeramente al habilitar las aplicaciones empresariales verticales.
159
ExtraGrid.
Sobre la base de una única organización, la extraGrid amplía el concepto al reunir dos o más intraGrids. Una
extraGrid, como se ilustra en la figura superior, normalmente implica más de un proveedor de seguridad, y el
nivel de gestión de la complejidad aumenta. Las características principales de una extraGrid se dispersan en
seguridad, múltiples organizaciones, y la conectividad remota. Dentro de una extraGrid, los recursos se vuelven
más dinámicos y su Grid tiene que ser más reactiva a los recursos y componentes que han fallado. El diseño se
vuelve más complicado y los servicios de información se convierten en relevantes para asegurar que los recursos
de la red tengan acceso a la gestión de carga de trabajo en tiempo de ejecución.
Las empresas se benefician de las extraGrids si existe una iniciativa empresarial para la integración con socios de
negocios externos que sean de confianza. Una extraGrid también podría ser utilizada para B2B y/o para
establecer relaciones de confianza.
InterGrid.
Una interGrid requiere la integración dinámica de aplicaciones, recursos y servicios con los proveedores, clientes
y otras organizaciones autorizadas que obtienen acceso a la red a través de Internet / WAN. Una topología de
interGrid, como se ilustra en la figura, se utiliza principalmente por empresas de ingeniería, industrias científicas,
y empresas del sector financiero. Las características principales de una interGrid incluyen seguridad, múltiples
organizaciones, y la conectividad remota dispersa. Los datos en una red interconectada son datos globales
públicos y aplicaciones, tanto vertical como horizontal, deben ser modificados para una audiencia global. Una
empresa puede asimilar la necesidad de una interGrid si existe una necesidad de computación peer-to-peer, una
comunidad de computación colaborativa, o para realizar procesos con las organizaciones que utilizan la interGrid.
160
Miguel Ángel Cifredo
macifredo@gmail.com
Los diferentes componentes del Grid se organizan en capas, donde en cada capa se construye con los servicios
ofrecidos por la capa inferior, e interactuando con los componentes de su misma capa.
Fábrica.
- Ordenadores: Pueden ser tanto clústeres, como supercomputadores o PCs, además pueden ejecutar
diferente ssistemas operativos.
- Entornos de ejecución:
- Condor (http://www.cs.wisc.edu/condor/).
- SunGridEngine (http://gridengine.sunsource.net/ para la versión libre y
http://www.sun.com/software/gridware/ para la versión comercial).
- Torque (http://www.clusterresources.com/pages/products/torque-resource-manager.php)
- Redes de comunicaciones.
- Dispositivos de almacenamiento.
- Instrumental científico.
161
Núcleo del Middleware Grid.
Proporciona abstracciones y servicios de más alto nivel en entornos de desarrollo de aplicaciones y para
herramientas de programación.
https://www.globus.org/
La Globus Alliance es una asociación internacional dedicada a desarrollar tecnologías necesarias para construir
infraestructuras de computación grid.
El proyecto Globus se define como un conjunto de herramientas de código abierto para construir infraestructuras
Grid. Permite la compartición de capacidad de proceso, de bases de datos y otros recursos de manera segura,
atravesando diferentes límites corporativos, institucionales o geográficos, sin sacrificar la autonomía local.
Donde los usuarios mantienen el control local de los propios recursos.
En 1995 se celebró el congreso SuperComputing’95, donde se demostró que era posible ejecutar aplicaciones
distribuidas de varias áreas científicas entre 17 centros de Estados Unidos conectados por una red de alta
velocidad de 155 Mbps. Este experimento se denominó I-Way, y fue el punto de partida de varios proyectos en
diferentes áreas, con un denominador común que era la compartición de recursos distribuidos de computación.
A partir de este momento, el libro “The Grid: Blueprint for a New Computing Instraestructure”, editado por Ian
Foster y Carl Kesselman supuso el primer paso para establecer unas primeras ideas claras sobre cómo debía
llevarse a cabo esta nueva tecnología.
A partir de estas ideas se desarrolló surgió el Globus toolkit, un proyecto open-source desarrollado en el Argonne
Nacional Laboratory dirigido por Ian Foster en colaboración con el grupo de Carl Kesselman de la Universidad de
Southern California. Globus da los medios básicos de la tecnología para construir un Grid computacional, y se ha
convertido gracias a su evolución y adopción por la comunidad científica como el estándar de facto en la
tecnología Grid.
162
Miguel Ángel Cifredo
macifredo@gmail.com
La arquitectura abierta de Globus se estructura en capas, siguiendo los estándares propuestos por Foster y
Kesselman e incluye servicios software para la monitorización de recursos, descubrimiento y gestión, además de
servicios de seguridad y de gestión de ficheros. Incluye software organizado en áreas como seguridad,
infraestructura de la información, detección de fallos, portabilidad, etc. Está empaquetado como una serie de
componentes que pueden usurase bien independientemente o conjuntamente para desarrollar aplicaciones.
El Globus toolkit fue concebido para eliminar los obstáculos que impide la colaboración entre diferentes
organizaciones o instituciones. Sus servicios centrales (core services), interfaces y protocolos permiten a los
usuarios acceder a los recursos remotos como si estuvieran presentes dentro de su propia sala de máquinas, a
la vez que preservan el control local sobre quién y cuándo puede usar los recursos.
Los elementos del Globus toolkit no asumen que los entornos locales estén
adaptados para soportarlo. En principio fue diseñado e implementado para adaptar
a los muchos y varios entornos locales bajo el cual pueda ejecutarse.
Globus ofrece una serie de servicios básicos para establecer una infraestructura
básica. Éstos son luego usados para construir soluciones específicas de cada
dominio, de alto nivel. Para ello tres principios de diseño clave que se siguen son
mantener los costes de participación bajos, mantener el control local cuando quiera
sea posible, y proveer soporte para la adaptación del toolkit a las necesidades
específicas de cada sitio y cada proyecto.
La base del reloj de arena de Globus representa los miles de recursos sobre los que los servicios de Globus han
sido construidos. Por ejemplo, los sistemas operativos locales de las varias máquinas sobre las que corre Globus,
además de los muchos tipos de redes, los sistemas de planificación, los sistemas de ficheros, etc. El medio es
comprimido por los servicios básicos (core services) que Globus ofrece, y la parte de arriba representa los
servicios de alto nivel que Globus ofrece, así como las propias aplicaciones escritas en Globus.
Las implementaciones locales de los servicios de Globus para un sistema operativo en particular liberan tanto a
los servicios básicos como a los servicios de alto nivel de tener que conocer cuestiones específicas del sistema
operativo. Sólo los servicios locales necesitan conocer cuál es el sistema operativo sobre el que funciona,
liberando a los programadores de aplicaciones de conocer estos detalles.
Una visión más detallada de los servicios ofrecidos por Globus puede ser representado en la siguiente figura:
163
Capas de la arquitectura Globus:
La capa superior contiene herramientas que integran los servicios de la capa inferior o los complementa.
La capa que contiene el núcleo de Globus está formada por los siguientes servicios:
Capa de seguridad GSI (GSI Security Layer). Métodos para autenticar a los usuarios y hacer que las
comunicaciones seguras.
Gestión de Recursos (Grid Resource Management [GRAM, GASS]). Para la asignación de recursos, tales
como el envío de trabajos a ejecutar, monitorización de trabajos y recogida de resultados.
Gestión de Datos (Grid Data Management [Grid FTP, Replica Catalog]). Proporciona utilidades y librerías
para transmitir, almacenar y gestionar grandes volúmenes de datos necesarios para las aplicaciones que
se ejecutan.
La capa de recursos y servicios locales (Grid Resource and Local Services) contiene:
164
Miguel Ángel Cifredo
macifredo@gmail.com
Agregan la capacidad computacional de los extremos de Internet. Por extremos de Internet entendemos todos
aquellos ordenadores de usuarios conectados a la red. Establece un middleware que permite a estos
ordenadores interconectarse y crear una red que agrega las capacidades de computación no usadas.
Aparece el proyecto de Infraestructura Abierta de Berkeley para la Computación en Red (BOINC, Berkeley Open
Infrastructure for Network Computing) como la tecnología más relevante en este área. Se trata de una
infraestructura para la computación distribuida, desarrollada originalmente para el proyecto SETI@home, pero
que actualmente se utiliza para diversos campos como física, medicina nuclear, climatología, etc. La intención de
este proyecto es obtener una capacidad de computación enorme utilizando computadores personales alrededor
del mundo. Los proyectos en los que trabaja este software tienen un denominador común, y es que requieren
una gran capacidad de cálculo.
BOINC ha sido desarrollado por un equipo ubicado en Space Sciences Laboratory en la Universidad de California
en Berkeley, liderado por David P. Anderson, que también lidera SETI@home. Como una plataforma "quasi-
supercomputador", BOINC tiene alrededor de 527.880 computadores activos (hosts) alrededor del mundo
procesando en promedio 5.549 petaFLOPS (el 11 de marzo de 2011), que supera el poder de cómputo del
supercomputador más rápido existente, el Tianhe-I de China, con un ritmo de procesamiento de 2.566
petaFLOPS.
165
5.9 Utility Computing.
Utility computing se define como el suministro de recursos computacionales, como puede ser el procesamiento
y almacenamiento, como un servicio medido similar a las utilidades públicas tradicionales (como la electricidad,
el agua, el gas natural o el teléfono).
Este sistema tiene la ventaja de tener un costo nulo o muy bajo para adquirir hardware; en cambio, los recursos
computacionales son esencialmente alquilados. Los clientes que realizan procesamiento de datos a gran escala
o que están frente a un pico de demanda también pueden evitar los atrasos que resultarían de adquirir y
ensamblar físicamente una gran cantidad de computadoras.
Esta nueva presentación de los servicios de computación se convirtió en el fundamento del cambio como
"on demand computing", software como servicio y modelos de Cloud Computing que propagó aún más la idea
de la computación, la aplicación y la red como un servicio. Había un poco de escepticismo inicial acerca de un
cambio tan significativo. Sin embargo, el nuevo modelo de computación tuvo éxito y finalmente se convirtió en
la corriente principal.
IBM, HP y Microsoft fueron los primeros líderes en el nuevo campo de la Utility Computing con sus unidades de
negocio y los investigadores que trabajan en la arquitectura, el pago y el desarrollo, desafíos del nuevo modelo
de computación. Google, Amazon y otros comenzaron a tomar la iniciativa en 2008, cuando establecieron sus
propios servicios de Utility Computing, de almacenamiento y aplicaciones.
La Utility Computing puede apoyar la computación Grid que tiene la característica de grandes cálculos o
repentinos picos de demanda que son apoyados a través de un gran número de equipos. La Utility Computing,
por lo general está provisto algún tipo de virtualización de modo que la cantidad de almacenamiento o de la
potencia de cálculo disponible es considerablemente mayor que la de un solo equipo en tiempo compartido. Se
utilizan varios servidores en el "back-end" para hacerlo posible. Estos pueden ser un clúster de ordenadores
dedicados construido específicamente para el propósito de ser alquilados, o incluso un superordenador
infrautilizado. La ejecución de un solo cálculo en varios equipos se conoce como computación distribuida.
Empresas con exceso de capacidad de cómputo pueden, de forma rentable, dejar usar sus sistemas a distintos
clientes. Por lo que otras empresas, con demanda de capacidad de cómputo, pueden alquilar la infraestructura
de quién le ofrezca mejor precio o servicio (o relación entre ellos). Así pues:
166
Miguel Ángel Cifredo
macifredo@gmail.com
La computación en la nube, conocido también como servicios en la nube, informática en la nube, nube de
cómputo o nube de conceptos, es un paradigma que permite ofrecer servicios de computación a través de
Internet.
En este tipo de computación todo lo que puede ofrecer un sistema informático se ofrece como servicio, de modo
que los usuarios puedan acceder a los servicios disponibles "en la nube de Internet" sin conocimientos (o, al
menos sin ser expertos) en la gestión de los recursos que usan. Según el IEEE Computer Society, es un paradigma
en el que la información se almacena de manera permanente en servidores de Internet y se envía a cachés
temporales de cliente, lo que incluye equipos de escritorio, centros de ocio, portátiles, etc.
La computación en la nube son servidores de Internet encargados de atender las peticiones en cualquier
momento. Se puede tener acceso a su información o servicio, mediante una conexión a internet desde cualquier
dispositivo móvil o fijo ubicado en cualquier lugar. Sirven a sus usuarios desde varios proveedores de alojamiento
repartidos por todo el mundo. Esta medida reduce los costos, garantiza un mejor tiempo de actividad y que los
sitios web sean invulnerables a los delincuentes informáticos, a los gobiernos locales y a sus redadas policiales.
El Cloud Computing es un nuevo modelo de prestación de servicios de negocio y tecnología, que permite incluso
al usuario acceder a un catálogo de servicios estandarizados y responder con ellos a las necesidades de su
negocio, de forma flexible y adaptativa, en caso de demandas no previsibles o de picos de trabajo, pagando
únicamente por el consumo efectuado, o incluso gratuitamente en caso de proveedores que se financian
mediante publicidad o de organizaciones sin ánimo de lucro.
El cambio que ofrece la computación desde la nube es que permite aumentar el número de servicios basados
en la red. Esto genera beneficios tanto para los proveedores, que pueden ofrecer, de forma más rápida y
eficiente, un mayor número de servicios, como para los usuarios que tienen la posibilidad de acceder a ellos,
disfrutando de la ‘transparencia’ e inmediatez del sistema y de un modelo de pago por consumo. Así, el
consumidor ahorra en costes salariales o en costes en inversión económica (locales, material especializado, etc.).
La computación en nube consigue aportar estas ventajas, apoyándose sobre una infraestructura tecnológica
dinámica que se caracteriza, entre otros factores, por un alto grado de automatización, una rápida movilización
de los recursos, una elevada capacidad de adaptación para atender a una demanda variable, así como
virtualización avanzada a un precio flexible en función del consumo realizado, evitando además el uso
fraudulento del software y la piratería.
La computación en nube es un concepto que incorpora el software como servicio, como en la Web 2.0 y otros
conceptos recientes, también conocidos como tendencias tecnológicas, que tienen en común el que confían en
Internet para satisfacer las necesidades de cómputo de los usuarios.
Por tanto, se trata de un modelo que permite el acceso a través de la red a un conjunto compartido de recursos
informáticos configurables (redes, almacenamiento, aplicaciones y servicios) que pueden ser rápidamente
provisionados y liberados con un esfuerzo mínimo de gestión o interacción con el proveedor de servicios.
Disponibilidad.
Tolerancia a fallos.
Escalabilidad.
Seguridad.
Conectividad.
167
Motivación:
La computación en nube permite acceder a todas sus aplicaciones y documentos desde cualquier lugar del
mundo, dándole por supuesto mucha más libertad que la que le dan las paredes de una empresa o los bordes
de una mesa de trabajo, además facilita la colaboración en grupo al por mayor. Sin embargo, la computación en
la nube no es para todos, hay pros y contras de este tipo de informática que se basa totalmente en Internet.
Este tipo de procedimientos basados en Internet nos libera de la tiranía de la computación de escritorio y abre
nuevas formas de colaboración en grupo. Pero por más atractivo que parezca, la computación en la nube no es
para todos. En general, el usuario final medio se puede beneficiarse de ello, pero algunos usuarios deben evitar
estas aplicaciones basadas en web, al menos por ahora.
Ventajas:
Menor gasto de informática. No necesitamos un ordenador de alta potencia y alto precio para procesar
aplicaciones basadas en web que están en la nube. Dado que las aplicaciones se ejecutan en la nube y
no en el ordenador de escritorio, no necesitamos la potencia de procesamiento o de espacio en disco
duro exigida por el software de escritorio tradicional. Cuando se está utilizando aplicaciones basadas en
web, nuestro ordenador puede ser uno más económico, con un disco duro más pequeño, menos
memoria y procesador. De hecho, un ordenador en este escenario no necesitamos ni siquiera una unidad
de CD o DVD, ya que no hay programas que tengan que ser instalados y los datos o los documentos no
necesitan ser salvados en el disco duro.
Mejora del rendimiento. Con un menor número de programas acaparando la memoria de nuestro
ordenador, veremos un mejor rendimiento. En pocas palabras, los ordenadores que utilicen aplicaciones
de la nube correrán más rápido porque tienen menos programas y procesos cargados en memoria.
Reducción del coste en software. En lugar de comprar costosas aplicaciones de software, podemos
conseguir casi todo lo que necesitamos de forma gratuita o a costes muy bajos. Así es, la mayoría de las
aplicaciones de computación en la nube de hoy, o bien son totalmente gratuitas o tienen costes muy
bajos. Lo cual es mejor que pagar los costes de licencia como el de alguno de los conocidos "suite de
oficina" o la compra de software en general.
168
Miguel Ángel Cifredo
macifredo@gmail.com
Las actualizaciones de software son instantáneas. Otra de las ventajas del software en la nube es que
estás ya no se enfrentan a la elección entre software obsoleto y los altos costes de actualización. Cuando
la aplicación está basada en la web, las actualizaciones desde el punto de vista del usuario, se hacen en
forma automática y simplemente estarán disponibles la próxima vez que iniciemos sesión en la nube. Al
acceder a una aplicación basada en web, tendremos siempre la última versión sin necesidad de pagar o
descargar e instalar actualizaciones.
Aumento de la fiabilidad de los datos. A diferencia de la computación de escritorio, en la que un fallo del
disco duro puede destruir todos nuestros valiosos datos, si el disco de nuestro ordenador simplemente
deja de funcionar no afectaría a nuestros datos. O si el ordenador se bloquea, todos los datos seguirán
en la nube, todavía accesibles. En un mundo donde pocos usuarios individuales de ordenadores hacen
copias de seguridad de sus datos en forma regular, en la computación en la nube se utilizan las últimas
tecnologías de copias de seguridad, y redundancia de discos duros, o sea lo último en seguridad de datos,
por lo que no debemos preocuparnos por realizar las copias de seguridad, la nube las realizará
automáticamente.
Acceso universal a los documentos. ¿No le ha pasado alguna vez que necesita recordar o acceder a algún
dato y no puede hacerlo porque no está en la oficina? Eso no es un problema con el Cloud Computing,
ya que los datos que están en la nube pueden accederse desde cualquier ordenador con conexión a
internet. Todos los datos están disponibles al instante desde cualquier lugar, simplemente no hay
necesidad de llevar sus documentos con nosotros.
Disponibilidad de la última versión. Otra de las ventajas en el caso de documentos relacionados con la
computación en nube es que cuando se edita un documento en algún lugar, la versión editada será lo
que verá al acceder al documento en cualquier otro lugar. La nube siempre se aloja la última versión de
los documentos, siempre y cuando estemos conectado.
Independencia del dispositivo. Finalmente, aquí está una de las ventajas más importantes de la
computación en nube es que ya no estamos atado a un solo ordenador. Podemos cambiar los
ordenadores, que las aplicaciones y los datos seguirán estando en la nube. Podemos utilizar un
dispositivo portátil, y sus aplicaciones y los datos estarán todavía disponibles. No hay necesidad de
comprar un dispositivo específico, un sistema operativo especial o un programa para ese dispositivo en
particular. Los documentos, datos y aplicaciones son los mismos sin importar qué ordenador o
dispositivo que esté utilizando para acceder a ellos. Hoy por hoy se pueden utilizar ordenadores con
Windows, Linux, Mac OS/X, tabletas o Smart Phones, etc. y pronto seguramente habrá muchos más
dispositivos y marcas en el mercado, todos con un objetivo común: poder acceder a la nube.
169
Desventajas:
Hay una serie de razones por las que es posible que haya usuarios no quieran adoptar la computación en la nube
para sus necesidades particulares. Vamos a examinar algunos de los riesgos relacionados con la computación en
la nube:
No funciona bien con conexiones de baja velocidad. Del mismo modo, una conexión a Internet de baja
velocidad, hace que la computación en nube sea en muchos casos imposible. Las aplicaciones basadas
en web requieren una gran cantidad de ancho de banda, al igual que para trabajar con documentos de
gran tamaño. En otras palabras, la computación en la nube funcionará correctamente siempre que la
velocidad de acceso sea suficientemente buena.
Algunas veces puede ser demasiado lento. Incluso con una conexión rápida, las aplicaciones basadas en
web seguramente serán más lentas que aplicaciones similares instaladas en nuestro ordenador de
escritorio. Esto se basa en muchas variables de las que depende el procesamiento en la nube, por
ejemplo cada actualización tiene que ser enviada de ida y vuelta desde nuestro ordenador hacia los
servidores en la nube. Si los servidores de la nube en ese momento están haciendo una copia de
seguridad, o si Internet está demasiado saturado (horas punta de utilización) nunca tendrá una respuesta
instantánea como suele pasar con las aplicaciones de escritorio.
Los datos almacenados pueden no estar seguros. Con la computación en la nube, todos los datos se
almacenan en la nube. ¿Qué tan segura es la nube? ¿Pueden los usuarios no autorizados acceder a
nuestros datos confidenciales? Pueden las empresas que ofrecen servicios de computación en nube
decir que sus datos están seguros. Tal vez sea demasiado pronto para poder afirmar estar
completamente seguro de eso. Sólo el tiempo dirá si nuestros datos están seguros en la nube.
Teóricamente siempre existirá la posibilidad de que los datos almacenados se puedan perder. La mayoría
de las empresas que brindan servicios de computación en la nube toman los recaudos suficientes para
que eso no ocurra, y aseguran que así será, por ejemplo instalando, líneas de datos redundantes
conectadas a firewalls físicos, sistemas de alimentación eléctrica ininterrumpidos, almacenamiento
tolerantes a fallos, servicios de copias de seguridad automáticos, almacenamiento de las copias de
seguridad en ambientes protegidos físicamente (contra incendios o robos), pero no obstante, al ser
medios físicos, nunca nos darán una seguridad del 100%, no nos olvidemos de lo que dijo el famoso
Murphy, todo lo que pueda fallar, fallará...
170
Miguel Ángel Cifredo
macifredo@gmail.com
Como nota común a todos ellos, este tipo de servicios se factura en función de los recursos consumidos que
normalmente son el reflejo del nivel de actividad del sistema.
Software como Servicio. Modelo de distribución de software donde una empresa sirve el
mantenimiento, soporte y operación que usará el cliente durante el tiempo que haya contratado el
servicio. El cliente usará el sistema alojado por esa empresa, la cual mantendrá la información del cliente
en sus sistemas y proveerá los recursos necesarios para explotar esa información.
Plataforma como Servicio. Aunque suele identificarse como una evolución de SaaS, es más bien un
modelo en el que se ofrece todo lo necesario para soportar el ciclo de vida completo de construcción y
puesta en marcha de aplicaciones y servicios web completamente disponibles en Internet. Otra
característica importante es que no hay descarga de software que instalar en los equipos de los
desarrolladores. PasS ofrece múltiples servicios, pero provisionados como solución integral en la web.
Ejemplos: Amazon Web Services (SimpleDB y SQS), Google App Engine, IBM IT Factory, Force.
Ejemplos: Amazon Web Services EC2, Sun Grid, IBM Blue Cloud, GoGrid, …
Almacenamiento como servicio. Modelo de negocios en el cual una compañía renta espacio en su
infraestructura de almacenamiento a otra compañía más pequeña o individuo. A nivel corporativo, los
proveedores de SaaS apuntan a aplicaciones de almacenamiento secundario, promoviendo SaaS como
una forma conveniente de gestionar los respaldos. Generalmente es visto como una buena alternativa
para una pequeña o mediana empresa que carezca del presupuesto y/o del personal técnico para
implementar y mantener su propia infraestructura de almacenamiento.
171
Main Access &
Service Class Service content
Management Tool
Cloud Applications
- Social networks.
- Office suites.
- CRM.
SaaS Web browser - Video processing.
- eMail.
- Virtual desktop.
- Communicacion.
- Games.
Cloud Platform
- Programming languages.
- Frameworks.
- Mashups editors.
PaaS Cloud Development Environment - Structured data.
- Execution runtime.
- Database.
- Web server.
- Development tolos.
Cloud infraestructura
- Compute servers.
- Data Storage.
- Firewall.
IaaS Virtual Infraestructure Manager
- Load balancer.
- Virtual machines.
- Storage servers.
- Networking.
Cloud clientes
Web browser, mobile app, thin client, terminal emulator, …
Soporte de virtualización.
Aprovisionamiento de recursos bajo demanda.
Virtualización del almacenamiento.
Interfaz con clouds públicos.
Virtualización de la red.
Asignación dinámica de los recursos.
Alta disponibilidad y recuperación.
- Apache VCL.
- AppLogicCitrix Essentials.
- EnomalyECP.
- Eucaliptus.
- Nimbus3.
- OpenNebula.
- OpenPEX.
- VMWare vSphere and vCloud.
172
Miguel Ángel Cifredo
macifredo@gmail.com
Características:
Distribución geográfica.
Interfaces de usuarios y accesos a servidores.
Open Cloud Computing Interface Working Group (OCCIWG).
Reserva adelantada de recursos.
Escalabilidad y equilibrio de carga.
Acuerdo de nivel de servicio.
Elección de hypervisor y sistema operativo.
Ejemplos:
- Cantidad almacenada: Se tarifa por GB almacenado/mes. Cuanto más almacenemos, más pagamos.
- Transferencia de Datos: Se tarifa por los GB transferidos. Cuanto más transfiramos, más pagamos.
- Peticiones de acceso: Se tarifa por las peticiones sobre los ficheros (GET, PUT, LIST, etc.).
A pesar de todo, los servicios de Amazon son competitivos, ya que unos 2’5 GB de datos almacenados y con una
transferencia de 15 GB al mes, no llegarán a los 4 dólares (2’69 €) mensuales.
173
5.11 Máquinas virtuales.
Las Máquinas virtuales posibilitan de ejecutar en un computador (host) un programa que crea un computador
virtual (guest) sobre el que ejecutar cualquier entorno.
Hypervisor: Software que permite que múltiples sistemas operativos puedan ejecutarse en un mismo
computador concurrentemente. Tenemos dos tipos de hipervisores: los que ejecutan directamente sobre el HW
(XenServer, XenClient, KVM, etc.) y los que ejecutan sobre un sistema operativo (Virtual Server, VMware, Virtual
BOX, Virtual PC, etc.).
Los Web Sites son la opción más adecuada para la mayoría de aplicaciones web. La implementación y la
administración están integradas en la plataforma, los sitios pueden escalarse rápidamente para asumir altas
cargas de tráfico y el equilibrio de carga y el administrador de tráfico incluido ofrecen una gran disponibilidad.
Se pueden mover los sitios actuales a Web Sites fácilmente con una herramienta de migración en línea, utilizar
una aplicación de código abierto de la galería de aplicaciones web o crear un sitio nuevo utilizando el marco y
las herramientas que se prefiera.
Pero si se necesita más control sobre el entorno del servidor web, como la posibilidad de tener acceso remoto
al servidor o configurar las tareas de inicio del servidor, los servicios en la nube son por lo general la mejor opción.
Si actualmente se tiene una aplicación que requeriría cambios sustanciales para ejecutarse en Web Site, se podría
elegir el uso de Máquinas virtuales a fin de simplificar la migración a la nube. Sin embargo, se requiere más
tiempo para configurar, proteger y mantener adecuadamente las Máquinas virtuales, además de mayores
conocimientos informáticos, en comparación con un Web Site y otros Cloud Services. Cuando se está
considerando la opción de Máquinas virtuales, hay que tener en cuenta el esfuerzo constante de mantenimiento
requerido para aplicar revisiones al entorno de la Máquina virtual, así como para actualizarlo y administrarlo.
El diagrama siguiente ilustra el control relativo frente a la facilidad de uso para cada una de estas opciones:
174
Miguel Ángel Cifredo
macifredo@gmail.com
Prácticas de Laboratorio.
P1. (Problema extraído de Arquitectura de Computadores, Un Enfoque Cuantitativo, Morgan Kaufmann 2012.
Hennesy Patterson, 5ª edición) Un desafío para los arquitectos es que el diseño creado hoy requerirá varios años
de implementación, verificación y pruebas antes de aparecer en el mercado. Lo que significa que debe
proyectarse hacia lo que la tecnología llegará a ser con varios años de antelación. A veces, esto es difícil de hacer.
a) De acuerdo con la tendencia de escalamiento observado por la ley de Moore (aprox. 35% por año),
¿Cuántas veces mayor debería ser el número de transistores en un chip en 2015 respecto a 2005?
b) Esta tendencia refleja el aumento de las velocidades de reloj. Si las velocidades de reloj continúan
subiendo al mismo ritmo que en la década de 1990, ¿qué valor tendría aproximadamente las velocidades
de reloj en el año 2015, suponiendo que era de 3’2 GHz en 2003?
c) La tasa actual de crecimiento (1% por año) ¿Cuáles son las velocidades de reloj proyectadas ahora para
el 2015?
d) ¿Qué ha limitado la tasa de crecimiento de la velocidad de reloj? ¿Qué hacen los arquitectos de hardware
con los transistores adicionales para aumentar el rendimiento?
Solución:
Apartado a)
Apartado b)
Apartado c)
Apartado d)
Al disminuir el voltaje se disminuye el umbral que definen los estados (0 y 1) llegando a su límite
físico. Se opta por paralelizar los procesos aumentando el número de procesadores.
175
P2. (Problema extraído de Arquitectura de Computadores, Un Enfoque Cuantitativo, Morgan Kaufmann 2012.
Hennesy Patterson, 5ª edición) suponga que está diseñando un sistema para una aplicación en tiempo real que
se deben ejecutarse en unos tiempos específicos. Acabado el cálculo más rápido observa que no se gana nada.
Posteriormente encuentra que su sistema puede ejecutar el código requerido, en el peor de los casos, dos veces
más rápido que el inicial.
a) ¿Cuánta energía se ahorra si se ejecuta a la velocidad actual (dos veces más rápido) y se apaga el sistema
cuando el proceso de cálculo se ha completado?
Solución:
Apartado a)
Potencia Potencia
Energía
Energía consumida consumida
Tiempo Tiempo
Caso Inicial: E0 = P ∙ t Caso Mejorado: Em = P ∙ t/2
Dos veces más rápido es reducir el tiempo a la mitad.
» Por tanto, si el proceso se ejecuta en la mitad de tiempo se ahorra la mitad de la potencia inicial.
Apartado b) Potencia
Energía consumida
Tiempo
Caso Mejorado (sólo la frecuencia): Ef = k ∙ V2 ∙ f/2 ∙ t∙2
Reducir la frecuencia a la mitad supone emplear el doble de tiempo en ejecución.
Luego reducir la frecuencia implica seguir consumiendo la misma cantidad de energía.
Potencia
Tiempo
Caso Mejorado (tensión y frecuencia): Ev f = k ∙ (V/2)2 ∙ f/2 ∙ t∙2 = k ∙ V/4 ∙ f ∙ t
Reducir la frecuencia a la mitad supone emplear el doble de tiempo en ejecución.
Luego reducir además la tensión implica reducir la energía consumida a la cuarta parte.
P3. (Problema extraído de Arquitectura de Computadores, Un Enfoque Cuantitativo, Morgan Kaufmann 2012.
Hennesy Patterson, 5ª edición). Usando el juego de instrucciones de la Figura A.27, calcular el CPI efectivo para
instrucciones enteras para el procesador MIPS. Supongamos que el 60% de las bifurcaciones condicionales se
toman. También asumimos que hemos tomado las siguientes medidas de CPI promedio para ese tipo de
instrucciones:
Figura A.27. Conjunto de instrucciones dinámicas del MIPS para cinco programas SPECint2000. Tenga en cuenta
que las instrucciones de movimiento de registro a registro entero están incluidas en la instrucción OR. Las
entradas en blanco tienen el valor de 0.0%.
177
Solución:
𝑎𝑑𝑑 19%
𝑠𝑢𝑏 3%
𝑚𝑢𝑙 0%
𝑐𝑜𝑚𝑝𝑎𝑟𝑒 5%
𝑙𝑜𝑎𝑑 𝑖𝑛𝑚 2%
𝐶𝑃𝐼𝑒𝑓𝑒𝑐𝑡𝑖𝑣𝑜 = ∙ (1.0 𝐶𝑃𝐼𝐴𝑙𝑙 𝐴𝐿𝑈 𝑖𝑛𝑠𝑡𝑢𝑐𝑡𝑖𝑜𝑛𝑠 )
𝑠ℎ𝑖𝑓𝑡 2%
𝐴𝑁𝐷 4%
𝑂𝑅 9%
𝑋𝑂𝑅 3%
(𝑜𝑡ℎ𝑒𝑟 𝑙𝑜𝑔𝑖𝑐𝑎𝑙
⏟ 0%)
∑ 48%
𝑙𝑜𝑎𝑑 26%
+( ) ∙ (1.4 𝐶𝑃𝐼𝐿𝑜𝑎𝑑𝑠−𝑠𝑡𝑜𝑟𝑒𝑠 )
⏟𝑠𝑡𝑜𝑟𝑒 10%
∑ 36%
𝑗𝑢𝑚𝑝 1%
+ (𝑐𝑎𝑙𝑙 1%) ∙ (1.2 𝐶𝑃𝐼𝐽𝑢𝑚𝑝𝑠 )
⏟𝑟𝑒𝑡𝑢𝑟𝑛 1%
∑ 3%
𝑐𝑜𝑛𝑑 𝑏𝑟𝑎𝑛𝑐ℎ 12%
+( ) ∙ [60% (2.0 𝐶𝑃𝐼𝐵𝑟𝑎𝑛𝑐ℎ ) + 40% (1.5 𝐶𝑃𝐼 𝐵𝑟𝑎𝑛𝑐ℎ )]
⏟𝑐𝑜𝑛𝑑 𝑚𝑜𝑣𝑒 1% 𝑇𝑎𝑘𝑒𝑛 𝑁𝑜𝑡 𝑇𝑎𝑘𝑒𝑛
∑ 13%
𝐶𝑃𝐼𝑒𝑓𝑒𝑐𝑡𝑖𝑣𝑜 ≅ 99%
Nota: No se llega al 100% de la estadística por los redondeos, por ejemplo, no es posible que el cálculo de
multiplicación (mul) sea exactamente 0%.
178
Miguel Ángel Cifredo
macifredo@gmail.com
P4. (Problema extraído de Arquitectura de Computadores, Un Enfoque Cuantitativo, Morgan Kaufmann 2012.
Hennesy Patterson, 5ª edición). Usando la combinación de instrucciones de la Figura A.28, calcular el CPI efectivo
para instrucciones de coma flotante para el procesador MIPS. Asumir las mismas medidas de CPI promedio para
este tipo de instrucciones del ejercicio anterior P3.
Figura A.28. Conjunto de instrucciones dinámicas del MIPS para cinco programas SPECfp2000. Tenga en cuenta
que las instrucciones de movimiento de registro a registro entero están incluidas en la instrucción OR. Las
entradas en blanco tienen el valor de 0.0%.
Solución:
Similar al ejercicio anterior pero teniendo en cuenta las operaciones de coma flotante en lugar de las enteras.
179
P5. Hallar la aceleración entre una máquina M1 y otra M2 si:
Solución:
M1 M2
Nº Instrucciones 2∙N N
CPI CPI 4 ∙ CPI
Frecuencia f 2∙f
Para calcular la aceleración entre dos máquinas se establece mediante el cociente entre los tiempos de
la máquina que menor tiempo requiere para el procesado entre la que requiere más tiempo. Por tanto,
𝑁º𝐼𝑛𝑠𝑡 ∙ 𝐶𝑃𝐼 (2 ∙ 𝑁) ∙ (𝐶𝑃𝐼)
( ) ⁄(𝑓)
𝑡𝑀1 𝐹𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎 𝑀1
𝐴𝑐𝑒𝑙𝑒𝑟𝑎𝑐𝑖ó𝑛 = = = = 1
𝑡𝑀2 𝑁º𝐼𝑛𝑠𝑡 ∙ 𝐶𝑃𝐼 (𝑁) ∙ (4 ∙ 𝐶𝑃𝐼)
( ) ⁄(2 ∙ 𝑓)
𝐹𝑟𝑒𝑐𝑢𝑒𝑛𝑐𝑖𝑎 𝑀2
» Por tanto, las máquinas M1 y M2 son iguales desde el punto de vista del rendimiento.
180
Miguel Ángel Cifredo
macifredo@gmail.com
P6. Suponga que un compilador consigue paralelizar, para N procesadores, la mitad de un determinado código.
Usando la Ley de Amdahl, calcule la aceleración total conseguida. Hallar el límite cuando N tiende a infinito.
Solución:
Por otro lado, sabemos que la mitad del código sigue igual y que la otra mitad se ha conseguido
paralelizar, luego
1
𝐴𝑐𝑒𝑙𝑒𝑟𝑎𝑐𝑖ó𝑛𝑡𝑜𝑡𝑎𝑙 =
0′50
(1 − 0′50) +
𝑁
Determinamos el valor del límite cuando este tiende a infinito:
1 1 1
lim ( ) = lim ( ) = = 2
𝑁→∞ 0′50 𝑁→∞ 0′ 50𝑁 + 0′50 0′50
(1 − 0′50) +
𝑁 𝑁
P7. Suponga que se desea conseguir una aceleración de 80 en un sistema con 100 procesadores. Usando la Ley
de Amdahl, calcule la fracción del código que debe ser paralela.
Solución:
181
P8. Dado el siguiente código, y suponiendo que en la parte optimizada (desenrollada) se consigue una
aceleración de 10, calcule la aceleración total conseguida en cada caso.
…
// Asuma que los vectores están inicializados
…
Solución:
int Daxpy_no_opt(void) {
int i;
for (i = 0; i < VECTOR_SIZE; i++) Caso base sin optimizar:
x[i] = a * x[i] + y[i];
for (i = VECTOR_SIZE; i < VECTOR_SIZE * 10; i++)
x[i] = a * x[i] + y[i]; 𝐴𝑐𝑒𝑙𝑒𝑟𝑎𝑐𝑖ó𝑛𝑡𝑜𝑡𝑎𝑙 = 1
return i;
}
int Daxpy_opt_10(void) {
int i;
for (i = 0; i < VECTOR_SIZE; i += 4) {
x[i] = a * x[i] + y[i]; Caso: Se optimiza 10 veces el 10% del código:
x[i + 1] = a * x[i + 1] + y[i + 1];
x[i + 2] = a * x[i + 2] + y[i + 2];
1
x[i + 3] = a * x[i + 3] + y[i + 3]; 𝐴𝑐𝑒𝑙𝑒𝑟𝑎𝑐𝑖ó𝑛𝑡𝑜𝑡𝑎𝑙 = = 1′099
} 0′10
+ 0′90
for (i = VECTOR_SIZE; i < VECTOR_SIZE * 10; i++) 10
x[i] = a * x[i] + y[i];
return i;
}
int Daxpy_opt_90(void) {
int i;
for (i = 0; i < VECTOR_SIZE; i++)
x[i] = a * x[i] + y[i]; Caso: Se optimiza 10 veces el 90% del código:
for (i = VECTOR_SIZE; i < VECTOR_SIZE * 10; i += 4) {
x[i] = a * x[i] + y[i]; 1
x[i + 1] = a * x[i + 1] + y[i + 1]; 𝐴𝑐𝑒𝑙𝑒𝑟𝑎𝑐𝑖ó𝑛𝑡𝑜𝑡𝑎𝑙 = = 5′263
0′90
x[i + 2] = a * x[i + 2] + y[i + 2]; 0′ 10 + 10
x[i + 3] = a * x[i + 3] + y[i + 3];
}
return i;
}
Conclusión:
» Se ha obtenido una mejor aceleración del proceso al optimizar la parte más repetitiva.
182
Miguel Ángel Cifredo
macifredo@gmail.com
P9. (Problema extraído de Arquitectura de Computadores, Un Enfoque Cuantitativo, Morgan Kaufmann 2012.
Hennesy Patterson, 5ª edición) En este ejercicio, suponemos que estamos considerando la mejora de una
máquina mediante la adición de hardware vectorial. Cuando se ejecuta un cálculo en modo vectorial en el
hardware, es 10 veces más rápido que en el modo normal de ejecución. Lo llamamos el porcentaje de tiempo
que podría ser empleado en el modo vector el porcentaje de vectorización. Los vectores se discuten más
adelante, pero no es necesario saber nada acerca de cómo trabajan para responder a esta pregunta.
a) Dibuje un gráfico que represente la aceleración como un porcentaje del cálculo realizado en el modo
vectorial. Rotule el eje y "Aceleración Net" y la etiqueta del eje x "Vectorización porcentual".
c) ¿Qué porcentaje del tiempo de ejecución de cálculo se gasta en modo vectorial si se logra una
aceleración de 2?
d) ¿Qué porcentaje de vectorización se necesita para alcanzar la mitad de la aceleración máxima posible
utilizando el modo de vector?
e) Supongamos que usted ha medido el porcentaje de vectorización del programa para ser del 70%. El
grupo de diseño de hardware estima que puede acelerar el hardware vectorial aún más con una inversión
adicional significativa. Uno se pregunta si el personal para el compilador podría aumentar el porcentaje
de vectorización en su lugar. ¿Qué porcentaje de vectorización necesitaría el equipo para el compilador
para lograr igualar una adición 2 × aceleración en el vector unitario (más allá del inicial de 10×)?
183
184
Miguel Ángel Cifredo
macifredo@gmail.com
Test 0.
SÍ.
NO.
No crecen prácticamente.
El ancho de banda.
La latencia de acceso.
Ambos igual aproximadamente.
La inversa de la latencia de acceso.
¿Qué significa que el CPI es menor que 1 para una CPU? (varias respuestas pueden ser correctas)
185
186
Miguel Ángel Cifredo
macifredo@gmail.com
1. Objetivos y preparación.
En esta práctica vamos a demostrar cómo se puede mejorar el rendimiento de un programa, bien ayudando al
compilador, o bien escribiendo el código de alto nivel de la forma más adecuada para que el computador
aproveche mejor el paralelismo.
Para ello, se usará una forma sencilla de medir tiempos de ejecución y se calcularán aceleraciones para varias
versiones de un mismo programa, usando el IDE VS2010 (Visual Studio 2010 de Microsoft).
En concreto, se va a estudiar la optimización de filtros de tratamiento de imágenes, atacando a la parte del código
donde está la mayor parte del tiempo de ejecución (bucle central de bucles anidados). El filtro que se probará
realiza un difuminado de la imagen, que sirve para eliminar “ruido” (es decir, suprimir o reducir los píxeles con
valores muy diferentes de sus píxeles vecinos) y para otras aplicaciones.
Se compararán los tiempos de ejecución del filtro de la librería OpenCV con 2 versiones escritas en lenguaje C++.
- Repasar los conceptos básicos del tema 1. Los que se van a pedir expresamente en esta práctica son:
aceleración, fórmula del tiempo de ejecución y CPI.
- Entender cómo están organizados los esquemas de código que se proporcionan con este enunciado. Ver
Apéndice: Esqueleto del código de prueba.
Apéndice: Función simple para filtro de imágenes.
- Notar que img → height es la altura en píxeles de la imagen y que img → width es la anchura en píxeles
de la imagen.
- Dibujar en un esquema los píxeles de una imagen RGB (un Byte por color; 3 Bytes por píxel: Red, Green,
Blue) y del algoritmo que hace la función asd_blurring_simplest(). Para ello se debe saber que los datos
de una imagen RGB se organizan como un vector, cuyos elementos contienen 3 Bytes, de esta forma
(siendo un Byte cada celda de este diagrama, y Nf: número de filas, Nc = número de columnas):
187
2. Realización de la práctica.
El profesor explicará:
- Cómo medir el tiempo usando la clase QPTimer que se da (Ver Apéndice: La clase QPTimer).
- Estos comentarios del filtro:
// Remark: this loop has been chosen to avoid boundary conditions be checked.
// in fact two rows and two columns are not processed (this is a little difference: less than 0.3%)
- Cómo cambiar las opciones de optimización de la compilación.
En el aula se dará un proyecto de VS2010 con todas las configuraciones ya preparadas (entre ellas, una serie de
rutas configuradas).
NOTA: Si se quiere trabajar en otro PC (por ejemplo en casa) o se quiere empezar un proyecto nuevo de VS2010,
hay que realizar una serie de pasos de configuración (ver Configuración básica de Windows y Visual
Studio 2010 para OpenCV al final del documento).
Descargar el fichero comprimido de ev.us.es. Descomprimirlo sobre una carpeta de C: (Nunca trabajar sobre el
comprimido).
A continuación abrir la “solución” ASD_P1.sln” haciendo doble clic (una solución es un conjunto de proyectos de
VS2010). Si VS2010 abriera una ventana para la elección de configuración del lenguaje, entonces seleccionar
“configuración de desarrollo de Visual C++” y luego “Iniciar Visual Studio” (tardará más de un minuto en
configurar herramientas de C++), según se ve en la siguiente figura:
Vamos a comparar la duración de dos rutinas que hacen lo mismo con el vídeo, pero escritas de forma diferente:
188
Miguel Ángel Cifredo
macifredo@gmail.com
Empecemos comparando la rutina de la librería OpenCV cvSmooth(), con la “normal” o simple que cualquier
programador de alto nivel escribiría, es decir, asd_blurring_simplest().
Ver el código C++ para asegurarse de que los dos cronómetros (crono_my_own_blurring y crono_opencv_lib)
están midiendo estas rutinas.
Ejecutar y anotar los tiempos de ambas para estas dos opciones de optimización de la compilación: /Od, /O2.
Recordar que para cambiar estas opciones de compilación, lo más fácil es hacer clic con el botón derecho del
ratón sobre el nombre del Proyecto (en el explorador de las soluciones), y en el árbol de navegación de la
siguiente figura, elegir primero: /Od, es decir, toda optimización está deshabilitada. Generar la solución y ejecutar
sin iniciar la depuración (Ctrl – F5)
Después, para la otra medición, elegir /O2, para una mayor velocidad de ejecución. Volver a generar la solución
y ejecutar sin iniciar la depuración (Ctrl – F5)
A continuación anotar tanto la aceleración como los frames por segundo (fps) que se podrían procesar entre
ambas rutinas para ambas opciones.
NOTA: La ejecución será más rápida, si no se define esta constante (se ha preparado el código con compilación
condicional, para que así no se muestren los vídeos). Para ello, comentar la línea: #define __VIDEO_ON
¿Por qué sale igual tiempo de cvSmooth() tanto para /Od como para /O2?
¿Cuál de los tiempos es más correcto para estas mediciones: el mínimo o la media (Minimum time o Mean time)?
¿Por qué?
189
2.2. Cálculo del CPI.
Hallar el número de instrucciones de la rutina, se arranca el depurador y se cuentan las instrucciones del bucle
central del ensamblador.
NOTA: Se puede copiar el texto de la ventana desensamblado y pegarlo en un editor para contar las
líneas, donde cada línea es una instrucción.
Contestar a:
- ¿Qué error se comete aproximadamente al medir sólo las instrucciones del bucle central del
ensamblador, cuando se usa /O2?
- Atendiendo a los tres parámetros Tiempo, Nº Instrucciones, CPI, ¿de dónde viene la principal diferencia
del tiempo de ejecución entre la ejecución con la opción /Od y con la opción /O2?
190
Miguel Ángel Cifredo
macifredo@gmail.com
191
Apéndice: Función simple para filtro de imágenes.
///////////////////////////////////////////////////////////////////
// My own blurring function (the "simplest")
void asd_blurring_simplest(IplImage* img, int kernel_radius) {
int number_of_neighbours = (1 + 2 * kernel_radius) * (1 + 2 * kernel_radius);
// Remark: this loop has been chosen to avoid boundary conditions be checked.
// in fact 2 rows and 2 columns are not processed (a little difference: less than 0.3%)
for (int y = 1; y < (img->height) - 1; y++) {
// THIS POINTER ptr (to unsigned char) will point to the first byte of a row in a RGB image
// Do not worry about the fields of this structure; they are given by OpenCV
uchar* ptr = (uchar*) (img->imageData + y * img->widthStep);
int image_width = img->width;
// Remark: this loop has been chosen to avoid boundary conditions be checked.
// in fact 2 rows and 2 columns are not processed (a little difference: less than 0.3%)
for (int x = 1; x < (image_width - 1); x++) {
for (int color = 0; color < 3; color++) {
int sum = 0;
for (int yshift = -kernel_radius; yshift <= kernel_radius; yshift++) {
for (int xshift = -kernel_radius; xshift <= kernel_radius; xshift++) {
sum += ptr[3 * (x + xshift) + 3 * image_width * yshift + color];
}
}
ptr[3 * x + color] = sum / number_of_neighbours;
}
}
}
}
192
Miguel Ángel Cifredo
macifredo@gmail.com
TABLA DE RESULTADOS
ALUMNO:
Objetivos y Preparación.
Dibujar en un esquema de los píxeles de una imagen RGB (3 Bytes por píxel: Red, Green, Blue) y del algoritmo
que hace la función simplest ().
193
2.1 Código más “simple”
CvSmooth asd_blurring_simplest
Tiempo con la opción /Od 0’00510262 0’07736
Tiempo con la opción /O2 0’00511623 0’0340358
0'07736
Aceleración entre ambos con /Od 1.0 = 15'1608
0'00510262
1 1
Frames por segundo (fps) = 195'977 = 12'926
0'00510262 0'07736
Contestar ¿Por qué sale igual tiempo de cvSmooth() tanto para /Od como para /O2?
Porque la librería cvSmooth() ya está compilada y únicamente se linka de forma directa al programa,
por tanto no se le aplica ninguna aceleración.
¿Cuál de los tiempos es más correcto para estas mediciones: el mínimo o la media (Minimum time o Mean
time)? ¿Por qué?
La media sería que el sistema operativo tiene cambio de contexto, mientras que sería mejor tomar el
valor mínimo, ya que es un valor realmente alcanzable por la máquina.
Atendiendo a los tres parámetros Tiempo, N Instrucciones, CPI, ¿de dónde viene la principal diferencia del
tiempo de ejecución entre la ejecución con la opción /Od y con la /O2?
194
Miguel Ángel Cifredo
macifredo@gmail.com
1. Objetivos y preparación.
En esta práctica vamos a continuar trabajando en la mejora del rendimiento de un programa, no sólo ayudando
al compilador, sino también escribiendo el código de alto nivel de la forma más adecuada para que el computador
aproveche mejor el paralelismo. En este caso vamos a trabajar con el desenrollado visto en teoría.
Para ello se va a continuar con el mismo proyecto IDE VS2010 de la práctica 1 de la asignatura.
Se compararán los tiempos de ejecución del filtro de la librería OpenCV con una versión desenrollada del código
simplest() escrita en lenguaje C++ (unrolled) que se ofrece y otra que el alumno realizará. Además se
compararán los tiempos y códigos en ensamblador que se obtienen con varias opciones de compilación.
2. Realización de la práctica.
En el aula se tomará el proyecto de VS2010 de la Práctica 2 con todas las configuraciones ya preparadas, y
disponible en WebCT (entre ellas, una serie de rutas configuradas).
NOTA: Si se quiere trabajar en otro PC (por ejemplo en casa) o se quiere empezar un proyecto nuevo de VS2010,
hay que realizar una serie de pasos de configuración (ver Configuración básica de Windows y Visual
Studio 2010 para OpenCV al final del documento).´
Vamos a comparar la duración de dos rutinas que hacen lo mismo con el vídeo, pero escritas de forma diferente:
195
Vamos a trabajar con asd_blurring_unrolled() para crear un nuevo código en el que además, se desenrolle el
color. Tener en cuenta que los colores de las imágenes son independientes entre ellos para este filtro, por lo que
atienden a un desenrollado sistemático.
Una vez creado el nuevo código, medir las prestaciones para ambas opciones de compilación:
///////////////////////////////////////////////////////////////////
// My own blurring function (the "simplest")
void asd_blurring_unrolled ( IplImage* img ) {
// Remark: this loop has been chosen to avoid boundary conditions be checked.
// in fact two rows and two columns are not processed (this is a little difference:
// less than 0.3%)
for( int y = 1; y < (img->height)-1; y++ ) {
uchar* ptr = (uchar*) (img->imageData + y * img->widthStep);
int image_width = img->width;
// Remark: this loop has been chosen to avoid boundary conditions be checked.
// in fact two rows and two columns are not processed (this is a little difference:
// less than 0.3%)
for( int x = 1; x < (image_width -1); x++ ) {
for( int color = 0; color < 3; color++ ) {
int sum = 0;
sum += ptr[3 * x + color];
sum += ptr[3 * (x-1) + color];
sum += ptr[3 * (x+1) + color];
196
Miguel Ángel Cifredo
macifredo@gmail.com
TABLA DE RESULTADOS
ALUMNO:
Objetivos y Preparación.
int sum0 = 0;
int sum1 = 0;
int sum2 = 0;
ptr[3 * x + 0] = sum0 / 9;
ptr[3 * x + 1] = sum1 / 9;
ptr[3 * x + 2] = sum2 / 9;
}
}
}
197
2.1 Código desenrollado
CvSmooth asd_blurring_simplest
Tiempo con la opción /Od 0’0052230 0’0392525
Tiempo con la opción /O2 0’0052246 0’0051549
Aceleración entre CvSmooth 0'0392525
1.0 = 7'5153
y unrolled con opción /Od 0'0052230
Justifique las diferencias que observa en los resultados entre la versión unrolled que se da y la versión que
usted ha desenrollado más iteraciones para la opción de compilación /O2:
198
Miguel Ángel Cifredo
macifredo@gmail.com
1. Objetivos.
En esta práctica los alumnos estudiarán los límites de ILP que se pueden conseguir con técnicas dinámicas en
procesadores de propósito general (GPP). Para ello medirán el tiempo de ejecución de un código en C++ en una
máquina GPP real. Se estudiarán diferentes variaciones, en alguna de las cuales prácticamente se alcanza el límite
del flujo de datos (data-flow limit), de manera que el tiempo medio por iteración de un bucle es debido a tal
límite. Los alumnos deberán analizar cada código para razonar cuál es su ruta crítica y entender el motivo de las
diferencias de rendimiento entre las diferentes variaciones.
2. Preparación.
Se da un código en C que resuelve un producto múltiple con una variable acumuladora. Es un bucle simple
para ver el máximo IPC que se puede conseguir.
Los cálculos se repetirán con vectores de distintos tamaños, para estudiar el impacto del acceso a memoria
en el rendimiento, según el tamaño de los mismos.
Al ejecutar en un GPP los programas reales (que se pueden descargar de ev.us.es), evidentemente se van a
obtener diferentes resultados de ciclos según la máquina donde se pruebe.
Nota: los programas están preparados para Visual Studio; si se usara el código fuente con otro compilador,
no olvidar activar opciones similares a: “Optimización completa (/Ox), Favorecer código rápido (/Ot), Modelo
de Punto Flotante: Fast”.
El código se ha escrito lo más simple posible para que pueda probarse en cualquier compilador.
Concretamente, para facilitar la ejecución, se proporciona un proyecto (o “solución”) creado con Visual
Studio 2010.
La instrucción de medida de ciclos rdtsc funciona en todos los procesadores Intel, pero no en ciertos AMD.
Si se quisiera medir tiempos en segundos, habría que modificar el valor de la constante siguiente en el fichero
de encabezado (cabecera, header): QueryPerformanceTiming_rdtsc.h:
#define CPU_FREQ_HZ (3.20e+9)
//write here your CPU frequency
Hay que ejecutar sin depuración (tecla Ctrl F5) para que la ventana de comando no se cierre, o bien ejecutar
en una consola. Debe usarse el modo Release (que está preparado con todas las optimizaciones). Ver figura:
199
Asegurarse de elegir el modelo Fast de coma flotante y las instrucciones del núcleo SIMD (se estudiarán en teoría
más adelante), como se muestra en la siguiente figura:
Para hacer esta prueba, no use combinaciones de energía del estilo de “Administración de energía mínima”,
pues puede ocasionar problemas en la medición de tiempos (la frecuencia de la CPU es variable en este
modo). En Windows, haga clic en Inicio y en Ejecutar, escriba powercfg.cpl y haga clic en Aceptar. Seleccione
una combinación de energía distinta de Administración de energía mínima en el cuadro Combinaciones de
energía.
200
Miguel Ángel Cifredo
macifredo@gmail.com
3. Realización de la práctica.
Ejecute todo el proyecto VS2010 (ver ev.us.es) en modo Release. El proyecto que se da no usa OpenCV y por
tanto, debería funcionar en cualquier PC Intel (no funciona correctamente en procesadores AMD).
Evidentemente la duración del código real dependerá de la máquina concreta y del estado de carga de la
memoria y CPU. Se aconseja no tener abiertas muchas aplicaciones y ejecutar varias veces para quedarse con el
mínimo tiempo.
- Máquina (sobre todo el procesador) donde se ejecuta el código real. Se puede consultar en Panel de
control → Sistema. También puede usar el programa CPU-Z (http://www.cpuid.com/softwares/cpu-z.html),
el cual indica muchas de las características del procesador.
Resultados:
- Ejecutar el proyecto en modo release y anotar los CPI, y los ciclos por iteración de test1().
- Tratar de razonar cuál es la operación u operaciones que están en la ruta crítica, o la circunstancia que
hace que el CPI sea mayor que 1/4, o que la duración de una iteración sea superior a:
Numero de instrucciones por iteración de ensamblador
grado superescalaridad (4 en la mayoría de las CPU superescalares de alto rendimiento)
- Dibujar un cronograma temporal simplificado de cómo se están ejecutando las operaciones. Considerar
que hay 2 UF de MULT (que permiten segmentación).
- Analizar qué ocurre cuando crece el número de iteraciones. Tratar de razonar el motivo por el que la
pérdida de rendimiento no se produce de igual manera en las distintas funciones.
A continuación, escribir el producto múltiple con 2, 3, 4, variables acumuladoras rellenando lo que falta en
test2() test3() test4(). Repetir los pasos anteriores.
Para poder ver el código ASM o depurar más cómodamente compruebe que esta opción está activa: Propiedades
del proyecto → Propiedades de configuración → C/C++ → Optimización → Expansión de las funciones inline →
Solo __inline (/Ob1)" (existen opciones similares en otros compiladores). De esa forma, las funciones test1(),
test2() etc. no se "empotran " en el código del main(), sino que tienen su cuerpo independiente y se llaman
(CALL ) desde el main(). Si tal opción no está activa, el compilador podría expandir todo el código de una función
dentro del ASM del main() y luego reordenar como le parezca, con lo cual el ASM no es fácil de seguir ni entender
Cada función tiene una sentencia return (p ej : return a[N_ITER-1 ];). Dejar siempre esta sentencia y las
impresiones de las variables devueltas. Si no se usa la variable de salida, el compilador podría eliminar
totalmente la llamada a la función.
201
APÉNDICE: Código fuente para las pruebas
//----------------------------------------------------
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Dpto ATC. www.atc.us.es ASD subject 3º GTI
// Versión 1. Nov. 2012. Fernando Díaz del Rio
// Versión 2. Mar. 2014. Jesús Rodríguez Leal
//
// - Doing a test to reach ILP limits in superscalar processor
// - TEST TIMING OF simple routine
//-----------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <iostream>
using namespace std;
//------------------------------------------------------------------
// project def and includes
// this test uses a new version of QueryPerformanceTiming to measure CPU cycles using rdtsc asm instr.
#include "QueryPerformanceTiming_rdtsc.h"
#define N_REPETIC 64
// repeat several times each test to extract the minimum time and to have the caches filled
#define N_ITER (4*1024*1024)
float a[N_ITER];
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// vectors init
void vectors_init() {
int i;
for (i = 0; i < N_ITER; i++) {
a[i] = 1.0f; // a[i] must have near to 1 values to avoid overflow/underflow in tests
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// tests
float test1(int niter) {
int i;
float acc = 1.0;
for (i = 0; i < niter; i++) {
acc = acc * a[i];
}
return acc;
}
float test2(int niter) {
int i;
float acc1 = 1.0;
float acc2 = 1.0; // initialization of accumulator variables
for (i = 0; i < niter % 2; i++) // this is a prolog code to check if niter is not multiple of 2
{
acc1 = acc1 * a[i];
}
// Write here the same algorithm using 2 accumulators
}
float test3(int niter) {
int i;
float acc1 = 1.0;
float acc2 = 1.0;
float acc3 = 1.0;
for (i = 0; i < niter % 3; i++) // this is a prolog code to check if niter is not multiple of 3
{
acc1 = acc1 * a[i];
}
// Write here the same algorithm using 3 accumulators
}
float test4(int niter) {
int i;
float acc1 = 1.0;
float acc2 = 1.0;
float acc3 = 1.0;
float acc4 = 1.0;
for (i = 0; i < niter % 4; i++) // this is a prolog code to check if niter is not multiple of 4
{
acc1 = acc1 * a[i];
}
// Write here the same algorithm using 4 accumulators
}
202
Miguel Ángel Cifredo
macifredo@gmail.com
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
float var1, var2, var3, var4;
int i, div, niter;
vectors_init();
for (div = N_ITER / 256; div >= 1; div /= 2) {
niter = N_ITER / div;
QPTimer c1, c2, c3, c4;
c1.Calibrate(); //calibrates timer overhead and set cronometer to zero
c2.Calibrate(); //calibrates timer overhead and set cronometer to zero
c3.Calibrate(); //calibrates timer overhead and set cronometer to zero
c4.Calibrate(); //calibrates timer overhead and set cronometer to zero
// measuring tests
for (i = 0; i < N_REPETIC; i++) {
c1.Start(); // start timer
var1 = test1(niter); // Do the test
c1.Stop(); // stop timer
c1.Reset();
c2.Start(); // start timer
var2 = test2(niter); // Do the test
c2.Stop(); // stop timer
c2.Reset();
c3.Start(); // start timer
var3 = test2(niter); // Do the test
c3.Stop(); // stop timer
c3.Reset();
c4.Start(); // start timer
var4 = test4(niter); // Do the test
c4.Stop(); // stop timer
c4.Reset();
}
// end of testing
cout << endl
<< "ONLY PRINTING OUTPUT VARIABLE TO PREVENT THAT THE COMPILER ELIMINATES FUNCTION CALLS: "
<< var1 << ", " << var2 << ", " << var3 << ", " << var4 << endl;
cout << endl << "-Number of iterations N_ITER: " << niter << endl;
cout << endl << "-Number of measures: " << c1.NumberOfMeasures()
<< endl;
c1.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test1() is:", niter);
c2.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test2() is:", niter);
c3.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test3() is:", niter);
c4.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test4() is:", niter);
cout << endl;
}
}
203
TABLA DE RESULTADOS
ALUMNO:
Datos previos:
Código C resuelto:
//----------------------------------------------------
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Dpto ATC. www.atc.us.es
// ASD subject 3º GTI
// Versión 1. Nov. 2012. Fernando Díaz del Rio
// Versión 2. Mar. 2014. Jesús Rodríguez Leal
//
//
// - Doing several test to reach ILP limits in superscalar processor
// - TEST TIMING OF simple routine
//-----------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <iostream>
#define N_REPETIC 64
// repeat several times each test to extract the minimum time and to have the caches filled
float a[N_ITER];
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// vectors init
void vectors_init() {
int i;
for (i = 0; i < N_ITER; i++) {
// a[i] must have near to 1 values to avoid overflow/underflow in tests
a[i] = 1.0f;
}
}
204
Miguel Ángel Cifredo
macifredo@gmail.com
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// tests
205
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
vectors_init();
// measuring tests
for (i = 0; i < N_REPETIC; i++) {
c1.Start(); // start timer
var1 = test1(niter); // Do the test
c1.Stop(); // stop timer
c1.Reset();
c1.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test1() is: ", niter);
c2.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test2() is: ", niter);
c3.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test3() is: ", niter);
c4.PrintMinimumCyclesByIteration(
" Minimum time in cycles for an iteration of test4() is: ", niter);
206
Miguel Ángel Cifredo
macifredo@gmail.com
test1() test2()
test3() test4()
207
RESULTADOS:
Tiempo mínimo en ciclos por Dibujar un esquema del flujo de datos. Latencia de la UF en la ruta
iteración (niter) y CPI. Señalar Operación(es) en la ruta crítica (en ciclos).
Anotar para diversos tamaños de crítica. Indicar la circunstancia que
vectores. conduce a una duración
ciclos mayor.
iteración del bucle Dibujar un cronograma
simplificado.
Ver anexo compilación.
Explicar el comportamiento del código cuando el tamaño del vector es elevado (anotar a partir de que N_ITER
hay cambios en los tiempos):
Desde la iteración 256 hasta 131.072 los tiempos disminuyen progresivamente desde 4’18359
hasta 4’0002. A partir de la iteración 262.144 los tiempos vuelven a aumentar, desde 4’00047 hasta
test1() 4’18449.
Denota una pendiente muy pronunciada en descenso de tiempos hasta la iteración 1024.
Denota una pendiente muy pronunciada en ascenso de tiempos a partir de la iteración 524.228.
Desde la iteración 256 hasta 131.072 los tiempos disminuyen progresivamente desde 2’10547
hasta 2’00026. A partir de la iteración 262.144 los tiempos vuelven a aumentar, desde 2’00047
test2() hasta 2’35237.
Denota una pendiente algo pronunciada en descenso de tiempos hasta la iteración 4.096.
Denota una pendiente muy pronunciada en ascenso de tiempos a partir de la iteración 524.228.
Desde la iteración 256 hasta 131.072 los tiempos disminuyen progresivamente desde 1’44922
hasta 1’3337. A partir de la iteración 262.144 los tiempos vuelven a aumentar, desde 1’33421 hasta
test3() 2’05429.
Denota una pendiente muy suave en descenso de tiempos hasta la iteración 4.096.
Denota una pendiente muy pronunciada en ascenso de tiempos a partir de la iteración 524.228.
Desde la iteración 256 hasta 8.192 los tiempos disminuyen progresivamente desde 1’25781 hasta
1’03223. A partir de la iteración 16.384 los tiempos vuelven a aumentar y se mantienen hasta la
test4() iteración 524.288, desde 1’18048 hasta 1’22194, que vuelve a subir con una pendiente muy
elevada hasta la iteración 1.048576 con un tiempo de 1’85404. Volviendo a suavizar nuevamente
la pendiente creada.
209
210
Miguel Ángel Cifredo
macifredo@gmail.com
1. Objetivos.
En esta práctica se trata de estudiar el Algoritmo de Tomasulo y algunos ejemplos simples de bucles vectorizables
para distintos grados de superescalaridad m.
Se manejará un simulador visual desarrollado como proyecto Fin de carrera en el Departamento de Arquitectura
y Tecnología de Computadores de la Universidad de Sevilla: SUPERTOMASIM (SUPERescalarTOMasuloSIMulator).
Éste simula un procesador didáctico superescalar y superencadenado de un modelo de RISC típico de 32 bits
(llamado DLX), que dispone de “scheduling” dinámico (Algoritmo de Tomasulo). Además son configurables tanto
el grado de superescalaridad como los recursos del procesador: F.U. (Functional Units), CDB (Common Data Bus),
RS (Reservation Station), etc.
2. Preparación.
Piense y resuelva las siguientes preguntas sobre el código dado. Anótelo en la tabla de resultados:
c) ¿Se puede desenrollar? ¿Se pueden computar en paralelo todos los elementos de los vectores x, y (es
decir, es vectorizable)? Razonar.
211
3. Realización de la práctica.
Este simulador admite cualquier código RISC (del llamado procesador DLX). Como se ve en la siguiente figura,
muestra el cronograma que se está ejecutando y el estado completo (RS, registros, memoria, etc.) del procesador
simulado.
Las etiquetas (nombre del registro interno tras el renombrado) se llaman igual que las RS (LOAD01, LOAD02, … ,
ALUINT01, ALUINT02, etc.). Los registros y RS que esperan por una etiqueta (por existir una dependencia real RAW)
muestran tal nombre en lugar del valor (por ejemplo, en los registros R1, F11 por las RS ALUINT01, MULFP02 de la
figura).
Y permite configurar en el menú Windows → Configuration (ver cuadro de diálogo inferior derecho de la imagen):
El cronograma y configuración se pueden salvar en ficheros aparte (el cronograma tiene cada fase separada por
tabuladores para que se pueda copiar fácilmente por ejemplo en una hoja de cálculo de Excel).
212
Miguel Ángel Cifredo
macifredo@gmail.com
Cada bloqueo estructural se muestra repitiendo la fase que no puede avanzar. Por ejemplo: IF IF IF IS significa
que han habido dos ciclos de bloqueo porque IS no pudo ejecutarse (según notación de clase: IF - - IS).
La BTB predice saltos como siempre tomados (se podría cambiar esto usando un fichero llamado BTB.txt).
Para cambiar cualquier parámetro de la configuración se tendrá que realizar (cuadro de diálogo de configuración:
Windows → Configuration) o para cambiar de código simulado, debe primero resetear el simulador con la
opción: File → Reset Sim. Se pierde evidentemente todo el cronograma anterior.
Se pide:
d) Ejecute paso a paso (tecla F7) o de 5 en 5 ciclos (tecla F8) hasta que la primera Store se haya emitido
(haya hecho la fase IS). Si es necesario, mueva la barra de scroll del cronograma para poder ver los
últimos ciclos. Dibuje el contenido de las R.S. (que están ocupadas) de ADDF, MULF, SF justo cuando la
primera Store se haya emitido (haya hecho la fase IS). Explique por qué las R.S. tienen este contenido.
e) Ejecute la primera iteración paso a paso (tecla F7) o de 5 en 5 ciclos (tecla F8), o de 20 en 20 icono .
Mueva scroll del cronograma para poder ver los últimos ciclos. Copie el cronograma de la primera
iteración y compruebe que las dependencias reales están retrasando las fases EX (dibuje con flechas de
WB a EX los bypasses a través del CDB).
f) ¿Cuál es la cadena crítica de ejecución (cadena de RAWs más larga) en una única iteración?
g) Ejecute más iteraciones hasta que se compruebe que se ha llegado a un “estacionario” del bucle, es
decir, que todas las iteraciones tienen el mismo cronograma. ¿Cuánto vale el CPI real del bucle en el
estacionario? Nota: para medir ciclos reales, cuente desde la fase IS del primer LF de una iteración, hasta
la fase IS del primer LF de la siguiente iteración.
213
2. Para bucle vectorizable (RISC SUPERESCALAR).
Modifique la configuración para que sea superescalar de grado 3 (Issue Width = 3).
Se pide:
i) Hallar el CPIideal, real y CPIbloqueo para las iteraciones 3 y 4. Recuerde: para medir ciclos reales, cuente
desde la fase IS del primer LF de iteración 3, hasta la fase IS del primer LF de iteración 5. El CPI bloqueo
puede calcularlo restando CPIreal - CPIideal o contando los bloqueos (esto puede resultar más difícil).
j) Resetear el simulador con la opción: File → Reset Sim y repita lo mismo para superescalar de grado 6
(Issue Width = 6). Hallar CPIideal, CPIreal y CPIbloqueo para las iteraciones 3 y 4.
k) ¿Por qué el CPIreal es el mismo para altos grados de superescalaridad (Issue Width)?
l) Resetear el simulador con la opción: File → Reset Sim, y aumente el número de R.S. de Store (y tal vez
de otras F.U.) hasta que no haya bloqueos por agotamiento de R.S. ¿Cree que el número máximo de R.S.
de Store consumidos u ocupados (sin que haya bloqueos) es proporcional al grado de superescalaridad
(Issue Width)? ¿Por qué?
- El número máximo de RS de cada tipo es 150. El número máximo de F.U. de cada tipo es 10. Podría
interrumpirse la simulación si se superan estos límites (no se chequean estos rangos en la configuración).
Cuando escriba código DLX, debe tener en cuenta los siguientes pormenores:
- Detrás de la directiva .text (cuando se escriba el código), no se pueden poner líneas en blanco, ni líneas
que comiencen por “;” (es decir, comentarios que ocupen toda la línea).
- Las etiquetas de salto deben empezar en el primer carácter de la línea y no pueden estar solas en una
línea, sino que detrás del “:” debe ir una instrucción válida; por ejemplo, así:
bucle: lbu r3, 0(r1) ; Carga Arrayx[i]
- Los registros flotantes de doble precisión se llaman d0, d1, d2, etc. (no son válidos los f0, f2, etc.).
214
Miguel Ángel Cifredo
macifredo@gmail.com
TABLA DE RESULTADOS
ALUMNO:
Preparación:
215
Tabla de Resultados:
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
lf f5, 0(r1) IF IS EX WB
lf f6, 0(r2) IF IS EX WB
multf f5, f11, f5 IF IS EX1 EX2 EX3 EX4 WB
addf f5, f6 , f5 IF IS ○ ○ ○ ○ EX1 EX2 WB
e) sf 0(r2) , f5 IF IS ○ ○ ○ ○ ○ ○ EX WB
addi r1, r1, 4 IF IS EX1 ○ WB
addi r2, r2, 4 IF IS EX1 ○ WB
slti r3, r2, a IF IS ○ ○ EX WB
bnez r3, bucle IF IS ○ ○ ○ EX WB
f) La cadena crítica de ejecución viene definida por las instrucciones: MULT → ADDF → SF
Cálculo del CPIreal del bucle en el estacionario, esto es, desde la fase IS del primer LF de una
iteración, hasta la fase IS del primer LF de la siguiente iteración, viene definido como:
g)
ciclos 9
CPIreal = = = 1
instrucciones 9
Aun habiendo sólo hay una R.S. de Store, el procesador no se bloquea porque esta se libera justo
h)
cuando se vuelve a necesitar, como podemos apreciar en antes de la etapa 19.
El CPIreal es el mismo para altos grados de superescalaridad (Issue Width) porque sólo
k) disponemos de una Estación de Reserva de LF y, hasta que no se libera el LF de la primera
iteración, no puede utilizarse el de la siguiente iteración.
l)
216
Miguel Ángel Cifredo
macifredo@gmail.com
Objetivos.
En esta práctica los alumnos estudiarán los límites del ILP que se pueden conseguir con técnicas dinámicas en
GPP. Los alumnos repartidos en distintos grupos, observarán diferentes casos en los que prácticamente se
alcanza el límite del flujo de datos (data-flow limit), de manera que el tiempo medio por iteración de un pequeño
bucle es debido a tal límite. Los alumnos tendrán que exponer los resultados de esta práctica en el laboratorio
(ver último apartado: “Presentaciones de los resultados de la práctica”)
Preparación.
A cada uno de los grupos (de 2 o 3 alumnos) se le asignará un problema a resolver. Deberán ejecutar y analizar
los resultados tanto del código que se le haya asignado (escribir en la función problem) como del código de
ejemplo suministrado (función example).
Realización de la práctica.
Cada grupo deberá resolver el problema asignado y descubrir si su problema es totalmente “paralelizable” y no
tiene saltos condicionales dentro del bucle ni instrucciones que puedan bloquear la CPU. Deberá también
analizar el comportamiento del código proporcionado como ejemplo y explicar las diferencias obtenidas.
Máquina (sobre todo procesador) donde se ejecuta el código. Se aconseja usar CPU-Z (hay una copia de
Nov.2012 en ev.us.es, o también descargar de http://www.cpuid.com/softwares/cpu-z.html), el cual indica
muchas de las características del procesador.
Compilador y Opciones de optimización usadas (sólo si algún grupo usa otro que no sea el del proyecto
suministrado).
Códigos de Ensamblador del x86 que crean interesantes para explicar los resultados de tiempo.
Realizar pruebas con tamaños mayores de vectores (cambiar valor de la constante N_ITER).
Con todo lo anterior, se deberá redactar una presentación (con Power Point u OpenOffice Impress) de
aproximadamente 8 minutos de duración, dividida en 2 partes aproximadamente iguales.
En la presentación, explicar la causa de la duración y las variaciones (si las hay) que se encuentran al cambiar de
código y al aumentar N_ITER. Y cualquier otra circunstancia que considere importante.
217
Problemas.
1) Sumatorios de elementos de vectores. Atención: evitar que se produzcan desbordamientos (overflow) en las
variables float. Probar con:
a. Una sola suma.
b. Dos sumas sobre el mismo acumulador.
2) Por iteración se debe incluir divisiones donde el denominador tenga algún elemento de vectores. Atención:
evitar que se produzcan desbordamientos (overflow) en las variables float. Probar con:
a. Una sola división.
b. Dos divisiones.
3) Provocar muchos accesos a memoria por iteración, sin que haya operaciones de tipo acumulador (sumatorio,
producto múltiple, etc.), ni tampoco dependencias entre iteraciones (recurrencia). Probar con:
a. 4 accesos por iteración.
b. 8 accesos por iteración.
c. 12 accesos por iteración.
4) Debe tener una sentencia if(). En el cuerpo del if() hay que realizar algún tipo de operación aritmética con
vectores (sin que haya operaciones de tipo acumulador -sumatorio, producto múltiple, etc.-, ni tampoco
dependencias entre iteraciones–recurrencia-). Probar con dos condiciones del if():
a. La condición sea difícilmente predecible (por ejemplo Random).
b. La condición sea predecible por la BTB.
Notas:
En la siguiente sesión, se sorteará qué 2 alumnos de cada grupo presentan (cada uno la mitad). Si el grupo fuera
de 3 alumnos, el tercero se quedará esperando las preguntas de los profesores. Por tanto, todos los alumnos
deben prepararse la presentación entera. Se proporciona a los alumnos en ev.us.es una plantilla sobre cómo se
van a evaluar las presentaciones (matriz de evaluación).
Importante.
Si se usa VS 2012 o VS 2013, el compilador trabaja por defecto con las instrucciones del núcleo vectorial o
multimedia SSE2 o AVX. Es decir, puede generar a veces código con instrucciones de sufijo ps (como addps, mulps,
movaps, etc. cada una computa 4 elementos por instrucción en paralelo, usando DLP).
Para esta práctica, eso conduce a tiempos muy pequeños. Es preferible, elegir en: Propiedades del proyecto →
C → Generación de código, la opción de usar Instrucciones SSE (no poner SSE2). De esa forma, se usan
instrucciones escalares con sufijo ss y los resultados son más acordes con lo que se espera (al procesar solo con
un elemento por iteración)
218
Miguel Ángel Cifredo
macifredo@gmail.com
Solución:
Objetivo:
Estudio de los casos en los que prácticamente se alcanza el límite del flujo de datos (data-flow limit), de
manera que el tiempo medio por iteración de un pequeño bucle es debido a tal límite.
Generación de código:
- Modelo de punto flotante: Rápido (/fp:fast)
Optimización:
- Optimización: Optimización completa (/Ox)
- Tamaño o velocidad: Favorecer código rápido ( /Ot)
219
Apartado a) Una sola suma.
Código fuente
220
Miguel Ángel Cifredo
macifredo@gmail.com
Código fuente
221
222
Miguel Ángel Cifredo
macifredo@gmail.com
Objetivos.
En esta práctica los alumnos estudiarán los límites de ILP que se pueden conseguir con técnicas dinámicas en
procesadores superescalares.
Para ello comprobarán cómo se ejecuta la cadena de diversos códigos ASM usando SUPERTOMASIM
(SUPERescalar TOMasulo SIMulator).
Se observarán diferentes casos en los que prácticamente se alcanza el límite del flujo de datos (data-flow limit),
de manera que el número de ciclos (tiempo medio) por iteración de un pequeño bucle es debido a tal límite.
Los alumnos tendrán que exponer los resultados de esta práctica en el laboratorio (ver último apartado
“Presentaciones de los resultados de la práctica”.)
Preparación.
Se dan 4 códigos ASM. Cada grupo tendrá que trabajar sobre dos códigos ASM que le asignará el profesor de
prácticas (como hay 4 códigos, de dos en dos salen 6 combinaciones posibles).
Son bucles simples para ver el máximo IPC que se puede conseguir.
ASDP3B_TOMASIM1.s
ASDP3B_TOMASIM3.s
ASDP3B_TOMASIM4.s
ASDP3B_TOMASIM5.s
Se recomienda trabajar con las siguientes configuraciones que van de menor a mayor grado de superescalaridad
(m = 1, 3, 6), con objeto de entender el código primero y luego que se vea más fácilmente como se alcanza el
límite de ILP. No obstante, los alumnos pueden usar otras configuraciones. Por ejemplo, una vez que sepan cuál
es la instrucción que causa el bloqueo, pueden reducir el número de R.S. de tal instrucción.
asdp3b_configuracion_a.xml (m = 1)
asdp3b_configuracion_b.xml (m = 3)
asdp3b_configuracion_c.xml (m = 6)
223
Las configuraciones que se dan son estas:
asdp3b_configuracion_a.xml (m = 1)
asdp3b_configuracion_b.xml (m = 3)
asdp3b_configuracion_c.xml (m = 6)
224
Miguel Ángel Cifredo
macifredo@gmail.com
Importante:
En el Simulador SUPERTOMASIM, las F.U. no están segmentadas nunca, por tanto, para emular la segmentación,
se puede configurar para que tenga tantas F.U. como latencia tiene la operación (por ejemplo, la FU MULT en la
configuración asdp3b_configuracion_c.xml). Por otra parte, si el número de Unidades Funcionales es mayor que
la latencia, se podrían ejecutar varias operaciones por ciclo (por ejemplo, la Unidad Funcional INT en la
configuración de arriba asdp3b_configuracion_c.xml).
De esa forma, si se ejecutan varias instrucciones juntas para una operación con única Unidad Funcional (aunque
fueran independientes), tendríamos:
OPER1 IF IS D1 D2 D3 D4 D5 WB
OPER2 IF IS o o o o o D1 D2 D3 D4 D5 WB
OPER3 IF IS o o o o o o o o o o D1 D2 D3 D4 D5 WB
Sin embargo, para otra operación donde el número de Unidades Funcionales es igual que la duración, tendríamos
si se ejecutan varias instrucciones juntas (sin dependencia real):
OPER1 IF IS D1 D2 D3 D4 D5 WB // está usando la Unidad Funcional #1
OPER2 IF IS D1 D2 D3 D4 D5 WB // está usando la Unidad Funcional #2
OPER3 IF IS D1 D2 D3 D4 D5 WB // está usando la Unidad Funcional #3
OPER4 IF IS D1 D2 D3 D4 D5 WB // está usando la Unidad Funcional #4
OPER5 IF IS D1 D2 D3 D4 D5 WB // está usando la Unidad Funcional #5
Realización de la práctica.
Con el Simulador SUPERTOMASIM, se aconseja en general probar primero con un procesador escalar con pocos
recursos (RS, FU, etc.), para comprender el código y el funcionamiento (grado superescalaridad = 1). Por ejemplo
con la configuración del primer fichero xml.
Después, usar la configuración del superescalar (la que se indica arriba para cada código o alguna similar), en la
que a pesar de que tiene muchos recursos, surjan rápidamente bloqueos y se vea fácilmente qué instrucción es
la que los empiezan a producir. Con ello, deducir cuál el límite de ILP y su causa.
Recordar que para saber qué instrucción es la causa de los bloqueos hay que buscar la primera que tiene varias
fases IF en su cronograma: IF IF IF IS …
225
Nota:
El grupo que tiene que estudiar los saltos del if() y pensar cuando la BTB acertará en la predicción. Para hallar
la duración media de una iteración, halle la media ponderada de dos iteraciones (una en la que la BTB acierta y
otra en la que falla).
Aunque el formato de la presentación es libre, se detallan algunos consejos sobre la realización y los resultados
buscados. Se deben recoger los resultados de ejecución. Utilice tablas con los resultados que obtenga. Piense y
resuelva por ejemplo preguntas sobre los códigos ASM dados, su ejecución y límite de ILP como estas:
a) ¿Qué hacen?
c) ¿Son vectorizables directamente? ¿Se podrían vectorizar si se hicieran cambios de alto nivel?
- Para los códigos que usan saltos dentro del bucle, además hay que pensar en estas preguntas:
- Como la BTB de SUPERTOMASIM predice siempre Tomada, ¿cuándo acertará en los diversos saltos?
- Si la BTB fuera muy sofisticada, ¿acertará en los diversos saltos?
e) ¿Cuánto valen los diversos CPI en el estacionario (recordar cómo medirlo según práctica anterior P3A)?
f) ¿Vale igual el CPI real en el estacionario para un RISC Escalar y uno Superescalar?
j) Identificar la causa del CPI bloqueo, e intentar calcular teóricamente por qué sale eso.
k) etc. etc.
Si algún alumno quiere probar una reordenación en SUPERTOMASIM, debe cortar y pegar líneas completas del
ASM para evitar fallos del simulador (ver apéndice para más detalle).
Con todo lo anterior, se deberá redactar una presentación (estilo Power Point u OpenOffice Impress) de
aproximadamente 10 minutos de duración, dividida en 2 partes aproximadamente iguales en duración
(aproximadamente 5 minutos).
En la sesión, se sorteará qué alumno de cada grupo presenta primero y cuál después (cada uno la mitad). Por
tanto, todos los alumnos deben prepararse la presentación entera. Se dejará en ev.us.es una plantilla sobre cómo
se van a evaluar las presentaciones (matriz de evaluación).
Todos los alumnos oyentes, deben aprender de lo que exponen sus compañeros, pues al final de esta sesión se
hará un test de evaluación ev3.us.es. Además el profesor de prácticas podrá valorar positivamente las críticas o
errores que un oyente encuentre sobre las exposiciones de otros.
226
Miguel Ángel Cifredo
macifredo@gmail.com
Suponer que:
- Superescalar de grado m = 5.
- Frecuencia = 3 GHz
- Suponer que la fase EX de la división tarda 6 ciclos. Es Unidad Funcional única y no está segmentada.
- Suponer que la fase EX de los Load/Store tarda 2 ciclos. Hay dos puertos para caché L1 (dos Unidades
Funcionales) y están segmentadas.
- El resto de operaciones con fase EX de sólo 1 ciclo y tienen infinitas Unidades Funcionales.
Responder:
a) Escribir el código (para un RISC). Por comodidad usar un único registro base Rb, modificando los
desplazamientos según el array. Los desplazamientos (offsets) respecto del registro base Rb serían:
(Rb)8*N_ITER, (Rb)16*N_ITER, para y[i], z[i] respectivamente.
b) Dibujar el cronograma de la primera iteración. Suponer BTB que siempre acierta y tiene precargadas
suficientes instrucciones, para que la fase IS pueda emitir hasta 5 instrucciones.
c) Calcular el CPI para el estacionario para N_ITER pequeño (todo vector cabe en caché L1) y para N_ITER
grande (todo acceso a vectores se hace a RAM). Justificar los resultados.
227
Solución:
Apartado a)
La idea principal de lo que se va a realizar es cargar cada elemento de vector en Dx, Dy y Dz, realizar las operaciones
correspondientes, y saltar. Obviamos la instrucción de store ya que se podría realizar al final del bucle y usar el
registro Dacum como temporal hasta el final de la operación. Suponemos todos los vectores y datos ya
precargados en registros y reordenamos el código para obtener menor número de ciclos de espera.
bucle:
LD (Rb)16, Dz
LD (Rb)8 , Dy
LD (Rb)0 , Dx
DIVD Dy, Dy, Dz
ADDI Rb, Rb, 8
ADDD Dacum, Dacum, Dx
ADDD Dacum, Dacum, Dy
SLT Rc, Rb, Rfinx
BNEZ Rc, bucle
Apartado b)
Observamos la primera iteración al completo. Como se puede apreciar, se producen ciertos ciclos de espera que
no afectan al CPI ya que quedan enmascarados por la operación DIVD.
En la siguiente captura observan las dos primeras iteraciones, donde podemos apreciar que el CPI vendrá
determinado por la distancia entre los WB de cada DIVD. Se denota por EX.L1 y EX.L2 a cada una de las unidades
funcionales para los LOAD/STORE.
228
Miguel Ángel Cifredo
macifredo@gmail.com
Apartado c)
1
Como el procesador se supone tiene escalabilidad m = 5, entonces sabemos que su CPI es: 𝐶𝑃𝐼𝐼𝑑𝑒𝑎𝑙 =
5
Procedemos a calcular el CPI de bloqueo. Para lo que necesitamos conocer el CPI real, luego:
𝐶𝑃𝐼𝐵𝑙𝑜𝑞𝑢𝑒𝑜 = 𝐶𝑃𝐼𝐼𝑑𝑒𝑎𝑙 − 𝐶𝑃𝐼𝑅𝑒𝑎𝑙
𝑛ú𝑚𝑒𝑟𝑜 𝑑𝑒 𝑐𝑖𝑐𝑙𝑜𝑠 6
𝐶𝑃𝐼𝑅𝑒𝑎𝑙 = = = 0′6̂
𝑛ú𝑚𝑒𝑟𝑜 𝑑𝑒 𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠 9
El número de ciclos es 6 porque entre DIVD y DIVD se desfasan entre iteración e iteración 6 ciclos de reloj, ya que
el resto de ciclos de esperas, quedan enmascarados por esta operación. Luego nuestro CPI de bloqueo será:
6 1 7
𝐶𝑃𝐼𝐵𝑙𝑜𝑞𝑢𝑒𝑜 = − = = 0′46̂
9 5 15
El CPI Real calculado es el correspondiente para un N Iter suficientemente pequeño para que sólo se acceda a caché,
y no se produzcan retrasos o esperas por accesos a memoria.
Para un N Iter suficientemente grande que requiera de constantes accesos a memoria por fallos de caché (tanto
forzosos como por conflicto) necesitamos del uso del ancho de banda de la memoria dado en el enunciado.
Calculamos a continuación el tiempo de ejecución para sustituirlo en dicha fórmula; de la que desconocemos
aún el CPI Real.
𝑡𝑒𝑗𝑒𝑐𝑢𝑐𝑖ó𝑛 = 𝐶𝑃𝐼𝑅𝑒𝑎𝑙 ∙ 𝑇 ∙ 𝑁º𝐼𝑛𝑠𝑡𝑟𝑢𝑐𝑐𝑖𝑜𝑛𝑒𝑠
Ahora, calculamos los bytes consumidos, que son 8 Bytes por acceso en tres accesos, durante N Iter iteraciones:
Conociendo el ancho de banda de la memoria, podemos sustituir en la fórmula del AB y despejar el CPI Real.:
24 ∙ 𝑁𝐼𝑡𝑒𝑟 24 ∙ 3 ∙ 109
𝐴𝐵 = = = 8 ∙ 109
𝐶𝑃𝐼𝑅𝑒𝑎𝑙 ∙ 𝑇 ∙ (𝑁𝐼𝑡𝑒𝑟 ∙ 9) 𝐶𝑃𝐼𝑅𝑒𝑎𝑙 ∙ 9
Test 1.
□ El compilador prepara las instrucciones para que las emita el procesador sin comprobar nada.
Para un procesador con Algoritmo de Tomasulo, predicción y especulación dinámicas, ¿qué características
tiene?:
Para un RISC con Algoritmo de Tomasulo y fases IF IS EX WB, qué frase/s son verdad para su BTB:
□ Se predice un salto en la fase IS, se busca la instrucción predicha y se resuelve en tal ciclo.
231
Parte: Rendimiento con Visual Studio.
¿Qué ocurre al ejecutar este código en un procesador moderno para un valor alto de N_ITER?
for (i=0; i<N_ITER; i++) a[i] = b[i] + 8;
¿Qué ocurre al ejecutar este código en un procesador moderno para un valor alto de N_ITER?
for (i=0; i<N_ITER; i++) a[i] = 3 / b[i];
□ Dependiendo del valor de los elementos de b[i] podrán aparecer ciclos de bloqueo.
□ Al no existir dependencias entre iteraciones no aparece un número significativo de ciclos de bloqueo.
La operación de división está no segmentada y requiere tantos ciclos que al cabo de algunas
iteraciones empiezan a aparecer ciclos de bloqueo.
□ El tiempo de ejecución está principalmente influido por los accesos a memoria al leer y escribir sobre
diferentes vectores.
¿Cuál es la causa de que al ejecutar el siguiente código, si N_ITER es grande, empiecen a aparecer ciclos
de bloqueo?
for (i=0; i<N_ITER; i++) a[i] = b[i] + c[i] + d[i] + e[i] + f[i] + g[i] + h[i];
¿Cuál es la causa de que al ejecutar el siguiente código, si N_ITER es grande (pero no se accede a RAM),
empiecen a aparecer ciclos de bloqueo?
for (i=0; i<N_ITER; i++) z += a[i];
□ Los saltos condicionales del bucle for generan muchos ciclos de bloqueo de control.
□ Los accesos a caché para acceder a los elementos del vector, suelen ser muy lentos (cientos de ciclos).
La suma acumulada introduce dependencias reales de datos entre iteraciones.
□ La suma no suele estar segmentada, lo que acabará bloqueando en el estacionario.
232
Miguel Ángel Cifredo
macifredo@gmail.com
Pregunta 1:
Dado el siguiente cronograma de dos iteraciones de un bucle, y sabiendo que la U.F. de DIVF no está segmentada
y dura 5 ciclos, la cual produce bloqueos totales que se muestran como IF IF IF IS (p ej a partir de C27), ¿cuál o
cuáles de las siguientes afirmaciones es (o son) verdadera?
Si el procesador tuviera un grado de superescalaridad (m) mayor que 2, el CPI seguiría siendo el mismo.
□ Si el procesador tuviera un grado de superescalaridad (m) mayor que 2, el CPI se iría reduciendo
aproximadamente de forma proporcional con m.
□ Si el procesador tuviera un grado de superescalaridad (m) mayor que 2, el CPI se iría reduciendo, pero a
partir de m = 5, el CPI ya sería el mismo (pues la U.F. DIVF dura 5 ciclos).
Pregunta 2:
La primera ADDF contiene el resultado 11, y los registros fuentes con valores 5, 6. La segunda ADDF no
contiene ningún resultado, y sólo un registro fuente con valor 5 (el otro aún no le ha llegado).
□ La primera ADDF aún no contiene el resultado (pues no ha hecho WB), y los registros fuentes con valores
5, 6. La segunda ADDF no contiene ningún resultado, pero sí los valores de los dos registros fuentes (5 y
11 que es la suma de 5+6).
□ La primera ADDF contiene el resultado 11, pero no los valores de los registros fuentes, pues tal R.S. se ha
liberado ya. La segunda ADDF no contiene ningún resultado, pero sí los valores de los dos registros fuentes
(5 y 11 que es la suma de 5+6).
233
Pregunta 3:
Dado el siguiente cronograma, y sabiendo que el contenido inicial de los registros es F0 = 0, F5 = 5 y F6 = 6, ¿cuál
o cuáles de las siguientes afirmaciones es verdadera?
El cronograma es correcto, y la segunda ADDF (que empieza a ejecutarse en el ciclo C30), tendría que
guardar el valor de F5 en algún campo de la R.S. Se necesitarán tres R.S. libres de ADDF.
□ El cronograma es incorrecto porque la tercera ADDF no puede ejecutarse a partir del ciclo C27, ya que
tiene que escribir sobre el registro F5, que lo necesita la segunda ADDF (que aún no ha empezado).
□ El cronograma es correcto y sólo se necesitarán dos R.S. libres de ADDF, ya que la segunda ADDF usaría la
R.S. de la primera (que aún no ha empezado).
□ El cronograma es incorrecto porque la segunda ADDF podría haber empezado a ejecutarse a partir del
ciclo C26.
234
Miguel Ángel Cifredo
macifredo@gmail.com
Objetivos y preparación.
En esta práctica vamos a estudiar la optimización de código mediante el uso de las extensiones multimedia o
núcleos vectoriales disponibles en los procesadores PIV.
- Comienza con el Pentium MMX (1994), en el que se añadieron un conjunto de 8 registros de 64 bits
(2x32 bits) y una serie de instrucciones que permitían el procesado SIMD (Single Instruction Múltiple
Data) de datos enteros. Estas extensiones se denominaron MMX.
- El siguiente paso se da en el Pentium III, el cual contiene un conjunto de 8 registros de 128 bits (4x32
bits) y una serie de instrucciones para el procesado SIMD de flotantes en simple precisión. A estas
instrucciones se les denomina SSE (Streamming SIMD Extensions).
- Posteriormente, en el P4 se incluyen instrucciones (SSE2 y SSE3) para poder emplear los registros SSE
con datos enteros y flotantes de simple o doble precisión (4x32bits o 2x64 bits).
Un detalle significativo de la implementación de las SSE, es que en el PIII cualquier operación cuyos operandos
sean 4 flotantes de simple precisión, tiene una duración equivalente a dos operaciones escalares; mientras que
en el PIV la duración es equivalente a la de procesar un único escalar.
Tanto los niveles de caché como su tamaño, número de vías y tamaño de la línea tendrán una influencia muy
importante en el rendimiento de las pruebas que haremos en esta práctica.
Entender cómo está organizado el código que se proporciona con este enunciado.
Conocer cómo se ubican en memoria cada una las variables que aparecen en éste en base a sus
declaraciones y entender el subconjunto de instrucciones multimedia IA32 que se proporcionan.
Estudio previo:
Antes de comenzar la práctica, traer un diagrama de L1, antes de la ejecución de init, tras su ejecución
y tras la ejecución del bucle SAXPY, para un número de elementos de x[] e y[] igual al que ocupan en
una vía de L1, para el doble de ésta, … y así hasta el número de elementos que llene y dobla dicha caché.
235
A continuación se presentan las instrucciones IA32 que serán necesarias para la realización de la práctica, con su
referencia a “IA32 Intel Architecture Software Developer’s Manual. Volume 2: Instruction Set Reference”. Nos
restringiremos a:
- Todos los operandos en registro o memoria con los que trabajaremos serán de 32 bits (o de 128 para
registros xmm de 128 bits y accesos multimedia) aunque algunas instrucciones admiten otros tamaños
de operandos.
- Los operandos inmediatos pueden cambiar de tamaño, pero no afecta a los benchmark de esta práctica.
ARITMÉTICAS ENTERAS:
add r/m, r/m/imm[32,16 o 8] (Add pág 3.021)
lea r, m (Load Effective Address pág 3.372)
sbb r/m, r/m/imm[32,16 o 8] (Integer SuBtraction with Borrow pág 3.685)
sub r/m, r/m/imm[32,16 o 8] (Subtract pág 3.729)
cmp r, r/m/imm[32,16 o 8] (CoMPare two operands pág 3.082)
SALTOS:
jne etiqueta (Jump if Not Equal pág 3.352)
ACCESOS A MEMORIA:
mov r/m, r/m/imm[32,16 o 8] (Move pág 3.430).
Nota: Ambos operandos deben ser del mismo tamaño y no pueden referenciar a memoria.
movss xmm/m, xmm/m (Move Scalar Single-precision floating-point values pág 3.483)
movaps xmm/m128, xmm/m128 (Move Aligned Packed Single-precision floating-point values pág 3.441)
ARITMÉTICAS MULTIMEDIA:
addps xmm, xmm/m (Add Packed Single-precision floating-point values pág 3.025)
addss xmm, xmm/m (Add Scalar Single-precision floating-point values pág 3.029)
mulps xmm,xmm/m128 (Multiply Scalar Single-precision floating-point values pág 3.496)
mulss xmm, xmm/m (Multiply Scalar Single-precision floating-point values pág 3.500)
shufps xmm, xmm/m128, imm8 (Shuffle Packed Single-precision floating-point values pág 3.703)
sqrtps xmm, xmm/m128 (Computate Square Roots of Packed Single-precision floating-points values – pág 3.713)
Como registros de programación, podrá hacer uso de eax, ebx, ecx, edx, esi y edi de 32 bits y xmm0, ... xmm7.
NOTA: la instrucción cmp no se debe separar, en general, del salto jne, pues en los x86 hay un bit de estado que
guarda el resultado de la última operación aritmética (de forma que si entre cmp y jne hubiese otra
instrucción aritmética, tal bit podría ser modificado y perderse el resultado de la comparación).
236
Miguel Ángel Cifredo
macifredo@gmail.com
Realización de la práctica:
En toda esta prueba, la constante N debe valer 1, de forma que el tamaño de los vectores será VSIZE.
a) Abrir el proyecto que se proporciona y verificar que se encuentran activas las siguientes opciones de
compilación en: Propiedades de configuración → C/C++ →
- General: Formato de la información de depuración → Base datos de prog. /Zi,
- Optimización → Optimización: Optimización completa (/Ox)
- Optimización → Tamaño o velocidad: Favorecer código rápido (Ot).
- Generación de código → Comprobaciones básicas en tiempo de ejecución: Predeterminado
- Generación de código → Habilitar conjunto de instrucciones mejorado: SSE2 (/arch:SSE2)
- Generación de código → Modelo de punto flotante: Rápido (/fp:fast)
c) Implementar el bucle SAXPY de tres formas: en lenguaje C (ya se da hecho), en ensamblador haciendo
uso de las instrucciones multimedia que operan sobre un único dato (sufijo ss), y en ensamblador
haciendo uso de las que operan sobre múltiples datos (sufijo ps); para sus correspondientes funciones:
codigo_c (ya se da hecho), codigo_sse_escalar y codigo_sse_vectorial, respectivamente. Para insertar
instrucciones en ensamblador dentro del código C (ensamblado en línea) puede utilizar como referencia
la función init(…) del proyecto.
d) Para ejecutar el programa utilice Depurar Iniciar sin depurar (o CTRL+F5). Si ha implementado de forma
incorrecta las funciones solicitadas anteriormente aparecerá el mensaje WRONG RESULTS: check your
code durante la ejecución. Una vez comprobada las implementaciones rellene la tabla de resultados.
a) Dar a la constante N el valor adecuado para que la suma de los tamaños de ambos vectores x[], y[] sea
8 MB y responder las preguntas de la hoja de resultados.
b) Usar CPU-Z para comprobar los tamaños de caché de las máquinas y comprobar que los saltos en
tiempos de ejecución se han producido al desbordar las cachés
237
Memoria voluntaria:
Se proponen las siguientes pruebas. El alumno deberá entregar al menos dos de ellas como memoria voluntaria.
Tenga en cuenta que los resultados van a ser muy diferentes con otro procesador, porque podría incluir diferentes
optimizaciones en hardware (por ejemplo caché de trazas, prebúsquedas de datos si el patrón de acceso es fácil
de adivinar como ocurre en los bucles SAXPY o similares, etc.):
Comprobar en el modo Debug, qué código genera el compilador Visual.Net y comentar como funciona
éste. A continuación, activar los flags de optimización del compilador para generar código rápido y anotar
resultados. Comprobar cómo ha cambiado el código ensamblador generado y pensar en las
optimizaciones que ha hecho el compilador.
Desenrollar en ensamblador dos iteraciones para el código SSE vectorial (se procesarían 8 elementos por
iteración) y para el código SSE escalar, entrelazando y reordenando. Anotar la aceleración conseguida
para diferentes tamaños.
Probar con diferentes zancadas de recorrido de los vectores (en lugar de i++ en el bucle for, usar i+=2,
i+=4, etc.). De esa forma el Miss rate cambia, y deben notarse diferencias mayores cuando los vectores
no caben en los diferentes niveles de cachés.
Realizar las diferentes pruebas con otro procesador más moderno, justificando los resultados en función
de las optimizaciones de la nueva arquitectura.
238
Miguel Ángel Cifredo
macifredo@gmail.com
#include <iostream>
#include "QueryPerformanceTiming_rdtsc.h"
//***********************************************************************
//inicialización con instr. SSE: x[]={1,2,3,4,1,2,3,4...}, y[i]=sqrt(x[i])
void init (int tam) {
__asm {
lea eax, m
lea ebx, x
lea edx, y
mov edi, tam
shl edi, 2
mov ecx, ebx
add ecx, edi
movaps xmm0, [eax] //LD
sqrtps xmm1, xmm0
lp:
movaps [ebx], xmm0 //ST
movaps [edx], xmm1
add ebx, 16
add edx, 16
cmp ebx, ecx
jne lp
}
}
//***********************************************************************
bool checkCode (int tam) {
int i = 0;
while (fabs (results[i] - y[i]) < 1e-6 && i<tam)
i++;
if (i != tam) {
cout << " -> WRONG RESULTS: check your code\n" << endl;
return false;
}
return true;
}
239
//***********************************************************************
int main(void) {
int i;
c1.Calibrate();
c2.Calibrate();
c3.Calibrate();
cout << "*** SSE Scalar for " << i*VSIZE << " elements ***" << endl;
if (codigo_sse_escalar(i*VSIZE)) {
c2.PrintMinimumCycles (" Minimum time in cycles: "); cout << endl;
c2.PrintMeanCycles (" Mean time in cycles: ");
c2.PrintMinimumCyclesByIteration(" Minimum time in cycles for an array
element: ",i*VSIZE); cout << endl << endl ;
}
cout << "*** SSE Packet (vectorial) for " << i*VSIZE << " elements ***" << endl
;
if (codigo_sse_vectorial(i*VSIZE))
{ c3.PrintMinimumCycles (" Minimum time in cycles: "); cout << endl ;
c3.PrintMeanCycles (" Mean time in cycles: ");
c3.PrintMinimumCyclesByIteration(" Minimum time in cycles for an array
element: ",i*VSIZE); cout << endl << endl ;
}
c1.ResetAll();
c2.ResetAll();
c3.ResetAll();
}
return 0;
}
//***********************************************************************
// FUNCIONES
//***********************************************************************
//Código C en otra función para que no influyan las optimizaciones del compilador
void codigo_c (int tam) {
int i, j;
init (tam);
for (j=0; j<N_REPETITIONS; j++) {
c1.Start();
for (i=0; i<tam; i++)
{ // Código a probar
y[i] = a*x[i] + y[i];
}
c1.Stop();
c1.Reset();
}
//Guardo resultados correctos en vector results[]
for(i=0;i<tam;i++)
results[i] = y[i];
}
240
Miguel Ángel Cifredo
macifredo@gmail.com
//***********************************************************************
//Código SSE escalar en ensamblador
bool codigo_sse_escalar (int tam) {
int i;
init (tam);
for (i=0; i<N_REPETITIONS; i++) {
c2.Start();
__asm {
lea eax, a
lea ebx, x
lea edx, y
mov edi, tam
lea ecx, [ebx+edi*4]
lea edi, y
movss xmm2, [eax] // escalar a parte menos significativa
// XXXXXXX EL alumno debe escribir aquí el Código SSE escalar del SAXPY
etiq_esc:
movss xmm0 , [ebx]
movss xmm1 , [edx]
mulss xmm0 , xmm2
addss xmm0 , xmm1
movss [edx], xmm0
add ebx , 4
add edx , 4
cmp ebx , ecx
jne etiq_esc
}
c2.Stop();
c2.Reset();
}
return checkCode(tam);
}
//***********************************************************************
// Código SSE vectorial en ensamblador
bool codigo_sse_vectorial (int tam) {
int i;
init (tam);
for (i=0; i<N_REPETITIONS; i++) {
c3.Start();
__asm {
lea eax, a
lea ebx, x
lea edx, y
mov edi, tam
lea ecx, [ebx+edi*4]
lea edi, y
movss xmm2, [eax] //escalar a parte menos significativa
shufps xmm2, xmm2, 0 //el elemento 0 se replica en todo xmm2
//XXXXXXX EL alumno debe escribir aquí el código SSE vectorial del SAXPY
etiq_vec:
movaps xmm0 , [ebx]
movaps xmm1 , [edx]
mulps xmm0 , xmm2
addps xmm0 , xmm1
movaps [edx], xmm0
add ebx , 16
add edx , 16
cmp ebx , ecx
jne etiq_vec
}
c3.Stop();
c3.Reset();
}
return checkCode(tam);
}
241
TABLA DE RESULTADOS
ALUMNO:
Tiempo en
1183 1351 343
ciclos
Ciclos por
1183 1351 343
Elemento = 2'3105 = 2'6386 = 0'6699
512 512 512
procesado
Aceleración
2'6386 2'6386
respecto del A = = 1'1420 A = = 3'9387
2'3105 0'6699
código 1.0
ensamblador C incrementa 14’2% PS incrementa 293’87%
escalar
19 Instr. 15 Instr. 16 Instr.
IPC = 2'0558 = 5'6848 = 5'9710
4∙2'3105 ciclo 2'6386 ciclo 4∙0'6699 ciclo
Número de elementos del vector x[] Fórmula de los ciclos por elemento procesado:
El bucle en código C, aunque hace una operación sobre un dato, el compilador desenrolla 4 veces.
El código escalar trabaja también sobre un dato, pero sin desenrollar. Por este motivo, el código
C es más rápido. Si lo comparamos con PS, múltiples datos, este es el mejor, 4 datos al mismo
tiempo.
¿Qué diferencia habría entre el código que usa el núcleo vectorial (instrucciones sobre múltiples datos)
y el código surgido del desenrollado de 4 iteraciones usando instrucciones escalares?
Las instrucciones vectoriales son más “pesadas” que las instrucciones escalares, ya que
las vectoriales (128 bits) tratan 4 elementos al mismo tiempo.
242
Miguel Ángel Cifredo
macifredo@gmail.com
Tamaño
que usan C , Escalar Vectorial C Escalar Vectorial
ambos Nº eltos
vectores de x[] MR, L1 MR, L2 MR, L1 MR, L2 ciclos/ ciclos/ ciclos/
Juntos (teórico) (teórico) (teórico) (teórico)
Acel. Acel. Acel.
eltos eltos eltos
(KBytes)
4 512 0,16 0 0’04 0 2,283200 1,1347305 2,590820 1,00 0,628906 4,119565
8 1024 0,16 0 0’04 0 2,262700 1,0483384 2,372070 1,00 0,611816 3,877095
16 2048 0,16 0 0’04 0 2,257570 1,0862983 2,452390 1,00 0,644287 3,806366
32 4096 0,16 0 0’04 0 2,403690 1,0970494 2,636960 1,00 1,159550 2,274134
64 8192 0,32 0 0’04 0 2,402400 1,0981682 2,638240 1,00 1,161250 2,271891
128 16384 0,32 0 0’08 0 2,402400 1,0989685 2,640170 1,00 1,171080 2,254469
256 32768 0,32 0 0’08 0 2,403900 1,0994401 2,642940 1,00 1,172360 2,254373
512 65536 0,32 0 0’08 0 2,405980 1,0998646 2,646260 1,00 1,179090 2,244316
1024 131072 0,32 0 0’08 0 2,437010 1,1022199 2,686120 1,00 1,189560 2,258081
2048 262144 0,32 0 0’08 0 2,549280 1,0905459 2,780100 1,00 1,466510 1,895736
4096 524288 0,32 0 0’08 0 5,134660 1,0052024 5,161370 1,00 5,104470 1,011148
8192 1048576 0,32 0 0’08 0 6,042040 1,0230682 6,181440 1,00 6,053060 1,021207
16384 2097152 0,32 0 0’08 0 6,408630 1,0014435 6,417870 1,00 6,277530 1,022355
32768 4194304 0,32 0 0’08 0 6,450150 1,0031012 6,470160 1,00 6,305160 1,026169
65536 8388608 0,32 0 0’08 0 6,489510 1,0028473 6,508000 1,00 6,350340 1,024826
131072 16777216 1 1 1 1 6,512760 1,0022239 6,527250 1,00 6,376280 1,023678
243
Acceso a L2
Acceso a RAM
¿Por qué cuando el tamaño de ambos vectores es L2 (teóricamente ambos vectores residirían en L2), se da ya
una degradación del rendimiento?
1 𝑓𝑙𝑜𝑎𝑡 → 4 𝐵𝑦𝑡𝑒𝑠 𝑐𝑖𝑐𝑙𝑜𝑠
8 } 𝑥 = 1024 ∙ 1024 → ≅ 2 𝑒𝑛 𝑡𝑜𝑑𝑜𝑠.
Porque acceder a las cachés
𝑥 𝑓𝑙𝑜𝑎𝑡 → 𝑀𝐵𝑦𝑡𝑒𝑠 𝑒𝑙𝑒𝑚𝑒𝑛𝑡𝑜𝑠 superiores requiere Cbloq memoria
2
¿Presentan una misma tasa de fallos de acceso a caché los códigos escalar y vectorial? ¿Por qué?
Ciclos/Elto Miss rate El Vectorial PS ha empeorado mucho más que los otros
Código C 1’619 2’02 respecto a Escalar SS por no tener que acceder a L2.
Escalar SS 2’090 2’10
El algoritmo vectorial se degrada más cuanto más
Vectorial PS 0’560 2’05
grandes son los vectores.
¿Tienen distinta penalización temporal (Pmiss) por fallo de caché ambos códigos? ¿Por qué?
244
Miguel Ángel Cifredo
macifredo@gmail.com
P0. Ejercicio de VLIW. Hallar el Tiempo de ejecución en un procesador VLIW con las siguientes propiedades:
- Sean M operaciones.
- Sea un procesador VLIW puro con 8 operaciones por macro-instrucción.
- El compilador rellena con un 20% las macros de NOOP.
- La frecuencia del procesador es de 1’5 GHz.
- El CPI MEM es de 0’2 ciclos de bloqueo por instrucción.
Solución:
TiempoEjecución = NúmeroInstrucciones ∙ CPI ∙ Periodo
M ∙ 0'20 1
TiempoEjecución = (M + ) ∙ (1 + 0' 2) ∙ ( ' ) = (0'82 ∙ 10-9 ) ∙ M segundos
8 1 5 ∙ 109
P1. Hallar la relación de CPI entre Pentium III y el Pentium IV para SPEC INT2000, con estos datos.
Solución:
Tomemos por ejemplo una de las referencias del Pentium IV de 1’26 (1’7 GHz). Donde se nos indica que
existe una aceleración de 1’26, teniendo en cuenta que referenciamos la del Pentium III como 1, luego:
Por tanto,
1
' CPI.P.III ⁄1'7∙109 CPI.P.III CPI.P.IV
1 26 = ∙ → = 0'7411 → = 1'3492
CPI.P.IV 1⁄ CPI.P.IV CPI.P.III
1'0∙109
245
P2. Dado el siguiente código de alto nivel:
int i;
float x[100], y[100], z[100];
for (i=0; i<100; i++)
x[i] = y[i] / z[i];
Suponer que la división tarda 10 ciclos y el resto de operaciones sólo 1 ciclo. Suponer BTB que siempre acierta.
a) Escribir el código (para un RISC escalar). Por comodidad usar un único registro base, modificando los
desplazamientos según el array.
b) Aplicar el Algoritmo de Tomasulo para un RISC escalar y calcular el CPI para el estacionario. Estudiar
los casos con Unidades Funcionales DIV tanto segmentada como no segmentada.
c) Ídem para superescalar de grado 4 (donde siempre puede lanzar 4 instrucciones en la fase IF, es decir
hay una IFU que tiene siempre llena la ventana de IF)
Solución:
Recordemos que una variable de tipo float requiere de 4 Bytes para su almacenamiento, luego los tres vectores
indicados tendrán la siguiente disposición en memoria:
x[0] x[99] y[0] y[99] z[0] z[99]
Apartado a)
bucle: LF FY, (Rb)400
LF FZ, (Rb)800
DIVF FX, FY, FZ
SF (Rb)0, FX
- - - - - - - -
ADDI Rb, Rb, 4 Dirección de &x[i]
SLT Rc, Rb, RfinX
BENZ Rc, bucle Dirección de &x[100] ∄ = &y[0]
246
Miguel Ángel Cifredo
macifredo@gmail.com
Aparatado b)
Caso Escalar. Tendremos en cuenta el Algoritmo de Tomasulo para la regla WB → EX debido a las
dependencias reales.
1 LF FY, (Rb)400 IF IS EX WB FY
2 LF FZ, (Rb)800 IF IS EX WB FZ
3 DIVF FX, FY, FZ IF IS ○ D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 WB FX
4 SF (Rb)0, FX IF IS ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ EX WB
5 ADDI Rb, Rb, 4 IF IS EX WB Rb
6 SLT Rc, Rb, RfinX IF IS ○ EX WB Rc
7 BENZ Rc, bucle IF IS ○ ○ EX WB
LF FY, (Rb)400 IF IS EX WB FY
LF FZ, (Rb)800 IF IS EX WB FZ
SF (Rb)0, FX IF IS ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ EX WB
ADDI Rb, Rb, 4 IF IS EX WB Rb
LF FZ, (Rb)800 IF IS EX WB FZ
LF FY, (Rb)400 IF IS EX WB FY
LF FZ, (Rb)800 IF IS EX WB FZ
SF (Rb)0, FX IF IS ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ EX WB
ADDI Rb, Rb, 4 IF IS EX WB Rb
LF FZ, (Rb)800 IF IS EX WB FZ
10 ciclos ciclos
CPIestacionario = = 1' 43
7 instrucciones instrucción
247
Aparatado c) Superescalar grado 4.
LF FY, (Rb)400 IF IS EX WB FY
FZ
LF FZ, (Rb)800 IF IS EX WB
DIVF FX, FY, FZ IF IS ○ ○ D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 WB FX
SF (Rb)0, FX IF IS ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ EX WB
ADDI Rb, Rb, 4 IF IS EX WB Rb
LF FY, (Rb)400 IF IS EX WB FY
FZ
LF FZ, (Rb)800 IF IS EX WB
DIVF FX, FY, FZ IF IS ○ ○ D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 WB FX
SF (Rb)0, FX IF IS ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ EX WB
ADDI Rb, Rb, 4 IF IS EX WB Rb
1 ciclo
CPIIdeal =
4 instrucciones
10 ciclos
CPIEstacionario =
7 instrucciones
248
Miguel Ángel Cifredo
macifredo@gmail.com
P3. Traducir a un RISC típico usando el mínimo número de instrucciones posibles (un único registro base para
todos los vectores) el siguiente bucle.
float a[1024], b[1024];
for (i=2; i<1024; i++)
a[i] = (a[i] + a[i-2]) / b[i-1];
A continuación, reordenar, para que el mayor número de instrucciones posible esté detrás de DIVF.
Solución:
249
P4. Para una única iteración y pensando en el data flow limit (suponemos que se puede ejecutar en paralelo
todas las operaciones posibles; es decir, que no tengan dependencia), ¿cuántos ciclos tarda en almacenarse
a[i] del problema P3 desde que se empieza a leer el primer elemento de la memoria de esa iteración
(que sería a[i], a[i-2] o b[i])? Duración operaciones: ADDF: 4 ciclos, DIVF: 20 ciclos, LF: 3 ciclos, SF: 2 ciclos.
Solución:
# Ciclos
1 3 LF LF LF +
2 4 + COMP
F
3 20 ÷ BENZ
4 2 SF
1 iteración ciclos
CPIIdeal = = 0'138
29 ciclos instrucción
4 instrucciones
250
Miguel Ángel Cifredo
macifredo@gmail.com
P5. Suponiendo que CPI ideal = 1, CPI datos = 0’15 y CPI control = (Frecuencia de saltos) * 1’5 ciclos bloqueo/salto, hallar
el número de instrucciones del bucle SAXPY original (para un total de 1024*3 iteraciones) y del desenrollado
de 3 iteraciones. Hallar también la aceleración entre ambos bucles.
Solución:
Sabiendo que:
entonces tenemos:
251
P6. Hallar el ancho de banda de acceso al sistema de memoria (AB MEM) para los dos casos del problema
anterior, si la frecuencia = 2 GHz. Calcule también los GFLOPS.
Solución:
En el bucle SAXPY anterior contamos con 3 instrucciones de acceso a memoria: LF, LF y SF que requieren 4 Bytes
cada una de ellas. El ancho de banda de acceso a memoria requerido para cada caso es:
Sabiendo que:
entonces tenemos:
Sabiendo que los GFLOPS [Giga FLoat OPerators Per Second] referencian a los operadores: ADDF, SUBF, MULTF y
DIVF, por unidad de tiempo; y que su cálculo viene determinado por la siguiente expresión:
entonces tenemos:
252
Miguel Ángel Cifredo
macifredo@gmail.com
P7. Desenrollar usando 2 variables con mínimos parciales el bucle que calcule el mínimo elemento de un
vector. Tras el bucle el mínimo sería el mínimo de ambas variables.
Solución:
index_min0 = 0;
min0 = v[0];
253
254
Miguel Ángel Cifredo
macifredo@gmail.com
1. Objetivos y preparación.
OpenMP utiliza para generar el código paralelo directivas y funciones insertadas en el código C++ (o Fortran). En
C las directivas usan el formato:
#pragma omp directiva [comando[(parámetros)], [comando[(parámetros)]
Tanto las directivas como las funciones no son tenidas en cuenta si:
No habilitamos la compatibilidad openMP en el compilador. En Visual Studio la opción para habilitar está
compatibilidad se encuentra en: Propiedades de Configuración → C/C++ → Idioma → Compatibilidad
con openMP.
Y se inserta #include <omp.h>
2. Realización de la práctica.
EJERCICIO 1.
Abre la solución p6. Dentro de ella elige el proyecto ej1 para que sea el proyecto inicial y abre dentro de él, el
fichero principal_ej1.cpp, que contiene este código:
using namespace std;
#include <iostream>
#include <omp.h>
int main()
{
cout << "Hola, mundo!" << endl;
}
a) Lo más sencillo que se le puede pedir a OpenMP es que cree diferentes hilos y ejecute en todos ellos un mismo
código. Cambia la línea cout … en main() por esta otra:
int main()
{
#pragma omp parallel
cout << "Hola, mundo!" << endl;
}
Si el sistema en el que se está ejecutando este programa contiene dos o más procesadores (lógicos o físicos), se
habrán creado tantos hilos de ejecución paralela como procesadores existan, así que deberían aparecer tantos
mensajes “Hola, mundo!” como hilos se hayan creado. Si sólo aparece un mensaje, y nuestro sistema es
multicore, probablemente signifique que nuestro compilador no soporta directivas OpenMP, o bien que éstas no
han sido habilitadas (ver punto 1 de la práctica)
255
b) Se puede forzar un número de hilos determinado usando la siguiente función del API de OpenMP:
omp_set_num_threads (numhilos); Cambia el código para crear 4, 8 o 16 hilos.
Tanto en este caso como en el anterior, es más que posible que los mensajes en pantalla aparezcan “pisados”
unos con otros. Esto es porque las múltiples llamadas a cout se ejecutan a la vez. Lo solucionaremos en un
instante.
c) Habitualmente nos interesa que cada uno de los hilos que se crea sea consciente de que es un hilo dentro de
un conjunto de hilos que se ejecuta en paralelo. Para ello hay dos funciones del API de OpenMP:
Cambia el código para que el mensaje que se muestra por pantalla indique qué hilo se está ejecutando y cuántos
hay. Es decir, que aparezcan líneas con un texto tal como: Hola, mundo! Soy el hilo __ de __
d) Si dentro de los hilos hay una cierta sección del código que no es reentrante, como puede ser la escritura en
la consola, podemos usar cerrojos (locks) para crear secciones críticas, de forma que nos aseguremos de que
sólo un hilo está ejecutando esa región. OpenMP dispone de una API para manejar cerrojos de forma sencilla.
Consiste en los siguientes tipos y funciones:
- omp_lock_t : Para cada cerrojo que necesitemos, declararemos una variable que sea de este tipo. Por
ejemplo: omp_lock_t cerrojo;
- omp_init_lock (omp_lock_t *lock); Se llamará a esta función indicando la dirección de una variable de tipo
omp_lock_t para inicializar ese cerrojo. Los cerrojos se inicializan al estado “disponible”. Esta operación se
hace sólo una vez durante la vida útil del cerrojo y fuera de los hilos en paralelo.
- omp_set_lock (omp_lock_t *lock); Cuando uno de los hilos necesite entrar en una región crítica, llamará a
esta función. Si el cerrojo está disponible, la función termina sin esperar, y lo marca como no disponible. Si
el cerrojo no estuviera disponible, el hilo se queda esperando hasta que lo esté.
- omp_unset_lock (omp_lock_t *lock); Cuando un hilo termine su región crítica, llamará a esta función para
marcar el cerrojo como disponible. De esa forma otro hilo tendrá la oportunidad de entrar en la región crítica.
- omp_destroy_lock (omp_lock_t *lock); Se llamará a esta función la dirección de una variable de tipo
omp_lock_t para destruir e invalidar ese cerrojo. Una vez destruido no puede volver a usarse salvo que se
vuelva a inicializar. Esta operación se realizará fuera de los hilos en paralelo.
Cambia el código para que cree 16 hilos de ejecución, y usa un cerrojo para encerrar en una región crítica la
ejecución de la orden cout para imprimir en pantalla. Comprueba que aparecen los 16 mensajes
correspondientes, sin pisarse unos a otros.
e) Busca información sobre la directiva #pragma critical y úsala para simplificar el programa obtenido en d)
256
Miguel Ángel Cifredo
macifredo@gmail.com
Solución:
#include <iostream>
#include <omp.h>
int main(){
omp_set_num_threads(8);
omp_lock_t cerrojo;
omp_init_lock(&cerrojo);
#pragma omp parallel
{
int numThreads = omp_get_num_threads();
int mythreads = omp_get_thread_num();
omp_set_lock(&cerrojo);
cout << "Soy el hilo " << mythreads << " de " << numThreads << endl;
omp_unset_lock(&cerrojo);
}
omp_destroy_lock(&cerrojo);
}
EJERCICIO 2.
Ahora vamos a usar el proyecto ej2, en la misma Solución. Activa ej2 y márcalo como proyecto de inicio. Abre
dentro de él, el fichero principal_ej2.cpp . En él hay dos funciones, que usaremos, la primera para los apartados
a) b) y c); y la segunda para el apartado d). Comenta la que no vayas a usar, y descomenta la que sí.
a) Queremos paralelizar este bucle. Es decir, que varias iteraciones del mismo se ejecuten en diferentes hilos.
Para ello se ha añadido una directiva #pragma omp parallel de forma que el bloque que contiene el bucle
se ejecute concurrentemente en varios hilos. Queremos que se ejecute el mismo número de iteraciones,
pero cada una de ellas (o un grupo de ellas) en hilos diferentes. Prueba primero el programa comentando el
#pragma y comprueba que el número de iteraciones que produce es el esperado. Descomenta el #pragma
para que surta efecto ¿Qué resultado tenemos ahora? (ignora el que los resultados en pantalla se pisen, o
mejor aún, usa cerrojos o #pragma omp critical para evitar que esto se produzca).
Solución:
void part_a_b_c (int n)
{
#pragma omp parallel
{
int this_thread = omp_get_thread_num();
for (int i=0; i<n; i++)
{
#pragma omp critical
cout << "La iteracion " << i << " la gestiona el hilo " << this_thread << endl;
}
}
}
257
b) Usa las funciones omp_get_thread_num(); y omp_get_num_threads(); para particionar el bucle de la
siguiente forma: el bucle da N vueltas. Hay T hilos, donde T es el valor devuelto por
omp_get_num_threads();. Supondremos que el número de vueltas N es divisible entre el número de hilos T,
o sea, N % T = 0. Entonces, para cada hilo t (0 ≤ t < T), el valor inicial del bucle for será t*(N/T) y el valor final
(al cual no debe llegar el bucle), (t+1)*(N/T). Comprueba que el número de iteraciones es el correcto tanto
si el #pragma existe como si no.
Solución:
c) Usando #pragma omp parallel for simplifica el programa del apartado b).
Solución:
void part_a_b_c (int n)
{
omp_set_num_threads(4);
#pragma omp parallel for
for (int i=0;i<n;i++)
{
int this_thread = omp_get_thread_num();
#pragma omp critical
cout << "La iteracion " << i << " la gestiona el hilo " << this_thread << endl;
}
}
d) El bucle mostrado en esta función calcula la suma de los N primeros términos de 0+1+2+3+…+(N-1).
Aplicando los #pragma adecuados, distribuye el cálculo de la suma en distintos hilos. ¿Qué resultados se
observan? ¿Cómo se puede arreglar?
Solución:
258
Miguel Ángel Cifredo
macifredo@gmail.com
EJERCICIO 3.
Activa el proyecto ej3, márcalo como proyecto de inicio y dentro de él, abre el fichero principal_ej3.cpp. Este
programa cronometra el tiempo de ejecución de la función que se ponga en el sitio indicado. Se pide escribir
una serie de funciones usando las directivas de OpenMP, y cronometrarlas. Para cada función hay que
cronometrar su tiempo de ejecución dos veces: una comentando todos los #pragma omp que haya insertado en
la función, y otra con todas las directivas activadas. Con los datos obtenidos, rellene la tabla de la última página
de este boletín.
259
RESULTADOS
PRÁCTICA 6. OPENMP.
ALUMNO:
Para valores de N muy pequeños se da una mayor velocidad sin OpenMP ya que se reducen los fallos por caché
al sólo haber un procesador y una caché asociada a dicho procesador. Además no existe código de sincronización
de hilos. Para valores de N muy grandes, con OpenMP se empieza a mejorar y para N enormes la mejora es
visible pero no excesiva, ya que se producen los mismos fallos de cachés que se producían para un sólo
procesador. Por lo general, en las versiones con OpenMP se producen los fallos de caché de la versión sin
OpenMP a partir de un CHUNK asignado grande; es decir, cuando a cada procesador se le asigna gran tamaño
de vector; de forma que la misma línea de caché sólo estará en un hilo a la vez y no se produzca ningún "tránsito"
de líneas de caché por "husmeo", que es lo que se produce en las ejecuciones con OpenMP en tamaños
pequeños (la misma línea se usa en cachés distintas y hay que sincronizar dichos datos).
260
Miguel Ángel Cifredo
macifredo@gmail.com
Para una misma máquina y un mismo valor de N, ¿es el speedup conseguido el mismo para los tres algoritmos?
¿A qué se puede achacar las diferencias de speedup entre cada uno?
Respuesta: No. La principal diferencia radica en la paralelización de cada uno. El primero (SAXPY) va a ser
totalmente vectorizable pero se va a escribir con dependencias en la misma i que se lee. En el segundo y en el
tercero existe una variable que debe ser "compartida" y ha de respetarse de alguna forma. En el producto escalar
se forma parte de la propia operación, pero hay una parte vectorizada totalmente [los vectores x e y] y en la
cuenta de ZEROS se comparte la variable contador mediante el reduction y la parte vectorizada sólo incluye un
vector. En el algoritmo de ZEROS sólo se usa la caché para un vector, por ello sus "fallos de caché" se darán más
tarde. De ahí su mejor rendimiento. La mejora es menor en el SAXPY motivado por el triple acceso a memoria
(lectura de x[i], lectura de y[i] y escritura en y[i]), más la sincronización que ello debe conllevar.
261
262
Miguel Ángel Cifredo
macifredo@gmail.com
Preparación de la práctica.
Objetivos.
En esta práctica se pretende que el alumno conozca la metodología a emplear para desarrollar aplicaciones
paralelas para computadores paralelos de memoria distribuida y se familiarice con las particularidades que
comporta este tipo de plataforma distribuida para la ejecución de los programas, su depuración y su rendimiento.
Metodología.
Se estudiarán los fundamentos de la programación utilizando el paradigma más empleado hoy en día en este
tipo de plataforma: la biblioteca de funciones MPI (Message-Passing Interface), junto con el lenguaje de
programación C.
Se realizarán programas para un PC con sistema operativo Linux, usando el compilador gcc junto con la biblioteca
de funciones MPI. El alumno empleará un único PC para aprender los mecanismos de desarrollo y ejecución de
programas paralelos que se emplean en plataformas distribuidas tales como un clúster. La ejecución en un PC de
la aplicación con distinto número de procesos podrá beneficiarse de los múltiples núcleos del procesador.
Estudio Previo.
Estudiar los contenidos relacionados con la programación paralela de multiprocesadores con memoria
distribuida impartidos en teoría.
Ambos contenidos podrán ser evaluados en un test de conocimientos previos al comienzo de esta práctica.
Realización en Laboratorio.
Como documentación básica para trabajar usando el sistema operativo Linux, el alumno puede emplear el
documento “Comandos UNIX” escrito por el Centro de Cálculo de la E.T.S.I.I., que se encuentra en la URL:
http://www.informatica.us.es/index.php/en/servicios/centro-de-calculo/comandos-unix
Tras iniciar sesión en Linux, abrir una consola de terminal y crear un directorio de trabajo para esta práctica,
llamado practica-mpi. Crear o editar los archivos de código fuente empleando cualquier editor de texto de Linux
(por ejemplo “gedit” o “nano”). Los comandos para compilar y ejecutar programas que usan MPI son los
siguientes:
El modificador opcional -lm hace que el compilador enlace el programa con las librerías matemáticas.
Donde X es un número que indica el número de procesos que debe lanzar la aplicación.
263
EJERCICIO 1
Investigar si se puede lanzar un número de procesos que sea mayor que el número de núcleos del procesador
utilizado y qué ocurre en ese caso.
EJERCICIO 2:
Crear un archivo de código fuente C conteniendo el programa “saludos.c” (presentado en las transparencias
incluidas en el material de esta práctica), compilarlo y ejecutarlo sobre distinto número de procesadores. ¿Qué
ocurre al ejecutar el programa sólo para un procesador? ¿Por qué?
EJERCICIO 3:
Partiendo del programa “saludos.c”, crear un programa llamado “saludos_en_anillo.c” en el cual el proceso i
le envía un saludo al proceso (i+1)%p (donde el operador % es el módulo matemático o resto de la división
entera). Prestar atención a cómo el proceso i calcula de quién debe recibir un mensaje. Contestar además:
a) El proceso i, ¿debe enviar primero su mensaje al proceso i+1 y después recibir el mensaje del proceso i-
1, o a la inversa (primero recibir y luego enviar)? ¿Importa el orden?
b) ¿Qué ocurre cuando se ejecuta el programa sobre un único procesador (con un único proceso)?
EJERCICIO 4:
- Estudiar el funcionamiento del programa. Para ello, cambiar inicialmente el número de elementos de los
vectores a un valor muy pequeño (por ejemplo 6). Ejecutar el programa variando el número de procesos
lanzados desde 1 hasta 3, comprobar que los resultados son correctos y estudiar el código para entender
cómo se realiza el cálculo.
- Modificar el programa añadiéndole una medición del tiempo de ejecución, usando para ello la función
MPI_Wtime.
- Medir el tiempo de ejecución del programa al variar el número de procesos con el que es lanzado desde
1 hasta 6 procesos.
264
Miguel Ángel Cifredo
macifredo@gmail.com
Programa prod_escalar_mpi.c:
/* prod_escalar_mpi.c
* CALCULO DEL PRODUCTO ESCALAR DE DOS VECTORES
* ENTRADA: NINGUNA.
* SALIDA : PRODUCTO ESCALAR
*/
#include <stdio.h>
#include <math.h>
#include <mpi.h>
/* FUNCION QUE CALCULA EL PRODUCTO ESCALAR LOCAL (DE UN TROZO DE LOS VECTORES) */
float prod_escalar_serie(float a[], // ENTRADA
float b[], // ENTRADA
float n // ENTRADA: NUMERO DE ELEMENTOS
) {
int i;
float suma = 0.0;
for (i = 0; i < n; i++)
suma = suma + a[i] * b[i];
return suma;
}
265
TABLA DE RESULTADOS
ALUMNO:
Ejercicio 1:
if (mi_rango == 0)
printf ("Soy el proceso %i de %i : Hola mundo\n", mi_rango, tamano);
else
printf ("Soy el proceso %d de %d, en %s.\n", mi_rango, tamano, host);
¿Qué ocurre en ese caso? El sistema operativo conmuta los diferentes procesos entre los distintos núcleos.
266
Miguel Ángel Cifredo
macifredo@gmail.com
Ejercicio 2:
if (mi_rango != 0)
{
/* Crea mensaje */
sprintf(mensaje, "Saludos desde el proceso %d!", mi_rango);
destino = 0;
/* Usa strlen+1 para que '\0' pueda ser transmitido */
MPI_Send(mensaje, strlen(mensaje)+1, MPI_CHAR, destino, etiqueta, MPI_COMM_WORLD);
}
else
{
// mi_rango == 0
for (fuente=1; fuente < p; fuente++)
{
MPI_Recv(mensaje, 100, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &status);
printf("%s\n", mensaje);
}
}
MPI_Finalize(); // Cierra el universo MPI
} // main
¿Qué ocurre al ejecutar el programa "saludos" sólo para un procesador? ¿Por qué?
Al ejecutarse para un solo procesador el código que se ejecuta es el envío de un mensaje y no hay
quien lo reciba. Sin embargo, si se ejecutase para más de un procesador, la salida por pantalla
sería diferente, el código que se ejecuta es mostrar un saludo desde los diferentes porcesos que
reciben un mensaje desde ellos.
267
Ejercicio 3:
/* Crea mensaje */
sprintf(mensaje, "Saludos desde el proceso %d!", mi_rango);
destino = (mi_rango+1)%p;
/* Usa strlen+1 para que '\0' pueda ser transmitido */
MPI_Send(mensaje, strlen(mensaje)+1, MPI_CHAR, destino, etiqueta, MPI_COMM_WORLD);
fuente = (mi_rango-1+p)%p;
MPI_Recv(mensaje, 100, MPI_CHAR, fuente, etiqueta, MPI_COMM_WORLD, &status);
printf("%s\n", mensaje);
a) El proceso i, ¿debe enviar primero su mensaje al proceso i+1 y después recibir el mensaje del proceso i-1, o
a la inversa (primero recibir y luego enviar)? ¿Importa el orden?
Primero debe enviar el mensaje y luego esperar a recibirlo. El orden sí es importante, ya que no se podría
estar esperando un mensaje que aún no ha sido enviado.
b) ¿Qué ocurre cuando se ejecuta el programa sobre un único procesador (con un único proceso)?
268
Miguel Ángel Cifredo
macifredo@gmail.com
Ejercicio 4:
01 /* prod_escalar_mpi.c
02 * CALCULO DEL PRODUCTO ESCALAR DE DOS VECTORES
03 *
04 * ENTRADA: NINGUNA.
05 * SALIDA : PRODUCTO ESCALAR
06 */
07 #include <stdio.h>
08 #include <math.h>
09 #include <mpi.h>
10 #define ELEMENTOS (2*3) // vale 6
11 /* ¡ATENCION: EL Nº DE ELEMENTOS DEBE SER DIVISIBLE POR EL Nº DE PROCESOS (p)! */
12 float x[ELEMENTOS], y[ELEMENTOS]; // VECTORES (VARIABLES GLOBALES)
13 /* FUNCION QUE CALCULA EL PRODUCTO ESCALAR LOCAL (DE UN TROZO DE LOS VECTORES) */
14 float prod_escalar_serie(
15 float a[], // ENTRADA
16 float b[], // ENTRADA
17 float n // ENTRADA: NUMERO DE ELEMENTOS
18 );
19 main(int argc, char** argv) {
20 int mi_rango; // RANGO DE MI PROCESO
21 int p; // NUMERO DE PROCESOS
22 int n = ELEMENTOS; // NUMERO DE ELEMENTOS DE LOS VECTORES
23 int n_local; // NUMERO DE ELEMENTOS DE CADA FRAGMENTO
24 int inicio_vector_local; // INDICE DE INICIO DE CADA FRAGMENTO
25 float suma_local; // PRODUCTO ESCALAR SOBRE MI INTERVALO
26 float suma_total; // PRODUCTO ESCALAR TOTAL
27 int fuente; // PROCESO QUE ENVIA RESULTADO DE SUMA
28 int dest = 0; // DESTINATARIO: TODOS LOS MENSAJES VAN A 0
29 int etiqueta = 0;
30 int i;
31 double ini, fin;
32 MPI_Status status;
33 MPI_Init(&argc, &argv); // INICIALIZA MPI
34 /* INICIALIZA LOS VECTORES */
35 ini = MPI_Wtime();
36 for (i = 0; i < n; i++)
37 x[i] = y[i] = i % 5;
38 MPI_Comm_rank(MPI_COMM_WORLD, &mi_rango); // AVERIGUA EL RANGO DE MI PROCESO
39 MPI_Comm_size(MPI_COMM_WORLD, &p); // AVERIGUA CUANTOS PROCESOS HAY
40 n_local = n / p;
41 inicio_vector_local = mi_rango * n_local;
42 suma_local = prod_escalar_serie(&x[inicio_vector_local], &y[inicio_vector_local], n_local);
43 printf("MI RANGO = %d , SUMA LOCAL = %f\n", mi_rango, suma_local);
44 /* SUMA LAS CONTRIBUCIONES CALCULADAS POR CADA PROCESO */
45 if (mi_rango == 0) {
46 suma_total = suma_local;
47 for (fuente = 1; fuente < p; fuente++) {
48 MPI_Recv(&suma_local, 1, MPI_FLOAT, fuente, etiqueta, MPI_COMM_WORLD, &status);
49 suma_total = suma_total + suma_local;
50 }
51 fin = MPI_Wtime();
52 }
53 else {
54 MPI_Send(&suma_local, 1, MPI_FLOAT, dest, etiqueta, MPI_COMM_WORLD);
55 }
56 /* MUESTRA EL RESULTADO POR PANTALLA */
57 if (mi_rango == 0) {
58 printf("PRODUCTO ESCALAR USANDO p=%d TROZOS DE LOS VECTORES X E Y = %f\n", p, suma_total);
59 printf("Resultado tiempo %f\n", fin - ini);
60 }
61 MPI_Finalize(); // CIERRA EL UNIVERSO MPI */
62 } /* MAIN */
63 /* FUNCION QUE CALCULA EL PRODUCTO ESCALAR LOCAL (DE UN TROZO DE LOS VECTORES) */
64 float prod_escalar_serie(
65 float a[], // ENTRADA
66 float b[], // ENTRADA
67 float n /* ENTRADA: NUMERO DE ELEMENTOS */){
68 int i;
69 float suma = 0.0;
70 for (i = 0; i < n; i++)
71 suma = suma + a[i] * b[i];
72 return suma;
73 } /* PROD_ESCALAR_SERIE */
269
a) Funcionamiento del programa con número de elementos = 6 (Producto escalar = 30):
Tiempo de ejecución del programa (con nº elementos = 2*2*2*2*3*3*5*7*11*13 = 720720) al variar el número
de procesos (p) con el que es lanzado desde 1 hasta 6 procesos:
Sí son lógicos, puesto que aunque la tarea de cómputo se reparte entre cada vez más
procesadores y por tanto el tiempo final para completarla disminuye, se producen tiempos de
penalización por fallos de acceso a caché que requiere acceso a memoria por tratarse de un
vector muy grande; de ahí el aumento del tiempo final.
Por otro lado, si aumentamos un número de procesadores y es mayor que del que disponemos
físicamente, se requiere tiempo de conmutación de procesos entre ellos; lo cual redunda
nuevamente en el aumento del tiempo requerido.
Entre los principales tiempos tenemos: tiempo de procesado, de acceso a memoria por fallo de
caché, de conmutación entre procesos y de mensajería entre procesos para su sincronización.
270
Miguel Ángel Cifredo
macifredo@gmail.com
Objetivos.
En esta práctica pretendemos ver cómo podemos usar OpenMP para paralelizar la ejecución de distintas
funciones, haciendo que cada hilo ejecute una función distinta.
Realización de la práctica.
Visual Studio 2010, o cualquier otro compilador compatible con OpenMP C++
Última versión de las librerías OpenCV: http://opencv.willowgarage.com/wiki/
Vamos a comparar los tiempos de ejecución necesarios para realizar las siguientes tareas. Partiendo de cuatro
imágenes en color y con resolución 640x480 (las imágenes pueden ser las mismas siempre que tengan nombres
distintos). Vamos a realizar el siguiente proceso:
1. Leemos las cuatro imágenes del disco (si es la misma podemos leerla una única vez, y obtener cuatro copias
de la imagen en cuatro variables tipo Mat distintas).
271
Tened presente, que ni la lectura de datos, ni la muestra de resultados está incluida en la medida de los tiempos.
Si decidimos usar la misma imagen con cuatro copias, a cada copia hay que aplicarle los tres pasos
correspondientes.
Se pide:
2. Realizar la versión paralela del algoritmo para 4 hilos midiendo los tiempos.
3. Comparar los tiempos obtenidos, explicando en detalle los resultados obtenidos y analizando los motivos de
las pérdidas en prestaciones de la versión paralela.
4. Suponiendo que tenemos una única imagen que procesar (y no cuatro como en el caso del apartado 2), y
que a dicha imagen le pretendemos aplicar los siguientes filtros en orden:
1. Conversión de color a escala de grises.
2. Cualquier función de Threshold.
3. Ecualización del histograma.
4. Aplicarle el operador de Sobel.
5. Y si tuviéramos un flujo de imágenes (por ejemplo, un vídeo), y queremos aplicarle el proceso del apartado
4 a cada frame. ¿Cómo lo paralelizaríais? Dar una respuesta razonada desde un punto de vista teórico (no es
necesario que lo implementéis, pero podéis intentarlo).
Con todo lo anterior, trabajando en grupos de 2 alumnos, se deberá redactar una presentación (estilo Power
Point u OpenOffice Impress) de aproximadamente 8 minutos de duración, dividida en 2 partes aproximadamente
iguales.
La presentación debe incluir las respuestas a las preguntas del enunciado, indicando la manera en que se ha
escrito el código, los resultados de tiempo, su interpretación, así como cualquier descubrimiento interesante.
En la siguiente sesión, los alumnos realizarán la presentación de su trabajo (cada uno la mitad) y a continuación
ambos contestarán las preguntas del profesor y de los compañeros. Por tanto, todos los alumnos deben
prepararse la presentación entera
272
Miguel Ángel Cifredo
macifredo@gmail.com
Objetivos.
En esta práctica se pretende que el alumno conozca la metodología a emplear para desarrollar aplicaciones
paralelas para computadores paralelos de memoria distribuida y se familiarice con las particularidades que
comporta este tipo de plataforma distribuida para la ejecución de los programas, su depuración y su rendimiento.
Metodología.
Se estudiarán los fundamentos de la programación utilizando el paradigma más empleado hoy en día en este
tipo de plataforma: la biblioteca de funciones MPI (Message-Passing Interface), junto con el lenguaje de
programación C.
Se realizarán programas para un PC con sistema operativo Linux, usando el compilador gcc junto con la biblioteca
de funciones MPI. Primeramente el alumno empleará un único PC para aprender los mecanismos de desarrollo
y ejecución de programas paralelos que se emplean en plataformas distribuidas. En ese caso la ejecución en un
PC de la aplicación con distinto número de procesos podrá beneficiarse de los múltiples núcleos del procesador.
Como segundo paso, el alumno lanzará la ejecución de su aplicación paralela en varios PCs trabajando como si
fueran distintos nodos de un clúster y analizará su rendimiento.
Como documentación básica para trabajar usando el sistema operativo Linux, el alumno puede emplear el
documento “Comandos UNIX” escrito por el Centro de Cálculo de la E.T.S.I.I., que se encuentra en la URL:
http://www.informatica.us.es/index.php/en/servicios/centro-de-calculo/comandos-unix
Tras iniciar sesión en linux, abrir una consola de terminal y crear un directorio de trabajo para esta práctica,
llamado practica-mpi. Crear o editar los archivos de código fuente empleando cualquier editor de texto de Linux
(por ejemplo "vi", “nano” o “gedit”). Los comandos para compilar programas y ejecutar programas que usan MPI
son los siguientes:
El modificador opcional -lm hace que el compilador enlace el programa con las librerías matemáticas.
Donde X es un número que indica el número de procesos que debe lanzar la aplicación.
273
Relalización de la práctica
Parte 1:
𝑆 = ∫ 𝑓(𝑥)𝑑𝑥
𝑎
El área del trapecio i-ésimo se puede calcular fácilmente como la suma de las
áreas de un rectángulo y un triángulo:
1 1
𝑆(𝑡𝑟𝑎𝑝𝑒𝑐𝑖𝑜𝑖 ) = ℎ ∙ 𝑓(𝑥𝑖+1 ) + ℎ ∙ [𝑓(𝑥𝑖 ) − 𝑓(𝑥𝑖+1 )] = ℎ ∙ [𝑓(𝑥𝑖 ) + 𝑓(𝑥𝑖+1 )]
2 2
1 1 1
𝑆 ≈ ∑ 𝑆(𝑡𝑟𝑎𝑝𝑒𝑐𝑖𝑜𝑖 ) = ℎ ∙ [𝑓(𝑥0 ) + 𝑓(𝑥1 )] + ℎ ∙ [𝑓(𝑥1 ) + 𝑓(𝑥1 )] + ⋯ + ℎ ∙ [𝑓(𝑥𝑛−1 ) − 𝑓(𝑥𝑛 )]
2 2 2
𝑖
1
= ℎ ∙ [𝑓(𝑥0 ) + 2 ∙ 𝑓(𝑥1 ) + 2 ∙ 𝑓(𝑥2 ) + ⋯ + 𝑓(𝑥𝑛 )]
2
𝑓(𝑥0 ) 𝑓(𝑥𝑛 )
=[ + + 𝑓 (𝑥1 ) + 𝑓 (𝑥2 ) + ⋯ + +𝑓 (𝑥𝑛−1 )] ∙ ℎ
2 2
Esta es la fórmula de los trapecios empleada en el programa anterior, teniendo en cuenta que la base de todos
(𝑏−𝑎)
los trapecios tiene la misma longitud, que es: ℎ =
𝑛
274
Miguel Ángel Cifredo
macifredo@gmail.com
/* integracion_trapecios_no_paralelo.c
* INTEGRACION NUMERICA POR EL METODO DE LOS TRAPECIOS
* ENTRADA: NINGUNA.
* SALIDA : ESTIMACION DE LA INTEGRAL DESDE a HASTA b DE f(x)
* USANDO EL METODO DE LOS TRAPECIOS CON n TRAPECIOS */
#include <stdio.h>
#include <math.h>
main(int argc, char** argv) {
float integral; // RESULTADO DE LA INTEGRAL
float a = -1.0; // EXTREMO IZQUIERDO
float b = 1.0; // EXTREMO DERECHO
int n = 1000; // NUMERO DE TRAPECIOS
float h; // LONGITUD DE LA BASE DEL TRAPECIO
float x;
float i;
float f(float x); /* FUNCION QUE ESTAMOS INTEGRANDO */
h = (b-a)/n;
integral = (f(a) + f(b))/2.0;
x = a;
for (i = 1; i <= n-1; i++) {
x = x + h;
integral = integral + f(x);
}
integral = integral*h;
printf("ESTIMACION USANDO n=%d TRAPECIOS,\n", n);
printf("DE LA INTEGRAL DESDE %f HASTA %f = %f\n\n", a, b, integral);
printf("ESTIMACION DE PI: %f\n", 2 * integral);
} /* main */
/* FUNCION QUE ESTAMOS INTEGRANDO */
/* CALCULA f(x) Y DEVUELVE SU VALOR */
float f(float x) {
float return_val;
return_val = sqrt(1 - x * x);
return return_val;
} /* f */
A partir de este programa, se pide que realice una versión paralela del mismo utilizando MPI. Para paralelizar el
programa, se deben distribuir los datos entre los diferentes procesadores. En este caso, los datos son los
trapecios dentro del intervalo [a, b]. La idea es que cada proceso (menos el de rango 0) estime el valor de la
integral sobre un subintervalo y envíe el resultado al proceso de rango 0, que se encargará de sumar todos los
resultados (y también computará el área del primer subintervalo).
Si tenemos en total n trapecios y p procesos (suponemos que n es mayor que p y además, por simplificar, que n
𝑛
es divisible por p), asignaremos 𝑝 trapecios a cada proceso. Así, cada proceso tendrá que aplicar la fórmula de
𝑛
los trapecios sobre un trozo del área total, de longitud ℎ ∙ 𝑝 . El proceso de rango 0 calculará la suma de los
𝑛
trapecios que van desde x = 𝑎 hasta x= 𝑎 + (ℎ ∙ 𝑝) . El proceso de rango 1, la suma de los trapecios que van
𝑛 𝑛
desde x = 𝑎 + (ℎ ∙ 𝑝) hasta x = 𝑎 + 2 ∙ (ℎ ∙ 𝑝) . Y así sucesivamente.
𝑛
Un proceso de rango genérico i calcularía la suma de los trapecios que van desde x = 𝑎 + 𝑖 ∙ (ℎ ∙ 𝑝) hasta
𝑛 𝑛
x = 𝑎 + (𝑖 + 1) ∙ (ℎ ∙ 𝑝) . Así pues, para este proceso su valor “a_local” sería 𝑎 + 𝑖 ∙ (ℎ ∙ 𝑝) y su valor “b_local”
𝑛 𝑛
sería 𝑎 + (𝑖 + 1) ∙ (ℎ ∙ ) . Asimismo, su número de trapecios a sumar, “n_local”, sería .
𝑝 𝑝
1. Cada proceso calcula cuál es su intervalo de integración, en función del valor de su rango i.
2. Cada proceso estima la integral de f(x) sobre su intervalo usando la regla de los trapecios.
3. Cada proceso distinto del 0 envía su integral al proceso 0.
4. El proceso 0 suma los valores recibidos de los procesos individuales y muestra el resultado final.
275
Probar el programa obteniendo una estimación del valor de π a partir del área de medio círculo. Es decir, calcular
el área de medio círculo de radio unidad. La función a integrar se obtiene teniendo en cuenta que la ecuación de
un círculo centrado en el origen es 𝑥 2 + 𝑦 2 = 𝑟 2 . Despejando y, resulta la función a integrar, que es:
2 𝜋
𝑦 = √𝑟 2 − 𝑥 2 . Sabemos que dicho área debe ser igual a 2 (ya que el área de un círculo de radio unidad es
𝜋 ∙ 𝑟 2 = 𝜋 . Por lo tanto, multiplicando por 2 el valor calculado por nuestro programa obtendremos una
estimación de π.
// Inicializa MPI
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &mi_rango);
MPI_Comm_size(MPI_COMM_WORLD, &p);
tiempo = MPI_Wtime();
tamanoTrozo = 2;
double trozos [p*2];
if(mi_rango == 0) {
sub_integral = 0.0;
for(iterador=0; iterador<p; iterador++) {
sub_integral += sub_integrales[iterador];
}
tiempo = MPI_Wtime() - tiempo;
printf("Algoritmo calculado en: %lf\n", tiempo);
}
MPI_Finalize();
}
276
Miguel Ángel Cifredo
macifredo@gmail.com
MPI_Comm_rank(MPI_COMM_WORLD, &mi_rango);
return_val = (f(a) + f(b))/2.0;
MPI_Comm_size(MPI_COMM_WORLD, &p);
for(i=1;i<n/p;i++) {
return_val += f(a+(i*h));
contador++;
}
return_val = return_val*h;
return return_val;
}
for(i=0;i<p*2;i=i+2) {
if(i==0)
trozos[i] = tmp;
else
trozos[i] = trozos[i-1];
trozos[i+1] = tmp + distancia;
tmp = trozos[i+1];
}
PROCESOS TIEMPO
1 0,1752950
2 0,0913940
3 0,0641140
4 0,0501350
5 0,0443300
6 0,0368210
7 0,0323190
277
Solución 2: Utilizando Paso de mensajes
#include <stdio.h>
#include <math.h>
#include <mpi.h>
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &mi_rango);
MPI_Comm_size(MPI_COMM_WORLD, &p);
tiempo = MPI_Wtime();
h = (b-a)/n;
distancia = (b-a)/p;
inicio_vector_local = a + mi_rango * distancia;
final_vector_local = a + (mi_rango+1) * distancia;
sub_integral = f2(inicio_vector_local, final_vector_local, h, n);
if (mi_rango == 0) {
sub_total = sub_integral;
for (fuente = 1; fuente < p; fuente++) {
MPI_Recv(&sub_integral, 1, MPI_DOUBLE, fuente, etiqueta, MPI_COMM_WORLD, &status);
sub_total += sub_integral;
}
printf("ESTIMACION USANDO n=%d TRAPECIOS,\n", n);
printf("\nESTIMACION DE PI: %lf\n", 2 * sub_total);
printf("%lf\n", (MPI_Wtime() - tiempo));
} else {
MPI_Send(&sub_integral, 1, MPI_DOUBLE, 0, etiqueta, MPI_COMM_WORLD);
}
MPI_Finalize();
}
278
Miguel Ángel Cifredo
macifredo@gmail.com
Parte 2:
Modificar el programa realizado en el ejercicio anterior añadiéndole una medición del tiempo de ejecución,
usando la función MPI_Wtime.
Modificar también los datos de ejecución en el código fuente de modo que se evalúe:
El resultado del área debe ser aproximadamente 1.0 x 106 u2 , aunque los siguientes dígitos variarán debido a los
errores de truncamiento y a que se ha usado una precisión pequeña (variables de tipo float).
Con estos datos, ejecutar el programa y anotar el tiempo de ejecución obtenido para un número de procesos
desde 1 hasta 7. Representar una gráfica del tiempo de ejecución respecto al número de procesos.
La aceleración de un programa paralelo se define como el cociente entre el tiempo de ejecución del programa
para un sólo procesador y el tiempo de ejecución con n procesadores. Con los datos anteriores, calcular la
aceleración del programa para n desde 2 hasta 7 procesos. Representar una gráfica de la aceleración respecto al
número de procesos.
Solución:
Tiempo de
ejecución
1 0,442766
Número de procesos
2 0,227557
3 0,226073
4 0,227226
5 0,212441
6 0,208168
7 0,198029
279
Solución: Utilizando Reducción.
#include <stdio.h>
#include <math.h>
#include <mpi.h>
/* VARIABLES ADICIONALES */
double a_local, b_local; // EXTREMOS LOCALES DE CADA PROCESO
int num_proceso; // NUMERO DE PROCESO (0...p)
int procesos; // NÚMERO TOTAL DE PROCESOS
int num_trap_x_proc; // NUM. TRAPECIOS POR PROCESO
double Tini, Tfin; // MOMENTO DE INICIO Y FINAL DE EJECUCION
/* VARIABLE LOCALES */
h = (b_global - a_global) / trapecios;
num_trap_x_proc = trapecios / procesos;
a_local = a_global + h * num_proceso * num_trap_x_proc;
if (num_proceso == procesos - 1) {
b_local = b_global;
} else {
b_local = a_global + h * num_trap_x_proc * (num_proceso + 1);
}
Tini = MPI_Wtime();
/* CÁLCULO DE LA INTEGRAL */
sub_integral = (funcion(a_local) + funcion(b_local)) / 2.0;
x = a_local;
for (i = 0; i <= num_trap_x_proc - 2; i++) {
x = x + h;
sub_integral = sub_integral + funcion(x);
}
sub_integral = sub_integral * h;
Tfin = MPI_Wtime();
MPI_Finalize();
}
280
Miguel Ángel Cifredo
macifredo@gmail.com
Parte 3:
Trabajando en grupos de 2 alumnos, lanzar la aplicación paralela desarrollada en la parte 2 sobre tres PCs del
aula. Los alumnos deberán configurar los ordenadores para ello, teniendo en cuenta las indicaciones que se
ofrecen a continuación e investigando en Internet para buscar información adicional.
Para que MPI sea capaz de lanzar procesos en otras máquinas, la cuenta debe estar configurada de modo que
MPI se pueda conectar remotamente en ellas, sin que se le pida contraseña, mediante una conexión segura
usando el protocolo de red criptográfico SSH (Secure Shell). Para ello hay que habilitar el acceso remoto sin
contraseña desde el nodo que lanza la aplicación paralela a todos los nodos adicionales sobre los que deba
ejecutarse.
Por ejemplo, si se quiere lanzar la aplicación paralela desde el nodo pc-14-145, corriendo además de en éste en
los nodos pc-14-146 y pc-14-147, el procedimiento es:
Activar el demonio SSH en todos los nodos adicionales donde se vaya a correr el programa. Ejecutar para
ello el siguiente comando en cada uno de ellos:
sudo service ssh start
Generar la pareja de claves SSH pública y privada del equipo que lanza el programa (tipo DSA). Para ello,
desde el nodo pc-14-145, ejecutar:
ssh-keygen -t dsa
Copiar e instalar la clave pública en todos los ordenadores adicionales donde se vaya a correr el
programa. Desde el nodo pc-14-145, hacer:
ssh-copy-id practica@pc-14-146
ssh-copy-id practica@pc-14-147
Desde el equipo que lanzará el programa, copiar el directorio completo de trabajo donde esté incluido
el ejecutable a todos los ordenadores adicionales donde se vaya a correr el programa. Por ejemplo, si
dicho directorio es /home/practica/mpi-001 habrá que situarse en /home/practica y ejecutar:
scp -r ./mpi-001 practica@pc-14-146:/home/practica
scp -r ./mpi-001 practica@pc-14-147:/home/practica
Ejecutar la aplicación paralela indicando explícitamente el nombre (o dirección IP) de todos los nodos
sobre los que se vaya a ejecutar en paralelo. Desde el nodo pc-14-145:
mpirun -np 7 -host pc-14-145, pc-14-146, pc-14-147 /home/practica/mpi001/ejercicio2
Con los mismos datos de ejecución indicados para el apartado 2, ejecutar el programa y anotar el tiempo de
ejecución obtenido para un número de procesos desde 1 hasta 7. Representar una gráfica del tiempo de
ejecución y otra de la aceleración respecto al número de procesos.
Interpretar ambas gráficas, comparándolas con las obtenidas en la parte 2. Razonar a qué se deben las diferencias
encontradas. ¿Se ha conseguido sacar rendimiento a la posibilidad de conectar entre sí varios PCs, tal como en
un clúster, para ejecutar aplicaciones paralelas?
281
Solución:
Tiempo de ejecución
Aceleración
Sin distribuir Con distribución
1 0,442766 0,1453270912 1,000000000
2 0,227557 0,0813248158 1,786995639
Número de
procesos
3 0,226073 0,0580251217 2,504554699
4 0,227226 0,0472111702 3,078235311
5 0,212441 0,0380239487 3,821988409
6 0,208168 0,0332419872 4,371793128
7 0,198029 0,0286531448 5,071942093
282
Miguel Ángel Cifredo
macifredo@gmail.com
Objetivos.
Configuración de las máquinas virtuales del clúster sobre el que se desarrollará la práctica.
Configuración de servicio FTP.
Configuración de un balanceador de carga basado en Linux Virtual Server (LVS).
Realización de pruebas básicas para comprobar el correcto funcionamiento del servicio.
Introducción.
El objetivo de la práctica es trabajar sobre un clúster de máquinas GNU/Linux (en este caso máquinas virtuales),
que en su conjunto prestan un servicio de transferencia de ficheros FTP. Al disponerse de varias máquinas, se
dotará al servicio FTP de redundancia, la cual constituye una de las formas que tenemos a nuestro alcance para
dotar de continuidad y disponibilidad a este servicio.
La configuración propuesta en esta práctica está basada en el uso de Linux Virtual Server (LVS), un proyecto open
source cuyo objetivo es facilitar la prestación de servicios en alto rendimiento y disponibilidad mediante el uso
de clústeres. Los componentes de LVS dependen del framework Linux Netfilter, especializada en el manejo de
paquetes de red.
El clúster propuesto está compuesto por una serie de máquinas que proporcionan servicio de FTP y una máquina
que hace las labores de balanceador de carga (Load Balancer, LB), actuando como receptor de las peticiones FTP
y siguiendo un algoritmo de balanceo, las redirigirá hacia los distintos servidores FTP del clúster. De este modo
se reparte la carga entre os servidores y en caso de que uno de ellos falle, el balanceador puede detectarlo y
enviar las peticiones a cualquiera de las otras máquinas que aún se mantienen prestando servicio.
Máquinas.
Las máquinas del clúster (servidores FTP y balanceador) serán máquinas virtuales ejecutándose sobre VMware
Player, el cuál proporciona tecnología de virtualización de hardware mediante la cual una máquina física
(máquina Host) puede albergar varias máquinas virtuales (máquinas Guest). Estas máquinas virtuales son
elementos software que simulan el comportamiento de un ordenador físico y pueden ejecutar programas como
si de una máquina real se tratase. El clúster propuesto consta de 3 máquinas virtuales inicialmente idénticas
sobre las que se realizará una serie de modificaciones de tal forma que una de ellas actuará como balanceador
de carga basado en LVS y las otras dos ejecutarán sendos servidores de FTP.
- 1 procesador mononúcleo.
- 256 MB memoria RAM.
- 10 GB disco duro virtual.
- Adaptador de red en modo NAT.
- Sistema operativo Debian 7 Wheezy 32 bits.
283
Además del sistema base Debian, se encuentra preinstalado el servidor de FTP vsftpd con la configuración por
defecto proporcionada por Debian. Los usuarios necesarios para manejar las máquinas son:
Usuario: root
Clave: clave.root
Privilegios: Usuario administrador del sistema
Usuario: usuario
Clave: clave.usuario
Privilegio: Usuario del sistema sin privilegios especiales
Por motivos de tamaño, las máquinas virtuales no cuentan con entorno gráfico, por lo que la administración y
configuración de las mismas se realizará mediante terminal de consola. En la siguiente URL se encuentra una
breve recopilación de los comandos más comunes para el manejo de la terminal:
http://www.informatica.us.es/index.php/en/servicios/centro-de-calculo/comandos-unix
En caso de usar una máquina anfitriona ejecutando Windows, la conexión a las máquinas virtuales mediante
protocolo SSH se puede realizar mediante la aplicación PuTTY, de código abierto y que no precisa instalación:
http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe
Desde entornos GNU/Linux, cualquier emulador de consola nos permitirá conectar vía SSH a las máquinas.
Red
VMware permite la conexión en modo NAT de las máquinas virtuales mediante la cual todas ellas se encuentran
en una red privada virtual donde tienen visibilidad unas sobre otras y todas son accesibles a su vez desde la
máquina anfitriona donde se están ejecutando.
Las máquinas del clúster están configuradas en modo NAT, por lo que tanto balanceador como servidores FTP se
encuentran conectados a la misma red local virtual. El clúster será identificado por una dirección IP virtual (VIP)
que no se corresponderá con ninguna de las máquinas virtuales, pero será el balanceador el encargado de
capturar las peticiones dirigidas a la VIP y redireccionarlas convenientemente.
Por simplicidad, las máquinas no contendrán ninguna regla de cortafuegos. Si bien esto supone un detrimento
de la seguridad, por tratarse de una prueba de concepto a desplegar en un entorno controlado, no supondrá
mayores problemas mientras que simplificará el despliegue de los servicios.
284
Miguel Ángel Cifredo
macifredo@gmail.com
Realización de la práctica.
El sistema operativo de la máquina anfitriona del clúster no es relevante para el desarrollo de esta práctica. Las
herramientas de virtualización de VMware están disponibles tanto para sistemas Windows como GNU/Linux, por
lo que el alumno puede decantarse por cualquiera de los dos sistemas a la hora de trabajar en casa. En el
laboratorio VMware Player únicamente se encuentra instalado en las imágenes Windows, por lo que esta será la
opción elegida para el desarrollo de la práctica y hacia la que irán orientados los procedimientos descritos, si
bien se acompañarán de las instrucciones alternativas para su realización en GNU/Linux.
En los apéndices al final de la práctica el alumno encontrará información relevante a las distintas herramientas
de administración necesarias para realizar la práctica: instrucciones para la edición de fichero, comandos de
terminal, copiado de máquinas virtuales y cualquier otra información considerada relevante.
NOTA: en caso de trabajar directamente con las ventanas de las máquinas en VMware, el cursor del ratón y
teclado queda capturad dentro de dichas ventanas. Para liberarlos y poder seguir trabajando con la máquina
anfitriona es necesario pulsar la combinación de teclas Ctrl Izquierdo + Alt Izquierda.
La primera tarea consiste en crear tres nuevas máquinas virtuales VMware a partir de la plantilla
debian_template, proporcionada para la realización de esta práctica. Los nombres recomendados para estas
nuevas máquinas virtuales son: debian_00, debian_01, debian_02. Una vez creadas, deberán abrirse a través de
la aplicación VMware Player para poder ser usadas en los posteriores apartados.
Los ordenadores del laboratorio de prácticas están equipados con el software VMware Player, una herramienta
gratuita distribuida por VMware que proporciona las funcionalidades básicas para la creación y manejo de
máquinas virtuales.
Para la presente práctica se han creado tres máquinas virtuales idénticas pero con distinto nombre para crear el
clúster virtualizado. Estas máquinas se denominan debian_00, debian_01 y debian_02 y están disponibles en las
siguientes URL de descarga, comprimidas en formato zip:
http://www.atc.us.es/descargas/debian_00_ok.zip
http://www.atc.us.es/descargas/debian_01_ok.zip
http://www.atc.us.es/descargas/debian_02_ok.zip
NOTA: Para descomprimir desde línea de comandos puede usarse la orden unzip archivo.zip, la cual
descomprimirá en el directorio de trabajo el contenido del archivo comprimido.
Las nuevas máquinas virtuales deben abrirse usando la aplicación VMware Player, a través del menú File, opción
Open Virtual Machine. La primera vez que arranquen estas nuevas máquinas virtuales, VMware detectará que
se tratan de máquinas duplicadas, preguntando al usuario si se tratan de máquinas que han cambiado de
ubicación o bien de copias independientes. En nuestro caso indicaremos es necesario indicar que se tratan de
copias a fin de que VMware pueda cambiar ciertos atributos únicos de la máquina como la dirección MAC de las
tarjetas de red virtuales.
NOTA: VMware Player no cuenta con la funcionalidad que nos permite copiar/clonar máquinas virtuales, por lo
que el proceso debe hacerse a mano. Para el copiado de máquinas virtuales VMware en sistemas Windows y
GNU/Linux, consultar el Apéndice B de la presente práctica.
285
Ejercicio 2: Configuración de red de las máquinas virtuales.
Editar la configuración de red de las máquinas virtuales para que cada máquina tenga asignada una IP estática
dentro de la red privada virtual a la que pertenecen las máquinas. Seleccionar también una IP para ser usada
como IP virtual (VIP). Esta IP no identificará a ninguna máquina concreta, sino al clúster en su conjunto como
entidad proporcionando un servido de FTP.
Las máquinas virtuales del clúster se encuentran conectadas a una red local virtual donde obtienen
dinámicamente mediante DHCP una IP correspondiente a una IP de red privada. Esta red se conecta al exterior
gracias a una configuración en NAT. Dependiendo de varios factores (configuración VMware player, versión, etc.),
el rango de IP privadas puede variar, pudiendo ser del tipo 192.168.X.X, 172.16.X.X, etc.
Debido a que las direcciones IP se asignan de forma dinámica, no tenemos asegurado que los servidores reciban
siempre la misma IP, por lo que el primer paso en nuestra configuración es cambiar la asignación dinámica de IP
por una IP estática dentro del mismo rango al que pertenece la red privada virtual a la que se conectan las
máquinas.
NOTA: se recomienda usar una consola independiente (en la máquina anfitriona), en lugar de usar la ventana
VMware donde se ejecuta la máquina virtual. Para entornos Windows, recurrir a la herramienta PuTTY.
Antes de modificar la configuración de red, es necesario tomar nota de la configuración actual de alguna de las
máquinas. Para ello, una vez conectados a cualquiera de las máquinas virtuales es necesario ejecutar los
comandos:
> ifconfig eth0
> route -n
• net addr Dirección IP de la tarjeta de red, necesaria para conocer el rango de la red privada.
• Mask Máscara de red.
• Gateway Puerta de enlace, sólo anotar datos de Gateway de la entrada con Destination 0.0.0.0
Con la información anterior, es necesario elegir 4 direcciones IP, una para cada una de las máquinas virtuales y
una cuarta para identificar a todo el clúster. Si la IP devuelta por ifconfig fue del tipo 192.168.2.X, la elección de
direcciones podría ser del tipo:
192.168.2.10 Dirección IP para identificar al clúster.
192.168.2.20 Dirección IP para el balanceador.
192.168.2.21 Dirección IP para el servidor 1 de FTP.
192.168.2.22 Dirección IP para el servidor 2 de FTP.
El resto de parámetros de red (dns, gateway, netmask) son idénticos para las 3 máquinas. Una vez obtenidos los
anteriores datos, es necesario editar el fichero /etc/network/interfaces de las tres máquinas virtuales. Se
recomienda el uso del editor de texto en consola nano por su simplicidad (Apéndice A). La configuración de la
interfaz de red eth0 debe quedar de la siguiente forma una vez editada:
# The primary network interface
allow-hotplug eth0
iface eth0 inet static
address ip.estatica.red.máquina
netmask mascara.red
gateway puerta.enlace
dns-nameservers 8.8.8.8
286
Miguel Ángel Cifredo
macifredo@gmail.com
Para aplicar los cambios es necesario reiniciar el servicio de networking. Para ello tenemos dos opciones:
Reiniciar el servicio networking desde la consola de la máquina en la ventana de VMware Player (si se
intenta desde una terminal de la máquina anfitriona, se perderá la conexión con la máquina virtual). Los
comandos necesarios para reiniciar la actividad de red son:
> service networking stop
> service networking start
Configurar adecuadamente los dos servidores FTP del clúster y ponerlos a funcionar de forma independiente. El
servidor FTP deberá ser configurado para cumplir con los siguientes requisitos:
Además de la configuración, deberá realizarse la subida y descarga de varios ficheros de prueba accediendo con
la cuenta “usuario”.
El File Transfer Protocol (FTP) es un protocolo de red estándar usado para la transmisión de archivos desde una
máquina hasta otra a través de redes basadas en TCP, como por ejemplo Internet. En esta práctica ha sido elegido
como el servicio al que se le dotará de redundancia. Éste servicio es proporcionado por un software específico
que se ejecutará en el servidor, el servidor de FTP. Si bien existen diversas implementaciones independientes del
protocolo FTP, hemos elegido una de las más extendidas, vsftpd, que se encuentra instalado por defecto en las
máquinas virtuales. Sólo 2 de las tres máquinas virtuales ejecutarán el servicio de FTP, la máquina elegida como
balanceadora no necesita que sea configurado.
Para permitir el correcto funcionamiento del protocolo FTP, es necesario cargar dos módulos del kernel Linux.
Para ello se ejecutarán las instrucciones:
> modprobe ip_conntrack
> modprobe ip_conntrack_ftp
Para asegurarnos que estos módulos se carguen automáticamente cuando arranque la máquina, se añadirán los
nombres de los módulos (ip_conntrack, ip_conntrack_ftp) en líneas independientes dentro del fichero
/etc/modules.
La configuración de cada uno de los servidores FTP se realizará editando los respectivos ficheros de configuración
de vsftpd, concretamente /etc/vsftpd.conf. Como en anteriores ocasiones, se recomienda la edición usando el
editor nano.
Para que los cambios en el archivo de edición surtan efecto, es necesario reiniciar el servicio de FTP, para ello se
puede hacer uso de la instrucción:
> service vsftpd {start|stop|restart}
indicando en cada caso la acción que se desea realizar con el servicio especificado.
287
Para comprobar el correcto funcionamiento de cada uno de los dos servidores FTP, se pide subir un fichero a
cada uno de los servidores FTP. El nombre de cada fichero será distinto. Para realizar esta acción, puede usarse
desde la máquina anfitriona el software cliente de FTP Filezilla, disponible tanto para sistemas Windows como
GNU/Linux. Los datos de conexión a usar son:
NOTA: las máquinas del laboratorio no permiten la instalación del software Filezilla, por lo que se recomienda
usar la versión portable de este software, la cual no requiere privilegios de administrador para su instalación. El
instalador puede descargarse de la siguiente URL: https://consigna.us.es/226208 clave: clave.asd
Configurar la máquina balanceadora de carga para que distribuya las conexiones FTP entrantes entre las
máquinas disponibles en el clúster. La máquina balanceadora se encargará únicamente de captar las peticiones
al clúster y redirigirlas a las máquinas correspondientes siguiendo un algoritmo de planificación especificado.
Se pide crear un script que configure el balanceador y se ejecute automáticamente durante el arranque de la
máquina, así como dar persistencia a las reglas de cortafuegos necesarias en las máquinas servidoras.
Linux Virtual Server (LVS) es una herramienta open source que permite la creación de clúster Linux de alto
rendimiento y disponibilidad. Una de las líneas de trabajo del proyecto es IPVS (IP Virtual Server) que implementa
balanceo de carga en el kernel Linux a nivel de capa de transporte (Layer-4 LAN switching).
IPVS forma parte del kernel de Linux, por lo que sus funcionalidades están disponibles junto con los kernels
empaquetados por la mayoría de las distribuciones actuales. IPVS hace que el clúster sea visto desde el exterior
como una única entidad proveyendo un servicio concreto a través de una IP virtual (VIP), el balanceador se
encarga de redirigir a las máquinas reales del clúster las peticiones de servicios basados en TCP/UDP que se
envían a la VIP.
En primer lugar la máquina balanceadora debe ser capaz de redirigir paquetes hacia otras máquinas, por lo que
es necesario activar la siguiente opción usando el comando:
> echo “1” > /proc/sys/net/ipv4/ip_forward
El siguiente paso es hacer que la máquina balanceadora escuche todas las peticiones dirigidas hacia la Virtual IP
(VIP) que identifica a todo el clúster. Para ello, hay que añadir una segunda dirección IP a la interfaz de red eth0:
> ifconfig eth0:1 VIP netmask X.X.X.X
donde VIP es la IP virtual elegida para el clúster y X.X.X.X la máscara de red correspondiente a la red privada en
la que se encuentran las máquinas virtuales.
Llegados a este punto, necesitamos instalar la herramienta de administración ipvsadm, la cual está disponible en
los repositorios de Debian. Para ello es necesario ejecutar:
> apt-get install ipvsadm
Una vez instalada, es conveniente purgar todas las reglas de balanceo que pudiesen existir antes de introducir
las nuevas. Esto se logra mediante la instrucción:
> ipvsadm -C
288
Miguel Ángel Cifredo
macifredo@gmail.com
A continuación se definirá un servicio al que posteriormente se agregarán las reglas de balanceo. Para ello es
necesario ejecutar:
> ipvsadm -A -t VIP:puerto -s alg.scheduling
el manual de la herramienta contiene una descripción más detallada de los parámetros que acepta (man
ipvsadm). Los parámetros correspondientes a la anterior instrucción son:
-A Añadir servicio.
-t Indica que el servicio está basado en TCP.
VIP Virtual IP, dirección IP que identifica a todo el clúster.
Puerto Puerto usado por el servicio, puede ser valor numérico o identificador de servicio.
-s alg.scheduling Especifica el algoritmo de planificación usado para el balanceo de carga.
Una vez creado el servicio, hay que asociar las máquinas servidoras con el servicio que hemos definido en el
balanceador (en este caso, añadir 2 servidores FTP). El esqueleto de la instrucción es el siguiente:
> ipvsadm -a -t VIP:puerto -r IP.servidor:puerto -g
Complementariamente, hay que desactivar el servicio FTP en el balanceador, pues sólo redireccionará peticiones
FTP, no las atenderá. Para ello hay que recurrir al comando service como se ha visto anteriormente. Para
asegurarse de que el servidor de FTP no arranca automáticamente cuando se inicie la máquina, se puede recurrir
al comando rcconf (específico para Debian y derivados).
Finalmente, es necesario configurar el cortafuegos de las máquinas servidoras de FTP para que sepan
redireccionar correctamente las respuestas a las peticiones FTP. Para ellos es necesario añadir la siguiente regla
de cortafuegos:
>iptables -t nat -A PREROUTING -p tcp -d VIP --dport ftp -j REDIRECT
289
Ejercicio 5: Pruebas sobre el clúster FTP con balanceo de carga.
Comprobar el correcto funcionamiento del clúster y contestando a las preguntas formuladas en las plantillas
entregables que acompañan al boletín de prácticas.
Realización:
A fin de realizar una monitorización básica del tráfico que recibe cada balanceador, usaremos el analizador de
paquetes de red tcpdump. Para instalarlo, ejecutar la siguiente instrucción:
> apt-get install tcpdump
Iniciar sesión en cada una de las máquinas servidoras de FTP (usar dos terminales de consola). En cada sesión,
ejecutar como administrador la instrucción
> tcpdump -n -i any port ftp
la cual monitoriza todos los paquetes FTP que pasen a través de las interfaces de red de la máquina. Cada vez
que se detecte uno de los paquetes especificados, se mostrará una nueva línea por pantalla con su información.
Una vez esté funcionando esta monitorización básica, contestar las preguntas formuladas en las plantillas.
A lo largo de la práctica será necesario editar archivos de configuración contenidos en las distintas máquinas
virtuales, que como se mencionó anteriormente, carecen de entorno gráfico. Existen diversos editores en modo
texto que pueden usarse sin necesidad de entorno gráfico. Por motivos de espacio, las máquinas virtuales sólo
incluye los editores vim y nano. En caso de no contar con experiencia previa con el primero de ellos, nano es la
opción más amigable para el usuario, contando a pie de pantalla con un listado de los distintas combinaciones
de teclas necesarias para llevar las distintas acciones de apertura, cierre, guardado de datos, etc.
Las opciones más relevantes del editor nano son las siguientes:
290
Miguel Ángel Cifredo
macifredo@gmail.com
En VMware, una máquina virtual consta de un directorio cuyo nombre se corresponde con la máquina virtual y
que contiene los archivos de configuración que la definen (.vmx, .vmxf), archivos de datos que contiene la
información del disco duro virtual de la máquina (.vmdk) así como otros ficheros auxiliares (logs, volcados de
memoria, etc.).
Debido a que VMware Player sólo proporciona funcionalidades básicas para el manejo de máquinas virtuales, la
clonación o copiado de máquinas no está incluida entre ellas. Las soluciones pasan por usar alguna de las
herramientas de pago de su suite de virtualización (VMware Workstation) o bien realizar manualmente el
proceso.
1. Realizar una copia completa del directorio que contiene la máquina virtual. El nuevo directorio llevará el
nombre de la nueva máquina que estamos creando. En Windows se puede realizar esta acción desde el
explorador de ficheros. El comando para terminal de GNU/Linux es el siguiente:
> cp -r maquina_original maquina_nueva
2. Acceder al directorio de la nueva máquina virtual y renombrar todos los archivos de tal forma que el nombre
de la máquina original que aparece en los nombres de fichero sea sustituido por el nombre de la nueva
máquina. En Windows se puede realizar esta acción desde el explorador de ficheros, renombrando los
elementos uno a uno. En GNU/Linux se puede usar el comando rename de la siguiente forma:
> cd ./maquina_nueva
> rename 's/maquina_original/maquina_nueva/' *
Para entornos GNU/Linux se ha definido el script para consola de comandos, clone.sh, que automatiza el proceso
en entornos GNU/Linux partiendo de los comandos descritos con anterioridad. La invocación del script debe
realizarse de la siguiente manera:
./clone.sh [ruta_maquina_virtual] maquina_original maquina_copia
[ruta_maquina_virtual] Indica el directorio que contiene la máquina virtual que queremos clonar. Si el
script se encuentra en el mismo directorio, no es necesario especificar este parámetro.
maquina_copia Nombre de la nueva máquina virtual que se creará a través del proceso de copiado.
Ejemplo: Dada la máquina virtual debian_template, el comando para crear una nueva máquina denominada
debian_01 sería el siguiente (suponemos que la máquina virtual se encuentra en la ruta $HOME/p9):
> ./clone.sh $HOME/p9 debian_template debian_01
291
RESULTADOS
Nombre:
Ejercicio 1: Realice un esquema del clúster donde se aprecie la estructura de red, rangos de IP e interconexión
entre las distintas máquinas (servidores, balanceador y máquina anfitriona.
HOST-0
BALANCEADOR
VIP: 192.168.205.10
IP: 192.168.205.20
HOST-1 HOST-2
FTP 1 FTP 2
IP: 192.168.205.21 IP: 192.168.205.22
ANFITRIÓN
CLUSTER VIP: 192.168.205.10
IP: 10.1.12.95
Nota: Todas las máquinas se encuentran interconectados en la misma red. Las flechas indican el balanceo FTP.
Ejercicio 2: Indicar la configuración de red de cada una de las máquinas (IP, netmask, gateway, …)
292
Miguel Ángel Cifredo
macifredo@gmail.com
Ejercicio 3: Las máquinas que dan servicio FTP cuentan con usuarios y contraseñas independientes (“root” y
“usuario”), son estos usuarios los únicos que pueden conectarse a la dirección de FTP.
¿Qué ocurre si añadimos un nuevo usuario únicamente en una de las máquinas y queremos acceder con dicho
usuario a través del balanceador?
Al acceder al servicio balanceado de FTP con un usuario que sólo está en algunas máquinas del
clúster podría causar error de inicio de sesión si el equipo balanceador le asignase una máquina
de servidor FTP donde no esté creado ese usuario.
¿Qué requisitos han de cumplirse para que un usuario pueda acceder a su cuenta FTP dentro del clúster sin
tener problemas de autentificación, independientemente de a qué maquina se redirija su petición?
Las máquinas de componen el clúster de servicio de FTP deberían de tener los mismos usuarios,
esto es, alguna forma de sincronizar los usuarios entre dichas máquinas.
Ejercicio 4a: Escriba el script necesario para configurar el LVS e indique la configuración necesaria en el sistema
para que arranque automáticamente al iniciar la máquina.
#!/bin/sh
echo "1" > /proc/sys/net/ipv4/ip_forward
ifconfig eth0:1 192.168.205.10 netmask 255.255.255.0
ipvsadm –C
ipvsadm -A -t 192.168.205.10:21 -s rr
ipvsadm -a -t 192.168.205.10:21 -r 192.168.205.21:21 –g
ipvsadm -a -t 192.168.205.10:21 -r 192.168.205.22:21 -g
Nota: Para que se ejecutara en el arranque podemos incluir la siguiente línea en el archivo /etc/rc.local:
/bin/sh ~/ejercicio4a.sh
Ejercicio 4b: Realice un script para activar la que debe añadirse a los cortafuegos de los servidores FTP e
indique la configuración necesaria para que se ejecute el script automáticamente al iniciar la máquina.
#!/bin/sh
iptables -t nat –F
iptables -t nat -a PREROUTING -p tcp -d 192.168.205.10 --dport ftp –j REDIRECT
Nota: Para que se ejecutara en el arranque podemos incluir la siguiente línea en el archivo /etc/rc.local:
/bin/sh ~/ejercicio4b.sh
293
Ejercicio 5a: Una vez tcpdump está monitorizando las conexiones FTP en cada uno de los servidores FTP,
realizar desde diversas conexiones FTP contra la IP virtual del clúster. ¿Qué algoritmo de balanceo se eligió en
la configuración de LVS? ¿Se corresponde el balanceo observado con el esperado?
Ejercicio 5b: Subir un archivo mediante Filezilla usando la dirección VIP del clúster. Finalizar la conexión e
iniciar una nueva, comprobando si el archivo se sigue encontrando en el servidor.
NOTA: para visualizar correctamente el contenido del repositorio FTP, será necesario pulsar F5 entre conexión
y conexión para refrescar el contenido mostrado.
Para solucionar este problema debería de replicarse todas las acciones en el resto de máquinas.
Por ejemplo se podrían sincronizar los archivos entre todas las máquinas con un programa
externo.
294
Miguel Ángel Cifredo
macifredo@gmail.com
Nota: Obtenga una versión posterior a la 2.3, ya que versiones anteriores sólo soportan 32-bits y no
incluyen soporte TBB (por lo no podrá utilizar múltiples cores a menos que multiprocese su propia
aplicación). Por lo que se recomienda reconstruir las versiones anteriores de código fuente usando
CMake con el mismo compilador que haya utilizado en sus aplicaciones.
Y descargue el archivo EXE para Windows e indique dónde instalar OpenCV (se recomienda una ruta
corta como C:\OpenCV, donde C:\ es una unidad de disco duro).
2. (Opcional) Instale Intel TBB para permitir la paralelización del código en OpenCV. No utilizaremos más
OpenMP. Es importante que tome nota acerca de que sólo la versión TBB 2.2 o posterior funcionarán.
Para los usuarios de Ubuntu 9.10, pueden hacerse con los paquetes libtbb2 y libtbb-dev desde Ubuntu
10.04 (Lucid Lynx), que permitirán instalar y funcionar sin problemas sobre la 9.10 (Hoy en día Intel TBB
no es gratuito, al menos durante el periodo de 30 días de evaluación).
3. Vea el documento "SPD BASIC CONFIGURATION OF WINDOWS AND VS2010 FOR OPENCV" para
configurar el sistema operativo y Visual Studio 2010.
pp.bat
set OPENCV_DIR=L:\Opencv-2.4.9\build
set OPENCV_VERSION=249
set path=%path%;%OPENCV_DIR%\x86\vc10\bin;%OPENCV_DIR%\common\tbb\ia32\vc10
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe"
295
Configuración básica de Windows y Visual Studio 2010 para OpenCV.
Este documento describe cómo configurar un nuevo proyecto VS2010 (Visual Studio 2010 de Microsoft)
utilizando las librerías OpenCV. Además se describen otras configuraciones interesantes.
Importante: Tenga en cuenta que VS2010 no debe ser lanzado hasta que estas variables hayan sido definidas, ya
que las tareas o procesos de VS2010 necesitará conocer dichas variables.
1) Declare estas dos variables de entorno del sistema en el Panel de Control (esto es, botón derecho sobre
el icono de “Mi PC” o “Equipo”, busque la opción “Configuración avanzada del sistema” → “Opciones
avanzadas” → “Variables de entorno” → “Variables del sistema” → “Nueva”; o bien, “Botón de inicio” →
“Panel de control” → “Propiedades del sistema” → “Variables de entorno” → “Variables del sistema” →
“Nueva”. Si no dispone de privilegios suficientes para declarar las variables de sistema, puede declarar
“variables de usuario”. Tal como sigue, se supone que C:\ es la unidad donde está ubicado OpenCV:
OPENCV_DIR → C:\opencv\build
2) Igualmente se necesitan algunos DLL (como las TBB de Intel), por lo que deben ser añadidas a la variable
PATH (similar a MSDOS) y en la misma ventana de configuración (esto es, “Variables de entorno del
sistema”), de esta forma:
De esta forma los DLL de TBB podrán funcionar (hoy en día Intel TBB no es gratuito, al menos durante el
periodo de 30 días de evaluación).
La opción “Tools (or Deug)” → “Options and Stettings” → “Text editor” → “C/C++” → “Advanced” →
“Always use fallback location” deberá marcarse como “TRUE”. Hacemos referencia a que siempre use la
ubicación de recursos de reserva.
La opción “Tools (or Deug)” → “Options and Stettings” → “Text editor” → “C/C++” → “Advanced” →
“fallback location” deberá indicarse un directorio temporal, por ejemplo “C:\temp_vs2010”. Hacemos
referencia a que a la ubicación de recursos de reserva.
Los valores de estos dos parámetros de configuración desde “Proyect” → “Properties” → “Configuration
properties”:
296
Miguel Ángel Cifredo
macifredo@gmail.com
Para hacer esto, declare una nueva variable de entorno del sistema en el Panel de Control (esto es, botón
derecho sobre el icono de “Mi PC” o “Equipo”, busque la opción “Configuración avanzada del sistema”
→ “Opciones avanzadas” → “Variables de entorno” → “Variables del sistema” → “Nueva”; o bien, “Botón
de inicio” → “Panel de control” → “Propiedades del sistema” → “Variables de entorno” → “Variables del
sistema” → “Nueva”. Si no dispone de privilegios suficientes para declarar las variables de sistema, puede
declarar “variables de usuario”. Esta variable es referente a los archivos temporales de VS.
VS10_TEMP_DIR → c:\temp_vs2010
$(VS10_TEMP_DIR)\$(ProjectName)$(Configuration)\
Esto puede conllevar algunos “efectos colaterales” por ejemplo los ficheros de código declarados ahora
en VS2010 serán ficheros genéricos en vez de códigos fuente, ficheros de inclusión, cabeceras, etc. Y por
supuesto, toda la información de debug ya no está en la ruta del fichero fuente. Para cambiar los ficheros
fuente, simplemente excluya los ficheros que nos sean de este proyecto e incluya los nuevos ficheros.
Realizado los cambios anteriores, “Properties of Solution” y “Proyect” han cambiados para soportar
ficheros grandes para incluirlos en la misma ruta donde están los ficheros importantes como (.c, .cpp, .h,
.prj, .sln y algunos más).
Importante: Tenga en cuenta que VS2010 no debe ser lanzado hasta que estas variables hayan sido
definidas, ya que las tareas o procesos de VS2010 necesitará conocer dichas variables.
Estos cambios introducidos en un proyecto, lo que ahora difiere de la típica “Console Application” que
crea VS2010.
297
Configuración de VS2010
Ahora describiremos el enfoque del uso de las librerías estáticas que tienen extensiones lib.
Recuerde que existen diferentes paquetes de reglas para cada uno de los modos build (por ejemplo, para los
modos DEBUG y RELEASE). Lo siguiente debe hacerse para cada modo. Para hacer esto, en para parte superior
izquierda de la ventana “Property Pages”, deberá seleccionar cada configuración. Existe una especial llamada “all
configurations”. Utilice esta para cambiar los dos puntos anteriores.
O bien, su equivalente:
%OPENCV_DIR%\include;%OPENCV_DIR%\include\opencv;%(AdditionalIncludeDirectories)
298
Miguel Ángel Cifredo
macifredo@gmail.com
5) Mire dentro de las propiedades del proyecto en “Linker” → “Input” ítem “AdditionalDependencies” y
añada las siguientes rutas (para que pueda utilizarse Debug):
$(OPENCV_DIR)\x86\vc10\lib\opencv_core%OPENCV_VERSION%d.lib;$(OPENCV_DIR)\x86\vc10\li
b\opencv_highgui%OPENCV_VERSION%d.lib;$(OPENCV_DIR)\x86\vc10\lib\opencv_video%OPENCV_
VERSION%d.lib;$(OPENCV_DIR)\x86\vc10\lib\opencv_ml%OPENCV_VERSION%d.lib;$(OPENCV_DIR)
\x86\vc10\lib\opencv_legacy%OPENCV_VERSION%d.lib;$(OPENCV_DIR)\x86\vc10\lib\opencv_im
gproc%OPENCV_VERSION%d.lib;%(AdditionalDependencies)
O bien, su equivalente:
%OPENCV_DIR%\x86\vc10\lib\opencv_core%OPENCV_VERSION%d.lib;%OPENCV_DIR%\x86\vc10\lib\
opencv_highgui%OPENCV_VERSION%d.lib;%OPENCV_DIR%\x86\vc10\lib\opencv_video%OPENCV_VER
SION%d.lib;%OPENCV_DIR%\x86\vc10\lib\opencv_ml%OPENCV_VERSION%d.lib;%OPENCV_DIR%\x86\
vc10\lib\opencv_legacy%OPENCV_VERSION%d.lib;%OPENCV_DIR%\x86\vc10\lib\opencv_imgproc%
OPENCV_VERSION%d.lib;%(AdditionalDependencies)
Si desea utilizar una construcción modo Release, en vez de modo Debug, dejar la última letra d, es decir:
%OPENCV_DIR%\x86\vc10\lib\opencv_core%OPENCV_VERSION%.lib;%OPENCV_DIR%\x86\vc10\lib\o
pencv_highgui%OPENCV_VERSION%.lib;%OPENCV_DIR%\x86\vc10\lib\opencv_video%OPENCV_VERSI
ON%.lib;%OPENCV_DIR%\x86\vc10\lib\opencv_ml%OPENCV_VERSION%.lib;%OPENCV_DIR%\x86\vc10
\lib\opencv_legacy%OPENCV_VERSION%.lib;%OPENCV_DIR%\x86\vc10\lib\opencv_imgproc%OPENC
V_VERSION%.lib;%(AdditionalDependencies)
299
300
Miguel Ángel Cifredo
macifredo@gmail.com
Exámenes.
2009 Junio 05
1. Indicar el tipo de disposición lógica de la memoria (espacio de direcciones) a los que estarían orientados
los siguientes modelos de programación:
2. Indicar la forma de comunicación (entre hilos o procesos respectivamente) más eficiente en los
siguientes modelos de programación:
4. Tenemos un procesador cuya frecuencia es de 1’3 GHz, A: ¿Qué CPI tendría que tener para obtener la
ejecución de 1805’5 Millones de instrucciones por segundo? B: ¿Qué ancho de banda máximo (en
MBytes/seg) me suministraría una red con enlaces de frecuencia 1’3 GHz y ancho de 24 bits?
1300 𝑀𝐻𝑧 𝑐𝑖𝑐𝑙𝑜𝑠 24 𝑀𝐵𝑦𝑡𝑒𝑠
𝐴: 𝐶𝑃𝐼 = ′
= 0′ 72 𝐵: 𝐴𝐵 = 1300 ∙ = 3900
1805 5 𝑀𝑖𝑛𝑠𝑡𝑟𝑐𝑐 𝑖𝑛𝑡𝑟𝑢𝑐𝑐𝑖ó𝑛 8 𝑠𝑒𝑔𝑢𝑛𝑑𝑜
5. Indicar si es cierta la siguiente afirmación: OpenMP es útil para implementar aplicaciones multiproceso
en arquitecturas construidas con procesadores de varios núcleos.
Razón: OpenMP está diseñado para aplicaciones multi-hilo (multi-thread). También se da como válida,
contestar que es útil siempre que fuera una aplicación híbrida MPI- OpenMP, donde se usaría MPI para
generar los procesos, y dentro de cada MultiCore se optimizara con OpenMP (multihilo).
TEORÍA GENERAL
= 2.160 𝑐𝑜𝑚𝑢𝑛𝑖𝑐𝑎𝑐𝑖𝑜𝑛𝑒𝑠
301
2. (3 puntos) El siguiente código calcula pi por el método de Montecarlo (rand devuelve un entero
pseudoaleatorio entre 0 y RAND_MAX):
for(t=0,i=0; i<IMAX; i++){
x = rand(); y=rand();
r = sqrt(x * x + y * y);
if (r < RAND_MAX) t++;
}
pi= 4 * t /IMAX;
a) En un UMA con p procesadores, ¿Cómo lo paralelizaríamos con openMP? Indicar cuantos procesos
e hilos se crearían en las distintas partes del código, si está optimizado para esta arquitectura.
b) En un clúster con C nodos, usando MPI, indicar cuántos procesos e hilos se crearían en las distintas
partes del código, si está optimizado para esta arquitectura. ¿Cuántos mensajes se transmitirían?
c) ¿Cuántos procesos e hilos tendríamos en una versión optimizada para una constelación formada por
C nodos con p procesadores cada uno?
Solución:
Aparatado a)
omp_set_num_threads(p);
#pragma omp parallel for prívate(x, y, r) reduction(+:t)
for(...){
...
}
pi = 4 * t / IMAX;
NumProcesos = 1; NumHilosXproceso = p;
Aparatado b)
NumProcesos = C ; NumHilosXproceso = 1 ;
/* Cada nodo ejecutaría IMAX/C iteraciones,
y se enviarían C-1 mensajes
(desde todos los nodos al nodo maestro, para realizar la reducción)
*/
MPI_Comm_size(..., &C);
MPI_Comm_rank(..., &myid);
Inicio = myid * IMAX / C;
Fin= Inicio + IMAX / C;
for (t=0, i=inicio; i < Fin; i++){
...
}
MPI_Reduce(&t, &total, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
if (myid==0) pi = 4 * total / IMAX;
...
Apartado c)
En una constelación de C nodos y p procesadores por nodo, en cada nodo habría 1 proceso y p
hilos, de forma que en total tendríamos C procesos y C*p hilos.
302
Miguel Ángel Cifredo
macifredo@gmail.com
P1. (5 puntos) Tenemos una aplicación que se va a ejecutar en un multiprocesador MPM con 16 nodos. La
aplicación consiste en lo siguiente:
1. El nodo 0 tiene almacenada una matriz de datos a procesar. Suponer que estos datos están organizados
de la forma más conveniente.
2. El nodo 0 trocea la matriz y la distribuye en partes iguales entre los procesadores.
3. Repetir 10 veces los pasos 4 a 5.
4. Los nodos impares envían sus datos a los pares y viceversa.
5. Los nodos realizan un cálculo usando sus datos locales y los recibidos de otros nodos.
6. Los nodos envían sus matrices parciales al nodo 0.
Se proponen dos posibles topologías para el sistema MPM: Hipercubo (existe un enlace entre aquellos
procesadores cuyo identificador difiere en un bit) y Anillo bidireccional (los procesadores se numeran
sucesivamente). Se pide:
a) Escribir el código MPI o Pseudocódigo indicando los envíos y recepciones bloqueante y no bloqueante.
b) Calcular el valor de 𝑙 en ambos caso e indicar que topología es más adecuada para este problema.
Solución:
Apartado a)
else{
...
// Es impar, sería igual pero cambiando en los bucles j=1 por j=0
...
}
303
Apartado b)
Caso Hipercubo:
Con esta topología, observamos que todos los nodos pares se encuentran a un lado y todos los impares
al otro. Se trata por tanto de una red simétrica vista desde cualquier punto de vista, por lo que 𝑙 será
igual para cualquier nodo que estudiemos.
Veamos el caso de uno cualquiera de sus nodos, tomemos por ejemplo, el nodo 3 [0011] :
Igual que en el caso del Hipercubo, resulta una topología simétrica, por lo que el valor de 𝑙 es el mismo para
cualquier nodo. Veamos el caso de uno cualquiera de sus nodos. Tomemos por ejemplo, el nodo 0.
Concluimos con que se comporta mejor en una topología tipo Hipercubo al tener un valor de 𝑙 menor.
304
Miguel Ángel Cifredo
macifredo@gmail.com
2013 Abril 22
T1. (1 punto) ¿Cuáles son las principales causas que limitan el ILP en los GPP (procesadores de propósito general)
actuales?
T2. (1’5 puntos) Enumerar y definir brevemente los distintos enfoques usados para reducir el tiempo de
ejecución en una máquina encadenada.
T3. (1’5 puntos) Enumerar y definir brevemente las técnicas de planificación estática avanzada que conozca.
P1. (1 punto) Indique cómo se escribe en lenguaje C el siguiente bucle para que sea paralelizable, si se utilizan
varios acumuladores parciales dentro del bucle.
for (i=0; i<1024; i+=2) p = p + x[i] * y[i]
Suponer que la división tarda 10 ciclos y el resto de operaciones sólo 1 ciclo. Suponer BTB que siempre acierta.
b) Escribir el código (para un RISC escalar). Por comodidad usar un único registro base, modificando los
desplazamientos según el array.
c) Aplicar el Algoritmo de Tomasulo para un RISC escalar y calcular el CPI para el estacionario. Estudiar los
casos UF DIV segmentada y no segmentada.
d) Ídem para superescalar de grado 3 (donde siempre puede lanzar 4 instrucciones en la fase IF, es decir
hay una IFU que tiene siempre llena la ventana de IF).
305
2013 Junio 04
Modelo 1
Datos: %ld/st = 25%; %Accesos Remotos = 15%; Mr = 3%; Tamaño Medio Mensaje = 100 Bytes; CPI ideal = 0’5
ciclos/instrucción; El tiempo medio de acceso a la red lo podemos aproximar por: t acc,red = 2 ∗ 𝑙 ∗ 𝑡ℎ donde
th = 20 ciclos CPU.
Modelo 2
Datos: %ld/st = 32%; %Accesos Remotos = 18%; Mr = 2’5%; Tamaño Medio Mensaje = 128 Bytes; CPI ideal = 0’5
ciclos/instrucción; El tiempo medio de acceso a la red lo podemos aproximar por: t acc,red = 2 ∗ 𝑙 ∗ 𝑡ℎ donde
th = 25 ciclos CPU.
306
Miguel Ángel Cifredo
macifredo@gmail.com
2013 Junio 18
T1. (1’5 puntos) Define los conceptos: Procesador VLIW, Ejecución fuera de orden y Planificación estática.
T2. (1’5 puntos) Hay dos tipos de sistemas P2P: Estructurados (por ejemplo, aquellos que usan el protocolo
Chord) y no estructurados (por ejemplo, los que usan inundación (flooding) o caminos aleatorios (random
walks)). Explica brevemente las diferencias y las principales ventajas/desventajas de los dos tipos.
P1 (3’5 puntos) Sea un procesador encadenado con el Algoritmo de Tomasulo clásico, donde la duración de la
UF ADDFP (totalmente segmentada) es 3 ciclos (IF IS A1 A2 A3 WB) y tiene 3 RS de tal UF e infinitos CDB. Se
ejecuta un programa que sólo contiene instrucciones ADDF y donde cada una depende de las dos anteriores (ver
código). Se pide:
a) (1’0 puntos) Dibuje el cronograma de las primeras seis instrucciones, indicando todos los bypasses y la RS
que ocuparía cada instrucción (numere las RS de 1 a 3).
b) (1’5 puntos) Para el cronograma anterior, el estado y contenido de todos los campos de cada RS al final del
ciclo 7 (contando los ciclos desde 1 para la primera IF). El nombre de las etiquetas coincide con el de las RS,
y es ADDFj, con j = 1, 2, 3.
c) (1’0 puntos) Los CPI datos, CPI estructural y CPI total en el estacionario, suponer que ejecutan infinitas instrucciones.
Código:
ADDF F1, F2, F1 ; antes del código, F1 y F2 están disponibles en el fichero de
; registros FP, y valen 1.0 y 2.0 respectivamente.
ADDF F2, F2, F1
ADDF F1, F2, F1
ADDF F2, F2, F1
…
P2 (3’5 puntos). Tenemos una matriz float m[n][n] en un sistema con p procesadores (podemos suponer que n
es múltiplo de p). Queremos implementar en MPI un algoritmo donde el valor final de m[i][j] depende de los
valores de sus vecinos:
m[i][j] = f(m[i-1][j], m[i+1][j], m[i][j-1], m[i][j+1])
En el caso de que algún elemento no exista (bordes) se sustituye su valor por 0. Para implementar el algoritmo
seguimos el siguiente esquema:
1. El nodo maestro (0) reparte la matriz por bloques iguales de filas consecutivas entre los procesadores
con rango consecutivo.
2. Cada procesador envía los elementos necesarios a los nodos que lo necesitan.
3. Se realizan los cálculos necesarios (se aplica la función f()).
4. Se repiten los pasos 2 y 3, K veces.
5. Se envían los resultados al maestro.
Se pide:
Nota: La malla se enumera de izquierda a derecha y de arriba abajo (0 al 15). El anillo se enumera
consecutivamente del 0 al 15
307
2013 Septiembre 07
a) Escribir el código para un DLX con bypasses y desenrollar 2 iteraciones. Los saltos se resuelven en ID.
Suponer “a” en F10. Existe una UF de suma flotante de 2 ciclos de duración, una de multiplicación de FP
de 4 ciclos, una de división de 10 ciclos y una de enteros de 1 ciclo. Todas segmentadas excepto la
división. Calcular el CPI. (Suponed que el estacionario se consigue en la segunda iteración).
b) Suponiendo ahora planificación dinámica con el Algoritmo de Tomasulo y BTB 100% efectiva, infinitas RS
y mismas latencias de UF que en el apartado anterior, calcular el CPI del bucle original (sin ningún tipo
de planificación estática) y la aceleración respecto al apartado anterior. (Suponed que el estacionario se
consigue en la segunda iteración).
P2. (3 puntos) Pretendemos estudiar cómo se adapta un sistema multiprocesador NUMA con 16 nodos
(procesadores RISC de 500 MHz) y dos topologías distintas, a un mismo patrón de comunicaciones (Todos con
Todos). La primera topología es en forma de anillo cordal cuyos nodos tienen grado 4 (cada nodo n se conecta
con el nodo n+2, y el nodo 15 con el 1). La otra topología es en forma de malla 2D. Suponiendo que la CPU se
bloquea en los accesos a la red y que el tiempo de acceso lo podemos aproximar por t acc,red ≈ 2 ∗ 𝑙 ∗ 𝑡ℎ
¿Qué topología tendría mayores prestaciones en función del ancho de bisección disponible y el demandado?
Datos: th = 12 ciclos de red; CPI base = 0’5 ciclos/instrucción, %LD/ST = 17%; %Accesos remotos = 22%; Mr = 1’5%;
tamaño de línea = 128 Bytes; tamaño de cabecera = 8 Bytes. Enlaces de frecuencia 125 MHz y 16 bits de ancho.
C1. (2 puntos) Se ejecutan en un UMA de 2 procesadores dos versiones paralelizadas de un sencillo algoritmo
(códigos A y B), sabiendo que cada hilo se lanza en un procesador distinto y que aproximadamente ambos hilos
van sincronizados. También se sabe que los procesadores tienen un buffer de precarga (cuyo tiempo de acceso
es idéntico al del caché) que carga la línea siguiente a la actualmente accedida. ¿Puede tardar uno de los dos
códigos más que el otro o ambos tendrán una duración similar? Razonar.
Código A Código B
double a[N]; double a[N];
hilo 1 hilo 2 hilo 1 hilo 2
for (i=0; i<N/2; i++) for (i= N/2; i<N; i++) for (i=0; i<N; i+=2) for (i=1; i<N; i+=2)
a[i] = a[i] * 3.1415926; a[i] = a[i] * 3.1415926; a[i] = a[i] * 3.1415926; a[i] = a[i] * 3.1415926;
308
Miguel Ángel Cifredo
macifredo@gmail.com
2013 Diciembre 10
C1 (1’0 punto). Se pide paralelizar usando OpenMP el siguiente bucle, indicando qué variables son shared y
cuáles son private.
for (i = 0; i < n; i++) {
temp = a[i] / b[i];
a[i] = b[i] + temp * c[i];
}
C2. (3’0 puntos) Define y explique brevemente las características de los principales aspectos a tener en cuenta
en el diseño de los Sistemas Distribuidos.
a) Traducir a un RISC típico usando el mínimo número de instrucciones posibles (un único registro base
para todos los vectores) el siguiente bucle.
b) A continuación reordenar para que el mayor número de instrucciones posible esté detrás de DIVF
c) Para una única iteración y pensando en el data flow limit (suponemos que se puede ejecutar en paralelo
todas las operaciones posibles; es decir, que no tengan dependencia), ¿cuántos ciclos tarda en
almacenarse a[i] de la cuestión anterior desde que se empieza a leer el primer elemento de la memoria
de esa iteración (que sería a[i], a[i-2] o b[i])? Duración de las operaciones: ADDF: 4 ciclos, DIVF: 20,
LF: 3, SF: 2.
P2. (3’5 puntos) Pretendemos estudiar cómo se adapta un sistema multiprocesador NUMA con 16 nodos
(procesadores RISC de 500 MHz) y dos patrones de comunicación distintos (Adyacente 2D y Todos con Todos).
La otra topología es en forma de malla 2D. Suponiendo que la CPU se bloquea en los accesos a la red y que el
tiempo de acceso lo podemos aproximar por t acc,red ≈ 2 ∗ 𝑙 ∗ 𝑡ℎ Calcular para ambos patrones de comunicación
la relación entre el ancho de banda de bisección disponible y el demandado
Datos: th = 12 ciclos de red; CPI base = 0’5 ciclos/instrucción, %LD/ST = 17%; %Accesos remotos = 22%; Mr = 1’5%;
tamaño de línea = 128 Bytes; tamaño de cabecera = 8 Bytes. Enlaces de frecuencia 125 MHz y 16 bits de ancho.
309
2014 Abril 02
T1 (1’0 punto). Aplicar la Ley de Amdahl al siguiente caso: sea un procesador VLIW puro con p = 8 operaciones
por macroinstrucción. Se sabe que un compilador es capaz de empaquetar el 90% de operaciones
perfectamente, pero el 10% restante se quedan “sueltas”, es decir, cada operación ocupa toda una
macroinstrucción (quedando las otras 7 operaciones vacía).
T2. (1’0 punto) Dibujar un croquis de la técnica del software pipeline (encadenamiento software de iteraciones)
para el bucle SAXPY.
Suponer que:
Se pide:
a) Escribir el código (para un RISC escalar) sin aplicar optimización estáticas. Por comodidad usar un único
registro base, modificando los desplazamientos según el array.
b) Hallar el camino crítico (del flujo de datos) para una iteración y calcular su número de ciclos.
c) Hallar el camino crítico (del flujo de datos) para muchas iteraciones y calcular su número de
ciclos/iteración.
d) Aplicar para 2 iteraciones el algoritmo de Tomasulo para un RISC superescalar de grado 5. Calcular los
GFLOPS para el estacionario.
e) Desenrollar 3 iteraciones usando acumuladores parciales en lenguaje C y hallar la aceleración respecto
al caso anterior (para el estacionario).
f) Si los vectores fueran muy grandes, de manera que se tuviera siempre que acceder a RAM (siendo su AB
de 8 GB/s), calcular los GFLOPs por culpa de este acceso.
310
Miguel Ángel Cifredo
macifredo@gmail.com
2014 Junio 16
PRIMER BIMESTRE
T1 (1’6 puntos). Explicar los tipos de predictores de saltos que se suelen implementar en una BTB y poner un
ejemplo de código por cada predictor, en donde se vea cuántas veces acertaría o fallaría un predictor.
T2 (2’4 puntos).
a) Explicar con un ejemplo (en ensamblador) la utilidad de las Instrucciones predictivas o condicionadas.
c) Calcular los GFLOPs para esta máquina, si el 25% de las instrucciones (operaciones) son FLOP: VLIEW
puro con p = 5 slots por macroinstrucción, donde el 80% de las macroinstrucciones son empaquetadas
perfectamente (5 operaciones) por el compilador y 20% restante sólo contienen una operación por
macroinstrucción). Frecuencia = 1 GHz.
P1 (6’0 puntos). Se quiere evaluar un benchmark, donde la mayor parte del tiempo de ejecución está en estos
dos bucles:
float x=1.0, a[n], b[n], c[n]; int i;
a) for(i=0; i<N; i++) x = x * a[i] * b[i];
b) for(i=0; i<N; i++) c[i] = a[i] * b[i];
Se pide:
a) Dibujar el flujo de datos y hallar el camino crítico para muchas iteraciones de cada bucle.
b) Para el estacionario de estos bucles, dibujar un cronograma para un RISC superescalar de grado 3,
algoritmo de Tomasulo (suponer que el ensamblador usa un único registro base, y no se desenrolla).
Suponer:
- Las etapas a considerar son IS EX.
- Infinitos recursos de UF, buses CDB, suponer que BTB y cachés siempre aciertan, etc.
- Excepción: Número de RS es grande pero limitado.
- Duraciones: MULTF (duración 5 ciclos). Totalmente segmentada. El resto de operaciones sólo 1
ciclo.
c) Para el bucle a). desenrollar 3 iteraciones usando las optimizaciones que crea conveniente en lenguaje
C y hallar:
1. Número mínimo de UF de MULTF para que no haya bloqueo por esto (en el estacionario).
2. El ancho de banda de RAM para que ésta no suponga un cuello de botella (en el estacionario, y
si los vectores fueran muy grandes, de manera que se tuviera siempre que acceder a RAM).
- Frecuencia = 3GHz.
311
SEGUNDO BIMESTRE
P2 (6’0 puntos). Pretendemos estudiar cómo se adapta un patrón de comunicaciones a dos topologías distintas
de un sistema multiprocesador NUMA con 16 nodos (procesadores RISC de 2’4 GHz):
1. Patrón de comunicaciones:
a. Los procesos se comunican con los que no pertenecen ni a su fila ni a su
columna.
2. Topologías:
a. Malla 4x4.
b. Toro 2D.
Nota: Los nodos se enumeran de izquierda a derecha y de arriba abajo, comenzando por Toro 2D
el 0 (es par), es decir la primera fila los nodos 0, 1, 2 y 3, la segunda 4, 5, 6 y 7 etc.
Suponer que la CPU se bloquea en los accesos a la red y que el tiempo de acceso lo podemos aproximar por
t acc,red ≈ 2 ∗ 𝑙 ∗ 𝑡ℎ Calcular para ambas topologías el ancho de banda de bisección disponible y el demandado,
indicando en cada caso si la red soportaría el ancho de banda demandado.
Datos: th = 15 ciclos de red; CPI base = 0’5 ciclos/instrucción, %LD/ST = 19%; %Accesos remotos = 21%; Mr = 1’8%;
tamaño de línea = 256 Bytes; tamaño de cabecera = 8 Bytes. Enlaces de frecuencia 600 MHz y 16 bits de ancho.
T3 (4’0 puntos). Escribir en código MPI o pseudocódigo un programa que implemente el patrón de
comunicaciones del problema P2.
312
Miguel Ángel Cifredo
macifredo@gmail.com
2014 Diciembre
Suponer que:
Se pide:
a) Escribir el código (para un RISC escalar) sin aplicar optimizaciones estáticas. Por comodidad usar un único
registro base, modificando los desplazamientos según el array.
b) Dibujar el cronograma para 2 iteraciones el algoritmo de Tomasulo para un RISC superescalar de grado
3.
c) Calcular los GFLOPs para el estacionario.
d) Si los vectores fueran muy grandes y se ejecutara en sólo 2 ciclos/iteración, de manera que se tuviera
siempre que acceder a RAM, calcular los GFLOPs si el AB de RAM está limitado a 3 GB/s.
e) Al desenrollar 3 iteraciones, ¿se producirá mucho, poco o ningún aumento de GFLOPs para los casos
anteriores c) d) e) en el estacionario? Razonar.
Problema 2. Pretendemos estudiar cómo se adapta un sistema multiprocesador NUMA con 16 nodos
procesadores RISC de 2500 MHz) y dos patrones de comunicación distintos, a una topología en forma de malla
2D. Los patrones de comunicación son:
1. Adyacente 2D.
2. Pares con ipares y viceversa.
Nota: Numerar los nodos empezando por el cero, de arriba abajo y de izquierda a derecha.
Suponiendo que la CPU se bloquea en los accesos a la red y que el tiempo de acceso lo podemos aproximar por
t acc,red ≈ 2 ∗ 𝑙 ∗ 𝑡ℎ Calcular para ambos patrones de comunicación la relación entre el ancho de banda de
bisección disponible y el demandado.
Datos: th = 20 ciclos de red; CPI base = 0’5 ciclos/instrucción, %LD/ST = 17%; %Accesos remotos = 35%; Mr = 1’8%;
tamaño de línea = 128 Bytes; tamaño de cabecera = 8 Bytes. Enlaces de frecuencia 500 MHz y 16 bits de ancho.
313
2015 Abril 09
GRUPO 1
T1. (1’0 puntos) Hallar el CPI de control del siguiente cronograma, si se sabe que en las primeras 9 iteraciones
acierta la BTB y falla en la número 10. El salto se resuelve en EX, y tras esta fase se busca la rama correcta en
caso de fallo de predicción.
Bucle:
IF IS EX WB
IF IS o EX WB
IF IS o o EX WB
IF IS EX WB
Salto: IF IS o o EX WB
hacia atrás
(del bucle)
a. un superescalar de grado 5, que tiene CPIbloqueo_total = 0.4, frecuencia = 2 GHz y ejecuta un millón de
instrucciones (operaciones).
b. Un VLIW puro de p = 4 slots que tiene CPIbloqueo_memoria = 0.2, frecuencia 1.5 GHz y el compilador
consigue compactar el millón de instrucciones, con 3 instrucciones (operaciones) por macroinstrucción.
T3. (1’8 puntos) Clasificar los bucles según se puedan desenrollar, paralelizar o no. Poner ejemplos en lenguaje
C o similar.
Suponer que:
- Las etapas a considerar son IS EX WB (la fase IF siempre tiene preparadas suficientes instrucciones para
intentar emitir).
- Superescalar de grado m = 4.
- Frecuencia = 3 GHz
- Suponer que la fase EX de Load/Store tarda 1 ciclo. Hay un único puerto para caché L1 (una U.F.).
- El resto de operaciones con fase EX de sólo 1 ciclo y tienen infinitas U.F.
- Suponer que BTB y cachés siempre aciertan (mientras no se indique lo contrario).
Se pide:
a) Hallar el camino crítico (del flujo de datos) para muchas iteraciones y calcular su número de ciclos/iteración
b) Desenrollar 2 iteraciones usando multiplicadores parciales en lenguaje C.
c) Escribir el código (para un RISC) sin aplicar optimizaciones estáticas. Por comodidad usar un único registro
base, modificando los desplazamientos según el array.
d) Dibujar el cronograma para 2 iteraciones del algoritmo de Tomasulo.
e) Calcular los GFLOPS para el estacionario (suponiendo que el número de R.S. es finito)
314
Miguel Ángel Cifredo
macifredo@gmail.com
GRUPO 2
T1. (1’4 punto) Realizar un croquis para poder aplicar la Ley de Amdahl y para hallar la aceleración a este caso:
- Suponemos que el 70% de un programa se puede ejecutar en paralelo en 4 procesadores,
- el 20% en 2 procesadores
- el 10% en secuencial (en un procesador).
T2. (1’0 puntos) Hallar la aceleración para la ejecución de un millón de instrucciones para un superescalar
dinámico de grado 5, que tiene CPIbloqueo_estructural (por agotamiento de R.S.) = 0.3 cic/instr, frecuencia = 3
GHz , comparando estos dos casos:
- BTB perfecta (acierta siempre).
- BTB realista: falla el 5% de las predicciones, en cada fallo introduce 20 ciclos de bloqueo de media y hay
un 20 % de instrucciones de salto.
T3. (1’6 puntos) Dibujar un croquis de la técnica del software pipeline (encadenamiento software de iteraciones)
para el bucle siguiente. Explicar las diversas partes del croquis resultante. Esbozar el código ensamblador que
resultaría (no es necesario especificar los registros de las instrucciones de overhead).
for (i=0; i<1024; i++)
x[i] = x[i] * y[i] * z[i];
Suponer que:
- Las etapas a considerar son IS EX WB (la fase IF siempre tiene preparadas suficientes instrucciones para
intentar emitir).
- Superescalar de grado m = 5.
- Frecuencia = 4 GHz
- Suponer que la fase EX de la multiplicación tarda 3 ciclos. Es U.F. única y está segmentada.
- Suponer que la fase EX de los accesos a caché tarda 1 ciclo. Es de puerto único (U.F. única).
- El resto de operaciones con fase EX de sólo 1 ciclo y tienen infinitas U.F.
- Suponer que BTB y cachés siempre aciertan (mientras no se indique lo contrario).
Se pide:
a) Hallar el camino crítico teórico (del flujo de datos) para muchas iteraciones y calcular su número de
ciclos/iteración
b) Escribir el código (para un RISC) sin aplicar optimizaciones estáticas. Por comodidad usar un único registro
base, modificando los desplazamientos según el array.
c) Dibujar el cronograma para 2 iteraciones del algoritmo de Tomasulo.
d) Calcular los GIPS (Giga instrucciones por segundo) para el estacionario (suponiendo que el número de R.S.
es finito) si los vectores caben en L1
e) Calcular los GFLOPS para el estacionario si los accesos a los vectores fueran a RAM (suponiendo que el AB
RAM es 6 GB)
315
2015 Mayo 28
MODELO 1
Se va a escribir un programa para sumar un vector de 65536 (216) número según el algoritmo visto en clase:
primero se suman trozos iguales en cada procesador (o U.F.) y luego, con los acumuladores parciales obtenidos
en cada procesador, se sigue la técnica de “divide y vencerás” entre procesadores (se suma una mitad con la otra
y así sucesivamente hasta quedar un solo acumulador). ¿Qué diferencias habría en el código escrito para un
UMA bus común, para un NUMA con árbol binario y para un multicomputador con árbol binario?
Defina y explique brevemente tres aspectos Se va a escribir un programa para sumar un vector de 65536 (216)
número según el algoritmo visto en clase:
P1. (7 Puntos)
Pretendemos estudiar cómo se adapta un sistema multiprocesador NUMA con 16 nodos (procesadores RISC de
1500 MHz) y con topología en forma de Hipercubo, a dos patrones de comunicación distintos. El primero es un
patrón adyacente, teniendo en cuenta que todos los nodos se comunican con aquellos con los cuales tiene
conexión directa. Por otra parte, tenemos un patrón de comunicaciones del tipo Todos con Todos. Suponiendo
que la CPU se bloquea en los accesos a la red y que el tiempo de acceso lo podemos aproximar por t acc,red ≈
2 ∗ 𝑙 ∗ 𝑡ℎ ¿Soportaría la red en su bisectriz el tráfico generado por ambos patrones de comunicación?
Datos: th = 15 ciclos de red; CPI base = 0’4 ciclos/instrucción, %LD/ST = 26%; %Accesos remotos = 34%; Mr = 2’4%;
tamaño de línea = 128 Bytes; tamaño de cabecera = 8 Bytes. Enlaces de frecuencia 300 MHz y 24 bits de ancho.
Nota: La red hipercubo se construye uniendo los enlaces cuya numeración binaria difiere en un bit.
316
Miguel Ángel Cifredo
macifredo@gmail.com
MODELO 2
Explique brevemente los tipos de Arquitecturas Centralizadas con las que nos podemos encontrar. Ponga algunos
ejemplos.
P1. (7 Puntos)
Pretendemos ejecutar un algoritmo sobre una matriz de tamaño 1024x1024, en un multiprocesador tipo MPM
de 16 nodos. La matriz se trocea en 16 submatrices. Teniendo en cuenta que el algoritmo implica el envío de los
datos limítrofes entre las zonas,
b) (2’5 puntos) ¿Cuál sería el número de comunicaciones a realizar, teniendo en cuenta que en cada
comunicación se envía un único elemento de la matriz? Poned la fórmula con la cual hayáis calculado el
resultado.
c) (3’5 puntos) Escribir un programa en MPI o pseudocódigo que implemente dicho algoritmo.
317
2015 Junio 11
Primer bimestre:
a) Desenrolle 4 iteraciones del siguiente bucle de la forma más eficiente que sepa.
b) ¿Qué cantidad de cargas de memoria, de multiplicadores y de sumadores en paralelo se necesitan para
poder ejecutarlo en forma de árbol binario (utilizando la reasociación de sumas)?
for (f=0 ; f< m ; f++)
suma += (x[f] * y[f]);
P1. (5’0 punto). Se quiere evaluar un benchmark, donde la mayor parte del tiempo de ejecución está en este
bucle.
float s[N], a[N][3]; int i;
for (i=0 ; i<N ; i++)
s[i] = a[i][0] +a[i][1] + a[i][2];
Suponer:
Se pide:
a) Para la primera iteración, dibujar un cronograma para un RISC superescalar de grado 6, algoritmo de
Tomasulo (suponer que el ensamblador usa un único registro base, y no se desenrolla). No es necesaria
una traducción a ensamblador detallada.
b) Para el estacionario, dibujar un cronograma (para la misma máquina) sólo para las instrucciones de
acceso a caché y de suma, donde se demuestre si van a existir bloqueos.
c) En el estacionario, ¿cuál debería ser el número de unidades funcionales para que no exista bloqueo
nunca?
d) En el estacionario, ¿cuál debería ser el Ancho de Banda de RAM para que no exista bloqueo nunca (aquí
suponemos que la matriz está en memoria)?
318
Miguel Ángel Cifredo
macifredo@gmail.com
P2. (1’0 punto). Dibujar en un grafo el camino crítico para muchas iteraciones del bucle (cálculo de la serie de
Fibonacci). Duración Load/Store: 3 ciclos, Suma: 2 ciclos. Resto operaciones 1 ciclo.
serie[0]=1; serie[1]=1;
for (f=2 ; f< m ; f++)
serie[f] = serie[f-1] + serie[f-2];
Segundo bimestre:
T3. (1’5 puntos). Indicar, en OpenMP, la directiva para paralelizar el siguiente código. Indicar que variables deben
ser privadas y cuáles compartidas.
for(i=0; i<10000; i++)
for(j=0; j<1000; j++)
{ b = 2.0 * i;
a+= sqrt(b + j);}
Una de las limitaciones principales de las Arquitecturas Multicapa 2-tier es su alto acoplamiento del cliente
respecto al servidor.
V/F:
En las arquitecturas descentralizadas los componentes actúan como clientes o como servidores, pero nunca
como ambos al mismo tiempo.
V/F:
319
P3 Tenemos una aplicación que se va a ejecutar en un multiprocesador MPM con 16 nodos. Los nodos están
organizados con forma de malla 2D (4x4, se numera empezando por el 0, de izquierda-derecha y de arriba-abajo).
La aplicación consiste en lo siguiente:
Se pide:
a) (3’5 puntos) Escribir el código MPI que implemente este algoritmo (ver nota).
Teniendo en cuenta sólo el punto 4 del algoritmo, y que cada envío de los datos de un nodo a otro se
considera un único mensaje (no se tendrá en cuenta el envío inicial, ni el envío final de los datos por
parte del nodo 0)
c) (2’0 puntos) Calcular el porcentaje medio de mensajes en la bisectriz para la malla 2D y también para el
caso de ejecutar el mismo programa en un anillo bidireccional (los procesadores se numeran en el anillo
sucesivamente 0,1,2,...).
320