4 - Arquitecturas de paralelismo

El paralelismo se materializa en el hardware de distintas formas. Algunos niveles operan dentro de una misma instrucción, otros combinan muchos núcleos y otros reparten tareas independientes. Entender estas arquitecturas permite mapear correctamente cada tipo de carga al recurso que mejor la ejecuta.

Esta sección recorre el paralelismo desde el nivel más fino (bits e instrucciones) hasta configuraciones con docenas o miles de unidades de cálculo, comparando CPU y GPU y sus casos de uso. También comenta cómo combinar niveles para alcanzar el mejor rendimiento posible.

4. Arquitecturas de Paralelismo

El paralelismo no es solo "usar muchos hilos". Incluye mecanismos en el procesador, extensiones vectoriales y diseños con varios núcleos especializados. Elegir el nivel correcto evita cuellos de botella y maximiza el uso del silicio. Cada nivel se puede aprovechar simultáneamente: una CPU moderna aplica ILP (Instruction Level Parallelism), SIMD (Single Instruction, Multiple Data) y varios núcleos, mientras la aplicación reparte tareas entre ellos.

4.1 Paralelismo a nivel de bit e instrucción

Los procesadores ejecutan operaciones sobre varios bits en una sola instrucción (por ejemplo, sumar enteros de 64 bits en un ciclo). También explotan paralelismo a nivel de instrucción (ILP) mediante canalización (pipelines), ejecución fuera de orden y decodificación de múltiples instrucciones por ciclo. Este paralelismo es transparente para el programador, pero fija el límite base de rendimiento secuencial.

Optimizar ILP implica cuidar dependencias de datos y saltos condicionales: instrucciones que dependen unas de otras reducen el paralelismo que el CPU puede extraer; ramas impredecibles degradan la eficiencia del pipeline por fallos de branch prediction.

4.2 Paralelismo a nivel de datos (SIMD)

Extensiones vectoriales como SSE, AVX o NEON permiten aplicar una misma instrucción sobre varios datos simultáneos. Es ideal para operaciones uniformes sobre arreglos: procesamiento de imágenes, señales o vectores numéricos. Requiere que los datos estén alineados y que las operaciones sean homogéneas para evitar divergencias.

  • Beneficio: gran aumento del throughput (cantidad de trabajo completado por unidad de tiempo) sin aumentar el número de hilos.
  • Condiciones: datos contiguos y alineados, bucles vectorizables y pocas dependencias.
  • Limitación: código con muchas ramas o acceso irregular a memoria pierde eficiencia vectorial.

4.3 Paralelismo a nivel de tareas (task parallelism)

Divide el problema en tareas independientes que pueden ejecutarse en paralelo. Cada tarea puede tener diferente trabajo y duración. Se implementa con hilos, pools de tareas o frameworks específicos. Funciona bien cuando el problema se puede fragmentar en unidades que comparten poco estado y pueden balancearse entre núcleos.

El desafío principal es el balanceo de carga: tareas muy desiguales dejan núcleos ociosos. Estrategias como dividir en unidades más pequeñas (work stealing) ayudan a usar todo el hardware disponible.

4.4 Arquitecturas multi-core vs many-core

Multi-core: unos pocos núcleos generalistas (por ejemplo, 4 a 16) con caches amplias y alto rendimiento por hilo. Son versátiles y soportan tanto cargas mixtas como secuenciales. Many-core: decenas o cientos de núcleos más simples y eficientes en energía, pensados para paralelismo masivo; priorizan throughput sobre el rendimiento por hilo.

En multi-core, la jerarquía de caches (L1, L2, L3 compartida) es crucial para evitar esperas por memoria. En many-core, la comunicación entre núcleos puede ser más lenta y los programas deben minimizar intercambio de datos o usar patrones de paso de mensajes.

4.5 CPU vs GPU: diferencias y casos de uso

Una CPU tiene pocos núcleos potentes y jerarquía de caches compleja; destaca en control de flujo, lógica condicional y tareas heterogéneas. Una GPU posee cientos o miles de unidades simples orientadas a ejecutar la misma instrucción sobre muchos datos (modelo SIMD masivo). Se usa para renderizado, aprendizaje profundo y simulaciones numéricas densas. Las CPUs complementan a las GPUs coordinando tareas, preparando datos y manejando secciones de código con control de flujo complejo.

  • CPU: baja latencia, buen rendimiento en tareas seriales, adecuada para decisiones frecuentes y acceso irregular a memoria.
  • GPU: alto throughput en operaciones uniformes, requiere grandes lotes de datos y código con pocas ramas divergentes.
  • Cooperación: la CPU organiza, prepara y reduce; la GPU procesa masivamente. La eficiencia depende de mover datos de forma coalescente y minimizar transferencias innecesarias.

En la práctica, los sistemas combinan estos niveles: ILP y SIMD para acelerar código interno, paralelismo de tareas para aprovechar todos los núcleos y GPU para workloads (cargas de trabajo) masivamente paralelos. La clave es alinear la carga con la arquitectura adecuada, medir y ajustar: perfiles de calor, consumo y ancho de banda de memoria suelen determinar qué tan lejos se puede escalar.