Tema 1: Evaluación de las prestaciones

Antecedentes

En Algoritmia se analizaba la eficiencia de algoritmos secuenciales con un enfoque monovariable (solo la talla nn). Aquí se reutilizan esos modelos y se amplían a algoritmos paralelos, donde el tiempo depende también del número de procesadores pp.

Tiempo de ejecución secuencial: tiempo que tarda el programa en una sola unidad de proceso (procesador o core). Depende de la entrada, el compilador, el programador… pero se ignoran las constantes del sistema y se asume que solo depende de la talla: T(n)T(n).

  • En el análisis a priori se cuentan FLOPs (no pasos).
  • En el análisis a posteriori se mide tiempo (segundos).

Sumatorios que aparecen continuamente:

∑i=1n1=n∑i=1ni≈n22∑i=1ni2≈n33∑i=1log⁡bnk≈klog⁡bn=klog⁡nlog⁡b\sum_{i=1}^{n} 1 = n \qquad \sum_{i=1}^{n} i \approx \frac{n^2}{2} \qquad \sum_{i=1}^{n} i^2 \approx \frac{n^3}{3} \qquad \sum_{i=1}^{\log_b n} k \approx k\log_b n = k\frac{\log n}{\log b}

Ejemplos de conteo (1 FLOP por iteración interna):

