¿Qué es el multinúcleo?
Cuando el aumento de rendimiento de las CPU de un solo núcleo encuentra cuellos de botella (el muro de consumo de energía, el muro de frecuencia, el muro de complejidad del pipeline), los ingenieros idearon una solución «simple y directa» pero efectiva: colocar varios núcleos de CPU en un solo chip. Esta lección presentará los principios del multinúcleo, sus ventajas y la limitación de la ley de Amdahl sobre la aceleración en paralelo.
Analogía en la vida diaria: la cocina de un restaurante de comida rápida
Imagina la situación de un restaurante de comida rápida en la hora punta del almuerzo.
Modo de un solo núcleo: solo hay un cocinero. Él intenta acelerar al máximo (aumentar la frecuencia) y optimizar el proceso de cocina (pipeline), pero por mucho que optimice, solo puede hacer un plato a la vez. Los clientes hacen largas filas y se impacientan.
Modo multinúcleo: el dueño contrató a 4 cocineros, cada uno con su fogón. Los 4 cocineros pueden preparar 4 platos diferentes al mismo tiempo. No es necesario que cada cocinero sea más rápido, pero la cantidad total de platos servidos se multiplica varias veces.
Esta es la filosofía de diseño del multinúcleo:Cuando es difícil acelerar más un solo núcleo, mejor poner varios núcleos y hacer que trabajen en paralelo.
Multinúcleo no significa «hacer todo por 4». Los 4 núcleos normalmente comparten la misma memoria, la misma interfaz de disco duro y el mismo sistema operativo; solo se replican a nivel de «unidades de cómputo». Es como 4 cocineros que comparten el mismo refrigerador y el almacén de ingredientes.
Núcleo único vs. multinúcleo: comparación de arquitecturas
El dilema de la CPU de un solo núcleo
A principios de la década de 2000, la industria de las CPU mejoraba el rendimiento aumentando la frecuencia continuamente: de 1 GHz a 2 GHz y luego a 3 GHz. Pero ese camino llegó pronto a su fin.
Los tres grandes cuellos de botella de la frecuencia de un solo núcleo:
- Muro de consumo de energía: cuanto mayor es la frecuencia, el consumo de energía crece exponencialmente; la disipación de calor se convierte en un límite físico.
- Muro de memoria: la CPU es demasiado rápida y la memoria no puede seguirle el ritmo; pierde la mayor parte del tiempo esperando datos.
- Muro de ILP: el paralelismo a nivel de instrucción (ILP) se ha explotado hasta el límite; cada vez hay menos instrucciones que se puedan ejecutar en paralelo en un único flujo de instrucciones.
Alrededor de 2005, Intel y AMD abandonaron sucesivamente la simple «carrera de frecuencias» y se orientaron hacia la ruta multinúcleo.
Estructura de una CPU multinúcleo
La estructura interna de una CPU multinúcleo típica es la siguiente:
+-----------------------------------------------------------+ | CPU 芯片 | | +-------------+ +-------------+ +-------------+ +-----+ | | 核心 1 | | 核心 2 | | 核心 3 | |核 4 | | | L1 Cache | | L1 Cache | | L1 Cache | | ... | | | L2 Cache | | L2 Cache | | L2 Cache | | | | +------+------+ +------+------+ +------+------+ +--+--+ | | | | | | +----------------+----------------+--------------+ | | | +-----+------+ | | 共享 L3 Cache | | +-----+------+ | | | +-----+------+ | | 内存控制器 | ← 连接主内存 (RAM) | +------------+ +-----------------------------------------------------------+
Diseño clave:
- Cada núcleo tiene su propia caché L1 y L2 (privada)
- Varios núcleos comparten una caché L3
- Todos los núcleos acceden a la misma memoria principal a través del controlador de memoria
- El sistema operativo se encarga de asignar las tareas a los diferentes núcleos
Ley de Amdahl: el techo de la aceleración multinúcleo
La intuición de «cuantos más núcleos, más rápido» no siempre es correcta. En 1967, el informático Gene Amdahl propuso una famosa ley.
Ley de Amdahl: la aceleración total de una tarea está limitada por la parte de la tarea que debe ejecutarse en serie.
La fórmula es la siguiente:
加速比 = 1 / (S + P/N) 其中: S = 任务中必须串行执行的比例(0~1) P = 任务中可以并行执行的比例(0~1),且 S + P = 1 N = 并行处理单元的数量(核心数)
Esta fórmula revela un hecho contundente:
Incluso con infinitos núcleos, si solo el 50% de la tarea se puede paralelizar, la aceleración máxima solo llega a 2 veces.
Derivación: cuando N tiende a infinito, P/N tiende a 0, y la aceleración tiende a 1/S.
| Proporción paralelizable | 1 núcleo | 2 núcleos | 4 núcleos | 8 núcleos | 16 núcleos | Infinitos núcleos (límite) |
|---|---|---|---|---|---|---|
| 50% | 1.00x | 1.33x | 1.60x | 1.78x | 1.88x | 2.00x |
| 75% | 1.00x | 1.60x | 2.29x | 2.91x | 3.37x | 4.00x |
| 90% | 1.00x | 1.82x | 3.08x | 4.71x | 6.40x | 10.00x |
| 95% | 1.00x | 1.90x | 3.48x | 5.93x | 9.14x | 20.00x |
| 99% | 1.00x | 1.98x | 3.88x | 7.48x | 13.91x | 100.00x |
Observa el patrón de esta tabla:
- Cuanto mayor es la proporción paralelizable, más notable es la aceleración multinúcleo. Una tarea con un 99% de paralelismo puede acelerarse casi 14 veces con 16 núcleos.
- Cuantos más núcleos, menor es el beneficio marginal. La mejora de aceleración de 2 a 4 núcleos es mucho mayor que de 16 a 32 núcleos.
- La parte en serie es un límite duro. Con un 50% de paralelismo, aunque uses diez mil núcleos, como máximo solo puede ser 2 veces más rápido.
La ley de Amdahl explica por qué «más núcleos no siempre significa más rápido». Si tu programa es principalmente lógica en serie —por ejemplo, un paso de cálculo debe esperar el resultado del paso anterior—, entonces por muchos núcleos que tengas, solo podrán mirar, sin poder ayudar.
Demostración interactiva: calculadora de la ley de Amdahl
El siguiente código Python calcula e imprime la tabla comparativa de aceleración según la ley de Amdahl, y también dibuja la tendencia de la aceleración según el número de núcleos, para que sientas intuitivamente el impacto del «cuello de botella en serie».
Ejemplo
Calculadora de la ley de Amdahl (demostración de ejemplo)
Calcula la aceleración para diferentes proporciones de paralelismo y números de núcleos
Imprime la tabla comparativa y el análisis de tendencia
"""
def amdahl_speedup(parallel_fraction, num_cores):
"""
Fórmula central de la ley de Amdahl
parallel_fraction: proporción ejecutable en paralelo (0.0 ~ 1.0)
num_cores: número de núcleos
Devuelve la aceleración
"""
if num_cores == 1:
return 1.0
serial_fraction = 1.0 - parallel_fraction
speedup = 1.0 / (serial_fraction + parallel_fraction / num_cores)
return speedup
def amdahl_limit(parallel_fraction):
"""
Calcula el límite teórico de aceleración de la ley de Amdahl (cuando el número de núcleos tiende a infinito)
Cuando N→∞, P/N→0, la aceleración → 1/S
"""
serial_fraction = 1.0 - parallel_fraction
if serial_fraction == 0:
return float('inf') # Totalmente paralelizable, en teoría se puede acelerar infinitamente
return 1.0 / serial_fraction
def print_comparison_table(parallel_fractions, core_counts):
"""Imprime la tabla comparativa completa de aceleración"""
# Encabezado de la tabla
header = f"'{'Núcleos':>6} |'"
for pf in parallel_fractions:
header += f" {pf*100:>5.0f}% paralelizable |"
print("=" * (12 + 12 * len(parallel_fractions)))
print(" Ley de Amdahl - Tabla comparativa de aceleración multinúcleo")
print("=" * (12 + 12 * len(parallel_fractions)))
print(header)
print("-" * (12 + 12 * len(parallel_fractions)))
for cores in core_counts:
row = f" {cores:>5} |"
for pf in parallel_fractions:
speedup = amdahl_speedup(pf, cores)
row += f" {speedup:>6.2f}x |"
print(row)
# Límite teórico
print("-" * (12 + 12 * len(parallel_fractions)))
limit_row = f" {'Límite':>5} |"
for pf in parallel_fractions:
limit = amdahl_limit(pf)
if limit == float('inf'):
limit_row += f" Sin límite |"
else:
limit_row += f" {limit:>6.2f}x |"
print(limit_row)
print("=" * (12 + 12 * len(parallel_fractions)))
def analyze_diminishing_returns(parallel_fraction, max_cores=64):
"""Analiza la disminución del beneficio marginal de la aceleración multinúcleo"""
print(f"\nAnálisis del beneficio marginal (proporción de paralelismo {parallel_fraction*100:.0f}%):)
print(f" {'Núcleos':>6} | {'Aceleración':>8} | {'Mejora':>10} | {'Eficiencia':>8}")
print("-" * 42)
prev_speedup = 1.0
cores = 1
while cores <= max_cores:
speedup = amdahl_speedup(parallel_fraction, cores)
improvement = speedup - prev_speedup
# Eficiencia = aceleración real / número de núcleos (100% en aceleración lineal perfecta)
efficiency = (speedup / cores) * 100
print(f" {cores:>6} | {speedup:>8.2f}x | +{improvement:>8.3f}x | {efficiency:>7.1f}%")
prev_speedup = speedup
if cores == 1:
cores = 2
else:
cores *= 2
def compare_serial_vs_parallel(serial_work_ms, parallel_work_ms, num_cores):
"""
Usa el tiempo de una tarea concreta para demostrar la ley de Amdahl
serial_work_ms: tiempo de la parte en serie (milisegundos)
parallel_work_ms: tiempo de la parte paralelizable (en un solo núcleo, milisegundos)
num_cores: número de núcleos disponibles
"""
total_single_ms = serial_work_ms + parallel_work_ms
parallel_fraction = parallel_work_ms / total_single_ms
parallel_time_multi = parallel_work_ms / num_cores
total_multi_ms = serial_work_ms + parallel_time_multi
speedup = total_single_ms / total_multi_ms
print(f"\nSimulación del tiempo de una tarea concreta:)
print(f" Tiempo de la parte en serie: {serial_work_ms} ms (no acelerable)")
print(f" Tiempo de la parte paralelizable: {parallel_work_ms} ms (un solo núcleo)")
print(f" Proporción de paralelismo: {parallel_fraction*100:.0f}%")
print(f"")
print(f" Tiempo total en un solo núcleo: {total_single_ms} ms")
print(f" Tiempo total con {num_cores} núcleos: {total_multi_ms:.1f} ms (serie {serial_work_ms} + paralelo {parallel_work_ms}/{num_cores} = {parallel_time_multi:.1f})")
print(f" Aceleración real: {speedup:.2f}x")
print(f" Límite de Amdahl: {amdahl_limit(parallel_fraction):.2f}x")
return speedup
# ===== Programa principal =====
print("=" * 60)
print(" Calculadora de la ley de Amdahl (ejemplo)")
print("=" * 60)
# 1. Tabla comparativa básica
parallel_levels = [0.50, 0.75, 0.90, 0.95, 0.99]
core_list = [1, 2, 4, 8, 16, 32, 64, 128, 256]
print_comparison_table(parallel_levels, core_list)
# 2. Análisis del beneficio marginal (ejemplo con 75% de paralelismo)
analyze_diminishing_returns(0.75, max_cores=64)
# 3. Simulación de tarea concreta
print("\n" + "=" * 60)
print(" Simulación de escenarios reales")
print("=" * 60)
# Escenario 1: renderizado de video (altamente paralelizable, 95%)
print("\n[Escenario 1] Tarea de renderizado de video)
print(" Parte en serie: leer archivo, inicializar codificador = 10ms")
print(" Parte paralelizable: renderizado por fotogramas = 200ms (un solo núcleo)")
compare_serial_vs_parallel(10, 200, 16)
# Escenario 2: transacción de base de datos (paralelismo medio, 50%)
print("\n[Escenario 2] Procesamiento de transacciones de base de datos)
print(" Parte en serie: escritura de registro, gestión de bloqueos = 50ms")
print(" Parte paralelizable: ejecución de consultas = 50ms (un solo núcleo)")
compare_serial_vs_parallel(50, 50, 16)
# Escenario 3: cálculo puro (altamente paralelizable, 99%)
print("\n[Escenario 3] Cálculo científico/operaciones con matrices)
print(Parte en serie: resumen de resultados = 1 ms)
print(Parte paralela: multiplicación de matrices = 1000 ms (un solo núcleo))
compare_serial_vs_parallel(1, 1000, 16)
# 4. Recomendación del número óptimo de núcleos
print("\n" + "=" * 60)
print(Consejo práctico: ¿cómo saber si necesitas más núcleos?)
print("=" * 60)
# Cálculo del punto de inflexión de la «relación calidad-precio» bajo diferentes proporciones de paralelismo
print(f{'Proporción paralela':>10} | {'Mejora 2 núcleos':>10} | {'Mejora 4 núcleos':>10} | {'Mejora 8 núcleos':>10} | {'Mejora 16 núcleos':>9} | {'Núcleos recomendados':>10})
print("-" * 70)
for pf in [0.50, 0.60, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99]:
ratios = []
prev = 1.0
recommendations = []
for cores in [2, 4, 8, 16]:
sp = amdahl_speedup(pf, cores)
gain = (sp / prev - 1) * 100 # Porcentaje de mejora relativo al nivel anterior
ratios.append(gain)
prev = sp
if gain < 10:
recommendations.append(cores)
# Sugerencia: si la mejora es inferior al 10%, no vale la pena actualizar
suggested = >=16 núcleos if not recommendations else f{recommendations
print(f" {pf*100:>9.0f}% | {ratios[0]:>9.1f}% | {ratios[1]:>9.1f}% | {ratios[2]:>9.1f}% | {ratios[3]:>8.1f}% | {suggested:>10}")
print()
print(Regla práctica:)
print(- Oficina diaria, navegación web (baja proporción de paralelismo): 4-6 núcleos son suficientes)
print(- Compilación de programas, juegos ligeros: 6-8 núcleos)
print(- Renderizado de video, modelado 3D: 8-16 núcleos)
print(- Cálculo científico, entrenamiento de IA: cuantos más núcleos, mejor (pero también hay que considerar la memoria de video y el ancho de banda de la memoria))
Al ejecutar el código anterior, verás varios fenómenos clave:
- La proporción de paralelismo lo determina todo: una tarea con un 50% de paralelismo, incluso usando 256 núcleos, solo logra una aceleración de menos de 2 veces. En cambio, una tarea con un 99% de paralelismo puede acelerarse casi 14 veces en 16 núcleos.
- Rendimientos decrecientes: del núcleo 1 al 2 la mejora es la mayor; después, cada vez que se duplica el número de núcleos, la aceleración adicional es cada vez menor.
- Caída de la eficiencia: al duplicar el número de núcleos, la eficiencia (aceleración real / número de núcleos) sigue disminuyendo. Por eso no es rentable acumular núcleos a ciegas.
Demostración interactiva: animación de asignación de tareas multinúcleo
A continuación se demuestra cómo asignar 12 tareas de cálculo a1 núcleoy4 núcleospara su procesamiento. En el modo de 1 núcleo, las tareas se ejecutan en serie haciendo cola; en el modo de 4 núcleos, 4 tareas se procesan en paralelo simultáneamente. Observa la diferencia en la velocidad de llenado de las barras de progreso y el tiempo de finalización entre los dos modos.
Demostración animada de asignación de tareas multinúcleo (ejemplo)
Cola de tareas1 núcleo (procesamiento en serie)
4 núcleos (procesamiento en paralelo)
Aplicaciones del multinúcleo en el mundo real
Cómo el sistema operativo programa los múltiples núcleos
El planificador (Scheduler) del sistema operativo se encarga de asignar diferentes procesos y subprocesos a diferentes núcleos.
Tiene en cuenta:
- Equilibrio de carga: intenta mantener todos los núcleos ocupados, evitando «un núcleo en apuros y los demás mirando».
- Afinidad de caché: intentar que el mismo subproceso se ejecute en el mismo núcleo para aprovechar la caché privada de ese núcleo.
- Gestión de energía: cuando la carga es baja, apaga algunos núcleos para ahorrar energía.
Multinúcleo vs. Hyper-Threading
Además de los múltiples núcleos físicos, existe una tecnología llamadaMultihilo simultáneo (Hyper-Threading / SMT)。
El multihilo simultáneo hace que un núcleo físico parezca dos «núcleos lógicos». Cuando un subproceso está esperando datos (fallo de caché), el núcleo puede cambiar a otro subproceso para continuar trabajando.
| Opción | Costo de hardware | Mejora de rendimiento | Escenarios adecuados |
|---|---|---|---|
| Multinúcleo físico | Alto (requiere duplicar todo el conjunto de unidades de ejecución) | Casi el doble (caso ideal) | Tareas de carga alta continua |
| Multihilo simultáneo (SMT) | Bajo (solo se necesitan registros adicionales + lógica de control) | Alrededor del 10%-30% | Aprovecha al máximo el tiempo de espera |
Muchas CPU modernas utilizan ambas tecnologías a la vez. Por ejemplo, un Intel Core i7 puede ser «4 núcleos físicos + Hyper-Threading = 8 núcleos lógicos».
Resumen y verificación
Resumen en una frase: los múltiples núcleos ejecutan diferentes tareas en paralelo colocando varios núcleos de CPU en un solo chip. Pero la ley de Amdahl señala que la aceleración real depende de la proporción paralelizable de la tarea: la parte en serie determina el límite máximo de aceleración; cuantos más núcleos, menor es el beneficio marginal.
Preguntas de autoevaluación
- Una tarea tiene un 80% que se puede ejecutar en paralelo. En una CPU de 4 núcleos, ¿cuál es la aceleración según la ley de Amdahl?
Respuesta: S=0.2, P=0.8, N=4. Aceleración = 1/(0.2 + 0.8/4) = 1/(0.2 + 0.2) = 1/0.4 = 2.5x. Es decir, 4 núcleos solo proporcionan una aceleración de 2.5 veces, con una eficiencia de solo el 62.5%.
- Si una tarea puede ejecutarse en paralelo casi al 100%, ¿cuál es el límite teórico? ¿Por qué en la práctica no se alcanza?
Respuesta: el límite teórico es infinito (aceleración = número de núcleos). Pero en la práctica está limitado por: (1) la sobrecarga de dividir y combinar tareas; (2) la latencia de sincronización y comunicación entre núcleos; (3) la competencia por recursos compartidos (como el ancho de banda de la memoria).
- ¿Por qué las CPU de servidores modernos pueden tener 64 o incluso 128 núcleos, pero las computadoras personales suelen tener solo de 4 a 16 núcleos?
Respuesta: las solicitudes que procesan los servidores (como peticiones web, consultas a bases de datos) son naturalmente altamente paralelas: cada solicitud de usuario es independiente, con una proporción de paralelismo cercana al 100%. En cambio, las tareas cotidianas de una computadora personal (como el renderizado del navegador, el procesamiento de documentos de Office) tienen un paralelismo limitado; demasiados núcleos no se aprovechan y solo aumentan el consumo y el costo.