<span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (i</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; i</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; i</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-comment)">            // T(n) = n^2 FLOPs</span></span>
<span><span style="color: var(--shiki-color-text)">  </span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (j</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; j</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; j</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    b </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> b </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> y[i][j];</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (i</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; i</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; i</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-comment)">            // T(n) ≈ n^2/2 + n/2 ≈ n^2/2 FLOPs</span></span>
<span><span style="color: var(--shiki-color-text)">  </span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (j</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)">i; j</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; j</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    b </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> b </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> y[i][j];</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (i</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; i</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; i</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span><span style="color: var(--shiki-token-comment)">            // T(n) ≈ n^3/3 FLOPs</span></span>
<span><span style="color: var(--shiki-color-text)">  </span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (j</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)">i; j</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; j</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (k</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)">i; k</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; k</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span></span>
<span><span style="color: var(--shiki-color-text)">      b </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> b </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> y[i][k];</span></span>
<span></span>

Por qué hace falta un modelo

El objetivo de un algoritmo paralelo es reducir el tiempo de ejecución: si el secuencial tarda t(n)t(n), con pp procesadores se busca (como máximo teórico) t(n)/pt(n)/p. El rendimiento real es un conjunto de aspectos muy distintos (tiempo, productividad o throughput, latencia, portabilidad, escalabilidad…), R(p,n,u,… )R(p, n, u, \dots); en este curso se simplifica a R(p,n)R(p, n).

Análisis a priori (teórico)Análisis a posteriori (empírico)
CuándoEn el diseño, independiente de la máquinaSobre una implementación y máquina concretas
Sirve paraElegir la mejor opción y descubrir las tallas adecuadasEncontrar cuellos de botella, dependencias, conflictos… no vistos en el diseño
HerramientasLas de Algoritmia (θ\theta, OO, Ω\Omega) ampliadasMedidas de tiempo

Lo recomendable es combinar ambos: saber modelizar y saber medir.

Parámetros absolutos y relativos

  • Absolutos: dan el coste real (muy importantes en tiempo real). Hay que normalizarlos y aun así no sirven para comparar algoritmos. Se usará el tiempo de ejecución, que es la base de los relativos.
  • Relativos: miden lo bien que el algoritmo aprovecha los recursos y permiten comparar algoritmos (paralelos y secuenciales). Se estudian coste, sobrecarga (overhead), eficiencia, speedup y escalabilidad.

Tiempo de ejecución paralelo

Definición: tiempo transcurrido desde que empieza el primer procesador hasta que termina el último.

Saber cuándo empieza o termina cada procesador no es trivial (es un orden parcial). Como en secuencial, admite análisis a priori y a posteriori. El modelo simplificado depende de la talla y del número de procesadores: T(n,p)T(n, p).

Primera aproximación:

T(n,p)=Tar(n,p)+Tco(n,p)T(n,p) = T_{ar}(n,p) + T_{co}(n,p)

con TarT_{ar} el tiempo de cálculo (aritmético) y TcoT_{co} el de comunicaciones. En un diagrama de Gantt, cada procesador alterna computación, comunicación e inactividad; T(n,p)T(n,p) es lo que tarda el más lento.

Segunda aproximación:

T(n,p)=Tar(n,p)+Tco(n,p)−Tsol(n,p)+Tov(n,p)T(n,p) = T_{ar}(n,p) + T_{co}(n,p) - T_{sol}(n,p) + T_{ov}(n,p)
  • TsolT_{sol}: tiempo de solapamiento entre cálculo y comunicación.
  • TovT_{ov}: tiempo de sobrecarga (esperas, creación de procesos…).
Tipo de algoritmoTiempo
Síncrono (Tsol=0T_{sol} = 0)T(n,p)=Tar+Tco+TovT(n,p) = T_{ar} + T_{co} + T_{ov}
AsíncronoT(n,p)=max⁡(Tar,Tco)+TovT(n,p) = \max(T_{ar}, T_{co}) + T_{ov}, con Tar+Tco2≤max⁡(Tar,Tco)≤Tar+Tco\frac{T_{ar}+T_{co}}{2} \le \max(T_{ar}, T_{co}) \le T_{ar}+T_{co}

TovT_{ov} y TsolT_{sol} son difíciles de estimar, así que en el curso se admite:

T(n,p)≅Tar(n,p)+Tco(n,p)T(n,p) \cong T_{ar}(n,p) + T_{co}(n,p)

Para calcularlo: estudiar TarT_{ar} y TcoT_{co} por separado y luego combinar, suponiendo una arquitectura paralela ideal.

Tiempo de cálculo TarT_{ar}

Tiempo que el algoritmo pasa haciendo cálculos. Se expresa en FLOPs (en segundos en el modelo empírico):

Tar(n,p)=n tcT_{ar}(n,p) = n\, t_c

donde nn es el número de FLOPs y tct_c el tiempo por FLOP.

Tiempo de comunicaciones TcoT_{co}

Tiempo que pasa enviando mensajes (memoria distribuida, MD) o sincronizándose (memoria compartida, MC). Por simplicidad se usa el mismo modelo para ambos, y también para comunicaciones internas y externas.

Un intercambio entre dos procesos vecinos (P2P) cuesta:

Tco(n,p)=ts+L tw(en la praˊctica ts+n tw)T_{co}(n,p) = t_s + L\, t_w \qquad \text{(en la práctica } t_s + n\, t_w\text{)}
  • LL: tamaño del mensaje.
  • tst_s: latencia (start-up time).
  • twt_w: inversa del ancho de banda (tiempo por palabra).
  • tst_s y twt_w pueden variar con la red o el tráfico; ese efecto no se considera.
  • El modelo P2P no vale para operaciones colectivas (difusiones, recolecciones…): se expresan en función de operaciones P2P.
  • En MC la sincronización se puede modelizar como mensajes de tamaño constante (coste constante) o en función de pp.

FLOP y rendimiento teórico

FLOP: una operación en coma flotante básica (suma, resta, multiplicación, división). El coste de otras operaciones en coma flotante se expresa en FLOPs, y el resto (aritmética entera, etc.) normalmente no se cuenta.

FLOPS (o FLOP/s, para no confundir): FLOPs por segundo.

Theoretical Peak Performance en doble precisión (TPPdpTPP_{dp}), en GFLOPS. Es una estimación muy optimista, pero útil en el diseño. Se acepta TPPsp=2 TPPdpTPP_{sp} = 2\, TPP_{dp} (simple precisión).

TPPdp=sockets×coressocket×ciclosGHz×FLOPscicloTPP_{dp} = \text{sockets} \times \text{cores}_{\text{socket}} \times \text{ciclos}_{\text{GHz}} \times \frac{\text{FLOPs}}{\text{ciclo}}

(con varios nodos se multiplica también por chasis y nodos por chasis). Los FLOPs por ciclo dependen de las extensiones vectoriales (SIMD):

ExtensiónFactor (doble precisión)Ejemplo
FMA (Fused Multiply-Add, A=A⋅B+CA = A \cdot B + C)2
SSE (128 bits)2 × nº unidadesNehalem / Westmere: 4 FLOPs/ciclo
SSE2, AVX (256 bits)4 × nº unidadesSandy/Ivy Bridge: 8 FLOPs/ciclo
AVX2 (AVX + FMA)8 × nº unidadesHaswell / Broadwell: 16 FLOPs/ciclo
AVX-5128 × nº unidadesKnights Landing: 8 y 16 FLOPs/ciclo
AVX-512 + FMA16 × nº unidadesSkylake-SP / Cascade Lake: 32 FLOPs/ciclo

Ejemplos (frecuencia base):

ModeloSocketsCores/socketGHzFLOPs/ciclo (DP–SP)TPPdpTPP_{dp} (GFLOPS)TPPspTPP_{sp}
Xeon E5-2603 v4261.716–32326.4652.8
Xeon E5620242.44–876.8153.6
i3-2100123.18–1649.699.2
Ryzen 7 3700X182.216–32281.6563.2
AMD EPYC 74131242.6516–321017.62035.4

Por ejemplo, Xeon E5620: 2×4×2.4×4=76.82 \times 4 \times 2.4 \times 4 = 76.8 GFLOPS.


Coste y sobrecarga

Coste:

C(n,p)=p T(n,p)C(n,p) = p\, T(n,p)

Un algoritmo es de coste óptimo si C(n,p)C(n,p) es proporcional a T(n)T(n). Ejemplo: T(n)=100T(n) = 100, p=10p = 10, T(n,p)=10⇒C(n,p)=100=T(n)T(n,p) = 10 \Rightarrow C(n,p) = 100 = T(n).

Sobrecarga (overhead): tiempo extra que los procesadores consumen entre todos respecto al mejor algoritmo secuencial.

T0(n,p)=C(n,p)−T(n)T_0(n,p) = C(n,p) - T(n)

Speedup y eficiencia

Speedup: ganancia de velocidad del paralelo frente al secuencial.

S(n,p)=T(n)T(n,p)S(n,p) = \frac{T(n)}{T(n,p)}

Cualquier algoritmo paralelo se puede simular en una máquina secuencial ejecutando sus pasos en serie (ignorando comunicaciones), así que:

T(n)≤p T(n,p)  ⟹  S(n,p)≤pT(n) \le p\, T(n,p) \;\Longrightarrow\; S(n,p) \le p
  • Si se usa como T(n)T(n) el paralelo con un procesador, T(n,1)T(n,1), el speedup mide la bondad del diseño paralelo.
  • Si se usa el mejor secuencial conocido, mide la eficacia (prestaciones) real del algoritmo paralelo.
SpeedupNombreCausa
S=pS = pLinealSin penalización por comunicaciones, sin dependencias, todos trabajan a la vez, carga balanceada
S<pS < pSublinealDependencias de datos, el problema no se divide en pp partes concurrentes…
S>pS > pSuperlinealEl secuencial no era óptimo, hay un error de cálculo o efectos colaterales (p. ej., cachés)

Eficiencia: grado de aprovechamiento del sistema (fracción de trabajo útil).

0≤E(n,p)=S(n,p)p=T(n)p T(n,p)≤10 \le E(n,p) = \frac{S(n,p)}{p} = \frac{T(n)}{p\,T(n,p)} \le 1

Ejemplos

1. Producto de matrices secuencial

<span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (i</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; i</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; i</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span></span>
<span><span style="color: var(--shiki-color-text)">  </span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (j</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; j</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">m; j</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">) {</span></span>
<span><span style="color: var(--shiki-color-text)">    c[i][j] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">0.0</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (r</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; r</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">k; r</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">)</span></span>
<span><span style="color: var(--shiki-color-text)">      c[i][j] </span><span style="color: var(--shiki-token-keyword)">+=</span><span style="color: var(--shiki-color-text)"> a[i][r] </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-color-text)"> b[r][j];</span></span>
<span><span style="color: var(--shiki-color-text)">  }</span></span>
<span></span>
T(n,m,k)=∑i=1n∑j=1m(1+∑r=1k2)tc=nm tc+2nmk tc∈θ(nmk)T(n,m,k) = \sum_{i=1}^{n}\sum_{j=1}^{m}\Big(1 + \sum_{r=1}^{k} 2\Big) t_c = nm\,t_c + 2nmk\,t_c \in \theta(nmk)

Con FMA la multiplicación y la suma cuentan como una sola operación: nm tc+nmk tcnm\,t_c + nmk\,t_c.

2. Grafo de tareas

Cuatro tareas independientes T1T_1–T4T_4 de coste 50 y una tarea final T5T_5 de coste 20 que depende de las cuatro. Secuencial: T(n)=4⋅50+20=220T(n) = 4 \cdot 50 + 20 = 220.

ppT(n,p)T(n,p)C=pT(n,p)C = pT(n,p)T0=C−220T_0 = C - 220SSEE
2100+20=120100 + 20 = 120240201.830.916
3100+20=120100 + 20 = 1203601401.830.61
450+20=7050 + 20 = 70280603.140.786
550+20=7050 + 20 = 703501303.140.629

Con 3 procesadores una de las tareas de 50 sigue necesitando una segunda "ronda", así que no mejora respecto a 2. A partir de p>4p > 4: el tiempo y el speedup quedan constantes, el coste y la sobrecarga crecen y la eficiencia decrece (lim⁡p→∞E=lim⁡ctep=0\lim_{p\to\infty} E = \lim \frac{cte}{p} = 0). No compensa usar más de 4 procesadores. El área en la que los procesadores están parados es justo la sobrecarga.

3. Suma de un vector con p=n/2p = n/2

<span><span style="color: var(--shiki-token-comment)">/* Secuencial */</span></span>
<span><span style="color: var(--shiki-color-text)">s </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">0.0</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (i</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">; i</span><span style="color: var(--shiki-token-keyword)">&lt;</span><span style="color: var(--shiki-color-text)">n; i</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">) s </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> s </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> v[i];</span></span>
<span></span>
<span><span style="color: var(--shiki-token-comment)">/* Paralelo, en cada Pi con i = 0..(n/2)-1: reducción en árbol */</span></span>
<span><span style="color: var(--shiki-color-text)">in </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-color-text)">i; des </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-token-keyword)">for</span><span style="color: var(--shiki-color-text)"> (k</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">; k</span><span style="color: var(--shiki-token-keyword)">&lt;=</span><span style="color: var(--shiki-token-function)">log2</span><span style="color: var(--shiki-color-text)">(n) </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span><span style="color: var(--shiki-color-text)"> (i </span><span style="color: var(--shiki-token-keyword)">%</span><span style="color: var(--shiki-color-text)"> des </span><span style="color: var(--shiki-token-keyword)">==</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">); k</span><span style="color: var(--shiki-token-keyword)">++</span><span style="color: var(--shiki-color-text)">) {</span></span>
<span><span style="color: var(--shiki-color-text)">  a[in] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> a[in] </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> a[in</span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)">des];</span></span>
<span><span style="color: var(--shiki-color-text)">  des </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> des</span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
  • T(n)=(1+n) tc≅n tcT(n) = (1 + n)\,t_c \cong n\,t_c
  • T(n,p)≅2log⁡2n  tcT(n,p) \cong 2\log_2 n\; t_c
  • C(n,p)=n tclog⁡2nC(n,p) = n\,t_c\log_2 n, T0(n,p)=n tc(log⁡2n−1)\quad T_0(n,p) = n\,t_c(\log_2 n - 1)
  • S(n,p)=n2log⁡2nS(n,p) = \dfrac{n}{2\log_2 n}, E(n,p)=1log⁡2n\quad E(n,p) = \dfrac{1}{\log_2 n}

No es de coste óptimo: el coste crece como nlog⁡nn \log n frente a nn.

4. Suma con 2≤p≤n/22 \le p \le n/2

Cada procesador suma primero su bloque de n/pn/p elementos y después se hace la reducción en árbol entre los pp resultados:

T(n,p)≅(np+2log⁡2p)tc2tclog⁡2n≤T(n,p)≤n2tcT(n,p) \cong \Big(\frac{n}{p} + 2\log_2 p\Big) t_c \qquad 2t_c\log_2 n \le T(n,p) \le \frac{n}{2}t_c

(los extremos son p=n/2p = n/2 y p=2p = 2). Con n=64n = 64, p=32p = 32: T≅12 tcT \cong 12\,t_c y E≅1/6E \cong 1/6, la misma eficiencia que el algoritmo anterior. Con menos procesadores (p≪np \ll n) el término n/pn/p domina y la eficiencia mejora.

5. Algoritmo iterativo con matrices

Con W,H,A∈Rn×nW, H, A \in \mathbb{R}^{n \times n}, cada producto de matrices cuesta 2n32n^3 y ⊛\circledast, ⊘\oslash son producto y división elemento a elemento:

Repetir
  B = W^T W H
  C = W^T A
  H = H ⊛ C ⊘ B
  D = W H H^T
  E = A H^T
  W = W ⊛ E ⊘ D
Fin repetir

Hay 6 productos de matrices (2+1+2+12+1+2+1) y 4 operaciones elemento a elemento:

T(n)=2n3(2+1+2+1) tc+n2(2+2) tc=12n3tc+4n2tcT(n) = 2n^3(2+1+2+1)\,t_c + n^2(2+2)\,t_c = 12n^3 t_c + 4n^2 t_c

Modelos de rendimiento

Ley de Amdahl (tamaño fijo del problema)

Mide el speedup máximo alcanzable. El tiempo secuencial se divide en una parte no paralelizable α(n)\alpha(n) y una paralelizable β(n)\beta(n):

T(n)=α(n)+β(n)T(n,p)=α(n)+β(n)pT(n) = \alpha(n) + \beta(n) \qquad T(n,p) = \alpha(n) + \frac{\beta(n)}{p}
lim⁡p→∞T(n)T(n,p)=lim⁡p→∞α(n)+β(n)α(n)+β(n)p=1+β(n)α(n)\lim_{p\to\infty}\frac{T(n)}{T(n,p)} = \lim_{p\to\infty}\frac{\alpha(n) + \beta(n)}{\alpha(n) + \frac{\beta(n)}{p}} = 1 + \frac{\beta(n)}{\alpha(n)}

Normalizando α(n)+β(n)=1\alpha(n) + \beta(n) = 1:

S(n,p)=p(p−1) α(n)+1≤1α(n)S(n,p) = \frac{p}{(p-1)\,\alpha(n) + 1} \le \frac{1}{\alpha(n)}
  • Paralelizable al 90 %: S<1/0.1=10S < 1/0.1 = 10, por muchos procesadores que se usen.
  • Paralelizable al 50 %: S<1/0.5=2S < 1/0.5 = 2.

Escrito con la fracción paralela PFPF (en tantos por uno):

S(n,p)=1(1−PF)+PFpS(n,p) = \frac{1}{(1 - PF) + \dfrac{PF}{p}}

Rendimiento efectivo en doble precisión: el speedup de Amdahl por lo que da un solo core.

Redp=1(1−PF)+PFp×ciclosGHz×FLOPscicloRe_{dp} = \frac{1}{(1 - PF) + \dfrac{PF}{p}} \times \text{ciclos}_{\text{GHz}} \times \frac{\text{FLOPs}}{\text{ciclo}}
ModeloTPPdpTPP_{dp}RedpRe_{dp} (PF=0.99PF=0.99)PF=0.95PF = 0.95PF=0.90PF = 0.90PF=0.75PF = 0.75
Xeon E5-2603 v4326.4294.1210.6155.487.0
Xeon Phi 31S1P1003.0306.881.242.317.4
i3-210049.649.147.245.139.7
AMD EPYC 74131017.6827.3473.3308.4150.8

Por ejemplo, Xeon E5-2603 v4 (p=12p = 12, PF=0.99PF = 0.99): S=1/(0.01+0.99/12)≈10.81S = 1/(0.01 + 0.99/12) \approx 10.81 y 10.81×1.7×16≈294.110.81 \times 1.7 \times 16 \approx 294.1.

Ley de Gustafson–Barsis

Speedup escalado:

SG(W,p)=p−(p−1) fsfs=1−PFS_G(W,p) = p - (p-1)\,f_s \qquad f_s = 1 - PF

donde WW es el tamaño computacional del problema.

  • Amdahl responde: ¿cuánto se puede acelerar un problema de tamaño fijo?
  • Gustafson responde: con más recursos, ¿cuánto más grande puede ser el problema manteniendo el tiempo aproximadamente constante?

Escalabilidad

Modelos de isorendimiento: caracterizan la escalabilidad manteniendo constante una métrica de rendimiento.

  • Isotiempo: se mantiene el tiempo; el recurso es el número de procesadores.
  • Isoeficiencia: se mantiene la eficiencia; el recurso es el número de procesadores.

Función de isoeficiencia: ¿cómo debe crecer el tamaño computacional WW en función de pp para que E(n,p)E(n,p) sea constante?

Algoritmo escalable: aquel cuya función de isoeficiencia es lineal en pp. Cuanto menos tenga que crecer WW al aumentar pp, más escalable es.

Enfoque 1: fijar la eficiencia y despejar WW

Ejemplo, con W=n3W = n^3 y T(n)=c n3tcT(n) = c\,n^3 t_c:

AlgoritmoT(n,p)T(n,p)
A(c n3p+b n2p)tc\left(\dfrac{c\,n^3}{p} + \dfrac{b\,n^2}{\sqrt{p}}\right)t_c
B(2c n3p+b n22p)tc\left(\dfrac{2c\,n^3}{p} + \dfrac{b\,n^2}{2\sqrt{p}}\right)t_c
EA=11+bpn cEB=12+bp2n cE_A = \frac{1}{1 + \dfrac{b\sqrt{p}}{n\,c}} \qquad E_B = \frac{1}{2 + \dfrac{b\sqrt{p}}{2n\,c}}
  • Con E=1/3E = 1/3: ambos dan n=bp2cn = \dfrac{b\sqrt{p}}{2c}, es decir, W=b3(2c)3 ppW = \dfrac{b^3}{(2c)^3}\,p\sqrt{p}. Iguales.
  • Con E=1/4E = 1/4: A da W=b3(3c)3 ppW = \dfrac{b^3}{(3c)^3}\,p\sqrt{p} y B da W=b3(4c)3 ppW = \dfrac{b^3}{(4c)^3}\,p\sqrt{p}. Analíticamente distintos, pero asintóticamente equivalentes (θ(p3/2)\theta(p^{3/2})).

Enfoque 2: Kumar et al.

Partiendo de la sobrecarga:

T0(n,p)=p T(n,p)−T(n)  ⇒  T(n,p)=T(n)+T0(n,p)pT_0(n,p) = p\,T(n,p) - T(n) \;\Rightarrow\; T(n,p) = \frac{T(n) + T_0(n,p)}{p}
E(n,p)=T(n)T(n)+T0(n,p)  ⇒  T(n)=E1−E T0(n,p)E(n,p) = \frac{T(n)}{T(n) + T_0(n,p)} \;\Rightarrow\; T(n) = \frac{E}{1 - E}\,T_0(n,p)

Llamando K=E1−EK = \dfrac{E}{1-E} (constante si EE lo es), la función de isoeficiencia es:

W=K T0(W,p)  ⇒  W=f(p)W = K\,T_0(W,p) \;\Rightarrow\; W = f(p)

Cómo se usa: se calcula T0T_0 y se analiza cada término por separado (el de tct_c, el de tst_s, el de twt_w); la isoeficiencia del algoritmo es la del término que exige un crecimiento mayor. Los términos que no dependen de pp no afectan a la escalabilidad.

Enfoque 3: sobrecarga relativa

Mira cómo crece la sobrecarga respecto al mejor secuencial:

T0(n,p)T(n)=p T(n,p)−T(n)T(n)∈O ⁣(prT(n)s)=O ⁣(prWs)\frac{T_0(n,p)}{T(n)} = \frac{p\,T(n,p) - T(n)}{T(n)} \in O\!\left(\frac{p^r}{T(n)^s}\right) = O\!\left(\frac{p^r}{W^s}\right)

Si r≤sr \le s el algoritmo escala óptimamente. Manteniendo constante la eficiencia sale W∈O(pr/s)W \in O(p^{r/s}); si r/s≤1r/s \le 1 la sobrecarga no obliga a que WW crezca más que linealmente. Como además el límite de concurrencia impone W∈Ω(p)W \in \Omega(p), el mejor crecimiento posible es W∈θ(p)W \in \theta(p): exactamente la escalabilidad óptima del enfoque 2.

Interpretación de la isoeficiencia

Función de isoeficienciaEscalabilidad
W∈θ(p)W \in \theta(p)Óptima
W∈θ(plog⁡p)W \in \theta(p\log p)Buena, pero no óptima
W∈θ(p2)W \in \theta(p^2)Peor
W∈θ(p3)W \in \theta(p^3)Mala: exige que el problema crezca muy rápido

Eficiencia escalada

Otra forma de medir la escalabilidad: se multiplican el tamaño del problema y el número de procesadores por el mismo factor rr y se compara el tiempo.

Esca(W,r)=Tpar(W0,p0)Tpar(r W0,  r p0)E_{sca}(W,r) = \frac{T_{par}(W_0, p_0)}{T_{par}(r\,W_0,\; r\,p_0)}
  • Si el tiempo se mantiene aproximadamente constante, Esca≈1E_{sca} \approx 1: comportamiento ideal de escalado débil (weak scaling).
  • Algunos autores consideran cuasi-escalable un algoritmo mientras Esca>0E_{sca} > 0 (Prieto et al., 2003).
  • Multiplicar por 2 es buena estrategia, pero un intervalo de observación mal elegido puede llevar a conclusiones erróneas.

Sistemas híbridos / heterogéneos

Sistemas formados por elementos de distinta naturaleza: CPU + GPU, ordenadores con distintas prestaciones, etc. Los grupos suelen (no necesariamente) hacer los mismos cálculos y comunicaciones.

El modelo se generaliza con varias constantes de cálculo y comunicación. Se obtiene el Ti(n,p)T_i(n,p) de cada grupo por separado y luego se compone T(n,p)T(n,p). El resto del análisis es igual, solo que más complejo algebraicamente.


Resumen de fórmulas

ConceptoFórmula
Tiempo paraleloT(n,p)=Tar+Tco−Tsol+Tov≅Tar+TcoT(n,p) = T_{ar} + T_{co} - T_{sol} + T_{ov} \cong T_{ar} + T_{co}
SíncronoT(n,p)=Tar+Tco+TovT(n,p) = T_{ar} + T_{co} + T_{ov}
AsíncronoT(n,p)=max⁡(Tar,Tco)+TovT(n,p) = \max(T_{ar}, T_{co}) + T_{ov}
CálculoTar=n tcT_{ar} = n\,t_c
Comunicación P2PTco=ts+n twT_{co} = t_s + n\,t_w
CosteC(n,p)=p T(n,p)C(n,p) = p\,T(n,p)
SobrecargaT0(n,p)=C(n,p)−T(n)T_0(n,p) = C(n,p) - T(n)
SpeedupS(n,p)=T(n)/T(n,p)≤pS(n,p) = T(n)/T(n,p) \le p
EficienciaE(n,p)=S/p=T(n)/(p T(n,p))∈[0,1]E(n,p) = S/p = T(n)/(p\,T(n,p)) \in [0,1]
AmdahlS=1(1−PF)+PF/p≤1αS = \dfrac{1}{(1-PF) + PF/p} \le \dfrac{1}{\alpha}
Gustafson–BarsisSG(W,p)=p−(p−1)fsS_G(W,p) = p - (p-1)f_s,   fs=1−PF\;f_s = 1 - PF
Rendimiento teóricoTPPdp=sockets×cores×GHz×FLOPs/cicloTPP_{dp} = \text{sockets} \times \text{cores} \times \text{GHz} \times \text{FLOPs/ciclo}
Rendimiento efectivoRedp=SAmdahl×GHz×FLOPs/cicloRe_{dp} = S_{\text{Amdahl}} \times \text{GHz} \times \text{FLOPs/ciclo}
IsoeficienciaW=K T0(W,p)W = K\,T_0(W,p),   K=E/(1−E)\;K = E/(1-E)
Sobrecarga relativaT0/T(n)∈O(pr/Ws)T_0/T(n) \in O(p^r/W^s); escala óptimamente si r≤sr \le s
Eficiencia escaladaEsca(W,r)=T(W0,p0)/T(rW0,rp0)E_{sca}(W,r) = T(W_0,p_0)/T(rW_0, rp_0)
Grado medio de concurrenciaM(G)=1L(G)∑i=1NciM(G) = \frac{1}{L(G)}\sum_{i=1}^{N} c_i (ver Tema 2 - Diseño de algoritmos paralelos)
Relación superficie-volumenTcomunicacioˊn/TcaˊlculoT_{\text{comunicación}} / T_{\text{cálculo}}