IPC > GHz: la explicación más simple que realmente recordarás

¿Te fue útil?

Lo has visto: Compras “las CPUs más rápidas” porque la hoja de especificaciones anuncia 3.8 GHz. El servicio sigue lento, el p95 se dispara y el de guardia aprende la diferencia entre marketing y física a las 03:17.

Si recuerdas una sola cosa de este texto, que sea esto: GHz es cuánto rápido marca el metrónomo. IPC es cuánto trabajo se hace por tictac. Tu carga vive en la parte de “trabajo”.

El modelo en una frase

Rendimiento ≈ (frecuencia de reloj) × (IPC) × (número de núcleos útiles) − (esperas por memoria, E/S, bloqueos y otras personas).

GHz es un multiplicador. IPC es otro. Y el término sustraído—esperas—es por qué tu “actualización a 3.8 GHz” puede no hacer nada salvo calentar la sala. No puedes superar con reloj un fallo de caché, una tormenta de fallos de página o un convoy de bloqueos. Sí puedes diagnosticar por qué estás pagando eso.

Qué es realmente el IPC (y qué no es)

IPC son instrucciones por ciclo: en promedio, cuántas instrucciones un CPU retira (termina) en cada pulso de reloj. Si un núcleo funciona a 3.5 GHz, eso son 3.5 mil millones de ciclos por segundo. Si el IPC es 2.0, retira unas 7 mil millones de instrucciones por segundo (aproximadamente; la realidad tiene notas al pie).

Instrucciones retiradas: el cubo de “hecho”

Los CPUs modernos son fábricas fuera de orden. Obtienen, decodifican y especulan. Ejecutan instrucciones en paralelo, las reordenan y a veces tiran trabajo si la especulación falló. IPC suele referirse a instrucciones retiradas: instrucciones que pasaron por la tubería y se volvieron realidad arquitectónica.

IPC no es “cuán inteligente es el CPU”

IPC depende de la carga. El mismo CPU puede mostrar IPC alto para bucles numéricos y un IPC pésimo para seguir punteros por memoria. Otro CPU puede invertir eso. Por eso medir un microservicio y extrapolar a “toda nuestra plataforma” es la vía rápida hacia un rack de arrepentimiento.

Una imagen mental práctica

Piensa en un núcleo de CPU como una cocina de restaurante:

  • GHz es con qué frecuencia el chef principal aplaude para señalar “siguiente paso”.
  • IPC es cuántos platos salen por aplauso.
  • Latencia de caché/memoria es el tiempo esperando por ingredientes.
  • Mispredicciones de rama son cuando el chef prepara el plato equivocado y lo descarta.
  • Bloqueos/contención son cuando la cocina pelea por la única sartén que todos necesitan.

Aplaudir más rápido no ayuda si sigues esperando al camión que trae tomates.

Broma #1: Comprar CPUs por GHz es como contratar camareros por cuán rápido agitan una coctelera vacía.

Por qué los GHz te siguen engañando

La velocidad de reloj es fácil de imprimir en una caja. También es el número menos estable en un servidor moderno, porque la frecuencia es una tregua negociada entre límites de potencia, límites térmicos, reglas de turbo y “qué más está pasando en este socket ahora mismo”.

Turbo es condicional, no un estilo de vida

El “max turbo” anunciado suele ser:

  • para uno o pocos núcleos,
  • por una ventana de tiempo limitada,
  • bajo condiciones específicas de potencia y temperatura,
  • con una carga que no provoque bajada de frecuencia (p. ej., código AVX-intenso en algunas arquitecturas).

Si tu servicio usa todos los núcleos bajo carga (la mayoría lo hace), te importa la frecuencia sostenida con todos los núcleos, no el pico.

La frecuencia puede bajar cuando el trabajo se vuelve “duro”

Algunas mezclas de instrucciones consumen más potencia. Código vectorial puede bajar el reloj. También puede hacerlo estar caliente en racks densos. O malas opciones en la BIOS. GHz no es una propiedad fija; es un resultado.

Y aun si GHz es alto, el IPC puede ser bajo

El IPC bajo ocurre cuando el núcleo pasa ciclos haciendo cualquier cosa además de retirar instrucciones: esperando fallos de caché, recuperando mispredicciones de rama, estancado por dependencias o bloqueado por límites del front-end (no puede obtener/decodificar lo suficiente).

La única gráfica que debes tener en la cabeza

Imagina una línea temporal de CPU:

  • Retirando (bueno): las instrucciones se completan.
  • Limitado por el front-end: no hay suficientes instrucciones alimentadas a la ejecución.
  • Limitado por el back-end: las unidades de ejecución o el subsistema de memoria son el límite.
  • Mala especulación: trabajo descartado por mispredicciones.

GHz alto solo acelera la línea temporal. No cambia cuál cubo domina.

Las cuatro cosas que controlan el IPC

Hay muchos detalles microarquitectónicos, pero puedes mantener el IPC atado a tierra con cuatro grandes palancas que aparecen en producción.

1) La jerarquía de memoria: los hits de caché son el salario

Un acierto de caché es rápido; DRAM es lento; el almacenamiento es glacial. Tu IPC se colapsa cuando la CPU espera datos. Los casos más desagradables son cargas que siguen punteros (tablas hash, B-trees, grafos de objetos) donde cada paso depende de que termine la carga previa.

La latencia importa más que el ancho de banda para mucho código de servidor. Si tu acceso a datos es serial, no estás “usando todo ese ancho de banda de memoria”. Estás esperando una única cadena de fallos.

2) Predicción de ramas: la CPU apuesta por tu futuro

Los CPUs especulan. Si el predictor de ramas acierta, pareces un genio. Si falla, la tubería se vacía y pagas en ciclos. Código con muchas ramas e impredecible puede hundir el IPC incluso si los datos están calientes en caché.

3) Paralelismo a nivel de instrucción (ILP): cuánto puede pasar a la vez

Los núcleos fuera de orden pueden ejecutar múltiples operaciones por ciclo si hay instrucciones independientes listas. Bucles cerrados con dependencias (p. ej., hashing iterativo donde cada paso depende del anterior) limitan el ILP. Verás IPC bajo aun cuando todo esté en caché.

4) El front-end: fetch/decodificar es su propio cuello de botella

Si la CPU no puede alimentar el motor de ejecución lo suficientemente rápido—por fallos de I-cache, problemas de layout de código o límites de decodificación—el IPC baja. Esto aparece en binarios grandes con mala localidad, código JIT con churn o cargas con frecuentes invalidaciones de caché de instrucciones.

Regla práctica: Si no sabes el cuello de botella, asume que es latencia de memoria o contención. Esas dos cosas pagan mi hipoteca.

IPC y cargas reales: BD, web, almacenamiento, JVM

Bases de datos: “limitado por CPU” a menudo significa “esperando memoria mientras mantiene bloqueos”

Las bases de datos pueden ser intensivas en cómputo (compresión, cifrado, ejecución de consultas) pero a menudo están dominadas por patrones de acceso a memoria: búsquedas en índices, churn del buffer pool y estructuras ricas en punteros. Un servidor BD puede mostrar 40–70% de utilización de CPU y aún estar “limitado por CPU” porque los hilos calientes se quedan estancados y el resto está inactivo o bloqueado.

Qué hacer: mide stalls, fallos en LLC y tiempo de bloqueo. Si escalas GHz sin arreglar la tasa de fallos de caché, solo aceleras la espera de la CPU.

Servicios web: ramificaciones, asignaciones y latencia en la cola

Los manejadores de peticiones tienen muchas ramas: comprobaciones de autenticación, routing, feature flags, serialización. Las penalizaciones por mispredicción de rama aparecen como varianza en la latencia tail. Suma presión de GC y ahora tienes tiempo de CPU que no retira instrucciones útiles.

Qué hacer: busca mispredicciones y problemas de caché de instrucciones, y reduce asignaciones. Si debes comprar hardware, prefiere núcleos con buen rendimiento por hilo y buen comportamiento de caché, no solo el turbo anunciado más alto.

Rutas de almacenamiento/E/S: el IPC es una víctima, no la causa

Las pilas de almacenamiento a menudo se atascan en interrupciones, syscalls, cambios de contexto y contención de bloqueos en el kernel o el driver. La CPU puede gastar ciclos en modo kernel sin retirar mucho trabajo de la aplicación. Las métricas de IPC pueden verse extrañas porque mides el “trabajo útil” equivocado.

Qué hacer: mide iowait, tiempo en softirq y tasas de syscall. No “optimices” fijando todo a un núcleo a menos que disfrutes sorpresas.

JVM y runtimes gestionados: el JIT puede elevar el IPC… hasta que no lo hace

La compilación JIT puede producir bucles cerrados con buena localidad y alto IPC. También puede producir sitios de llamada megamórficos, caminos desoptimizados y safepoints frecuentes que convierten tu CPU en un planificador. Si diagnosticas, separa “CPU gastada haciendo trabajo” de “CPU gastada gestionando el runtime”.

Una cita para mantenerte honesto: “La esperanza no es una estrategia.” — General Gordon R. Sullivan

Hechos e historia para usar en discusiones

  • Las tasas de reloj chocaron contra un muro a mediados de los 2000 debido a la densidad de potencia y la fuga; la industria pasó de “relojes más rápidos” a “más núcleos” y “más trabajo por ciclo”.
  • “NetBurst” (era Pentium 4) persiguió GHz altos con pipelines profundos; a menudo perdió frente a diseños con clocks más bajos y mejor IPC, especialmente en código del mundo real.
  • La ejecución fuera de orden existe principalmente para mejorar IPC explotando el paralelismo a nivel de instrucción; es un planificador de hardware que corre a GHz.
  • Los predictors de rama se volvieron sofisticados porque las mispredicciones son caras; pipelines más profundos hicieron que los errores dolieran más.
  • Las cachés crecieron porque la DRAM no siguió el ritmo; la “pared de la memoria” es un tema recurrente en arquitectura.
  • El IPC no es portable entre ISAs; comparar IPC entre x86 y ARM sin contexto suele ser sin sentido porque “una instrucción” no es una unidad uniforme de trabajo.
  • La especulación y la seguridad colisionaron a finales de los 2010s (problemas tipo Spectre); algunas mitigaciones pueden reducir rendimiento al impactar la especulación y los costes de acceso a memoria.
  • Los contadores de rendimiento existen desde hace décadas, pero la “observabilidad de hardware” solo se volvió mainstream en operaciones cuando la eficiencia de flotas y los costes cloud exigieron mejor medición.

Manual rápido de diagnóstico

Este es el flujo de trabajo de “tengo 30 minutos antes de la llamada por el incidente”. El objetivo no es una tesis. Es identificar el cuello de botella dominante y tomar una decisión segura.

Primero: confirma qué tipo de lentitud tienes

  • Latencia aumentada pero CPU baja? Probablemente esperando: E/S, bloqueos, red o stalls de memoria.
  • CPU al máximo y rendimiento topeado? Podría ser limitado por cómputo, pero también por spinlocks, tormentas de syscalls o GC.
  • Sólo la latencia tail mala? Busca contención, pausas de GC, vecinos ruidosos, throttling y thrash de caché.

Segundo: revisa frecuencia y throttling

  • ¿Está la CPU funcionando a las frecuencias sostenidas esperadas?
  • ¿Están en juego límites de potencia (PL1/PL2), throttling térmico o cuotas de cgroup?

Tercero: mide si estás retirando trabajo

  • Mira IPC y stalls usando contadores de rendimiento.
  • Si IPC es bajo y los stalls son altos: causas probables memoria/branch/bloqueos.

Cuarto: decide qué subsistema es el dueño del incidente

  • Alta iowait / tareas bloqueadas → camino de almacenamiento/red.
  • Alta cola de ejecutables y muchos cambios de contexto → planificación/overcommit de CPU.
  • Alta tasa de fallos de caché → localidad de datos, conjunto de trabajo o colocación NUMA.
  • Altas fallas de rama → impredecibilidad en el camino de código o mal layout.

Quinto: elige una acción que puedas justificar

Ejemplos: reducir concurrencia, mover un job ruidoso por lotes, ajustar cuotas de CPU, fijar memoria al nodo NUMA local o revertir un cambio que aumentó el conjunto de trabajo.

Tareas prácticas: comandos, salidas, decisiones

Estas son tareas reales que puedes ejecutar en Linux. Cada una incluye qué significa la salida y qué decisión tomar. Ejecutalas en el host afectado, no en tu portátil. La realidad es hostil a la teoría.

Task 1: Ver modelo de CPU y base/máx anunciados

cr0x@server:~$ lscpu | egrep 'Model name|CPU MHz|CPU max MHz|CPU min MHz|Socket|Core|Thread|NUMA'
Model name:                         Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz
CPU MHz:                            2095.123
CPU max MHz:                        3900.0000
CPU min MHz:                        800.0000
Socket(s):                          2
Core(s) per socket:                 20
Thread(s) per core:                 2
NUMA node(s):                       2

Significado: La máquina anuncia 3.9 GHz máximo, pero el actual es ~2.1 GHz. Eso puede ser normal (reloj base), o puede ser throttling bajo carga.

Decisión: Si las quejas de rendimiento coinciden con MHz actuales bajos bajo alta carga, procede a revisar governor y throttling.

Task 2: Vigilar frecuencias en tiempo real por núcleo

cr0x@server:~$ sudo turbostat --quiet --interval 1 --num_iterations 5
     CPU     Avg_MHz   Busy%   Bzy_MHz   IRQ
       -       2240    62.14     3605   1123

Significado: Avg_MHz es promedio entre todos los núcleos; Bzy_MHz es la frecuencia mientras están ocupados. Aquí, núcleos ocupados suben a ~3.6 GHz.

Decisión: Si Bzy_MHz está muy por debajo de lo esperado (p. ej., estancado cerca de 2.1 GHz), sospecha límites de potencia/thermal throttling, ajustes de BIOS o downclock por AVX.

Task 3: Revisar governor de CPU (común en configuraciones antiguas y algunas imágenes cloud)

cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave

Significado: El kernel está intentando ahorrar energía, lo que puede reducir la capacidad de respuesta de frecuencia.

Decisión: En servidores sensibles a latencia, ponlo en performance (tras confirmar política). Si estás en un DC con límites de potencia, coordina; no sorprendas a Facilities.

Task 4: Comprobar throttling por cgroup (los contenedores mienten educadamente)

cr0x@server:~$ cat /sys/fs/cgroup/cpu.stat
usage_usec 983211234
user_usec 702112345
system_usec 281098889
nr_periods 12345
nr_throttled 9321
throttled_usec 551234567

Significado: nr_throttled y throttled_usec muestran que la carga fue frecuentemente throttled por cuotas de CPU.

Decisión: Si el throttling es significativo durante la ventana del incidente, aumenta cuota/requests de CPU o reduce concurrencia. Comprar GHz más altos no arreglará una cuota.

Task 5: Instantánea rápida “¿es CPU o espera?”

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 6  0      0 812344  22044 912344    0    0     1     3 1800 4200 32  8 58  1  0
12  0      0 809112  22044 913008    0    0     0     0 2100 5200 44 11 45  0  0

Significado: r son hilos ejecutables, b bloqueados, CPU dividido en user/system/idle/iowait. Bajo wa sugiere que no está detenido por almacenamiento.

Decisión: Si r consistentemente supera el número de núcleos y id es bajo, estás saturado de CPU o con alta contención. Mide IPC/stalls.

Task 6: Revisar cola de ejecución y carga relativa a núcleos

cr0x@server:~$ uptime
 10:41:22 up 23 days,  4:12,  2 users,  load average: 38.12, 41.03, 39.77

Significado: Un load average cercano o por encima del número de núcleos indica muchas tareas ejecutables o no interrumpibles.

Decisión: Si la carga es alta pero hay CPU idle, sospecha E/S bloqueada o contención de mutex. Combínalo con vmstat y pidstat.

Task 7: Identificar procesos calientes y tiempo user vs kernel

cr0x@server:~$ pidstat -u -p ALL 1 3
Linux 6.2.0 (server)  01/10/2026  _x86_64_  (80 CPU)

#      Time   UID       PID    %usr %system  %CPU  Command
10:41:40  1001    21433   520.00   80.00 600.00  java
10:41:40     0     1321    10.00  120.00 130.00  ksoftirqd/12

Significado: La JVM usa mucha CPU en user; ksoftirqd indica manejo intenso de soft interrupts (a menudo red).

Decisión: Si ksoftirqd está caliente, revisa tasas de paquetes, distribución de IRQ y ajustes de la NIC. Si domina user CPU, mira IPC y caminos de código.

Task 8: Medir IPC rápidamente con perf (a nivel de sistema)

cr0x@server:~$ sudo perf stat -a -e cycles,instructions,branches,branch-misses,cache-references,cache-misses -- sleep 10
 Performance counter stats for 'system wide':

    38,221,456,789      cycles
    41,002,112,334      instructions              #    1.07  insn per cycle
     7,991,122,010      branches
       211,334,556      branch-misses             #    2.64% of all branches
     2,114,334,221      cache-references
       322,114,990      cache-misses              #   15.23% of all cache refs

      10.001234567 seconds time elapsed

Significado: IPC ~1.07 a nivel de sistema. Tasa de fallos de rama ~2.6% (no está mal). La proporción de fallos de caché es notable.

Decisión: IPC algo bajo más fallos de caché significa problemas de localidad/ conjunto de trabajo. No persigas GHz; persigue fallos, layout de datos, NUMA y contención.

Task 9: Medir IPC para un proceso (útil durante un incidente)

cr0x@server:~$ sudo perf stat -p 21433 -e cycles,instructions -- sleep 10
 Performance counter stats for process id '21433':

    12,112,334,990      cycles
    10,001,223,110      instructions              #    0.83  insn per cycle

      10.000998321 seconds time elapsed

Significado: Este proceso retira <1 instrucción por ciclo en promedio. Eso es “mucha espera” o “trabajo difícil de paralelizar”.

Decisión: Si la app debe ser intensiva en cómputo, esto indica stalls (memoria, bloqueos, syscalls, ramas). Procede a análisis top-down y flame graphs si está permitido.

Task 10: Detectar fallos mayores y tormentas de fallos de página (matan el IPC)

cr0x@server:~$ pidstat -r -p 21433 1 3
#      Time   UID       PID  minflt/s  majflt/s     VSZ     RSS  %MEM  Command
10:42:20  1001    21433   12000.00      0.00 18432324 9321120  28.3  java
10:42:21  1001    21433   11500.00      3.00 18432324 9325540  28.3  java

Significado: Los fallos menores altos pueden ser normales (p. ej., archivos mapeados), pero los fallos mayores significan page-ins desde disco. Eso es latencia, que se convierte en stalls de CPU.

Decisión: Si los fallos mayores aumentan durante picos de latencia, investiga presión de memoria, comportamiento de THP y churn de caché de archivos; añade RAM o reduce el conjunto de trabajo.

Task 11: Revisar balance NUMA y accesos remotos (latencia oculta)

cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0-39
node 0 size: 192000 MB
node 0 free: 81234 MB
node 1 cpus: 40-79
node 1 size: 192000 MB
node 1 free: 23456 MB

Significado: Dos nodos NUMA; el nodo 1 está mucho más asignado. El desequilibrio puede implicar accesos a memoria remota.

Decisión: Si un proceso abarca sockets pero su memoria no es local, fija CPUs/memoria o arregla la colocación del scheduler. La memoria remota es un impuesto que pagas en IPC.

Task 12: Confirmar si transparent huge pages causa variación de latencia

cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

Significado: THP está en always. Esto puede ayudar el throughput pero a veces perjudica la latencia tail por stalls de compactación.

Decisión: Para bases de datos sensibles a latencia, considera madvise o never tras probar. Cambia con cuidado; es una palanca de política, no una superstición.

Task 13: Buscar tiempo de steal (chequeo en la nube)

cr0x@server:~$ mpstat 1 3
Linux 6.2.0 (server)  01/10/2026  _x86_64_  (8 CPU)

10:43:10 AM  all   %usr %nice %sys %iowait %irq %soft %steal %idle
10:43:11 AM  all   41.00  0.00  9.00    0.00 0.00  1.00   8.00 41.00

Significado: %steal muestra tiempo que tu VM quiso CPU pero el hypervisor no te programó. Eso puede parecer “IPC bajo” porque no estás ejecutando.

Decisión: Si el steal es alto durante incidentes, cambia tipos de instancia, evita hosts sobrecommitidos o negocia ubicación. No refactorices código para arreglar el vecino ruidoso de otro.

Task 14: Revisar latencia de E/S de disco rápidamente (porque almacenamiento puede hacerse pasar por CPU)

cr0x@server:~$ iostat -x 1 3
Linux 6.2.0 (server)  01/10/2026  _x86_64_  (80 CPU)

Device            r/s     w/s   r_await   w_await  aqu-sz  %util
nvme0n1         120.0   450.0     2.10     8.40    4.20   96.00

Significado: Alto %util y awaits elevados indican que el dispositivo está saturado o hay colas. Eso significa que los hilos se bloquean, la CPU queda idle o hay cambios de contexto, y tu sueño de “mejorar con CPU” muere silenciosamente.

Decisión: Si await de almacenamiento coincide con picos de latencia, arregla la E/S: reduce escrituras síncronas, ajusta colas, añade dispositivos o separa cargas.

Task 15: Confirmar distribución de interrupciones (las tormentas de IRQ reducen IPC útil)

cr0x@server:~$ grep -E 'eth0|nvme' /proc/interrupts | head
  64:  99112233   0   0   0  IR-PCI-MSI 524288-edge  eth0-TxRx-0
  65:    112233   0   0   0  IR-PCI-MSI 524289-edge  eth0-TxRx-1
  66:     99887   0   0   0  IR-PCI-MSI 524290-edge  eth0-TxRx-2

Significado: Los IRQs están llegando a una CPU (primera columna) mientras otras muestran cero. Eso es un hotspot, a menudo causando carga de softirq y latencia.

Decisión: Ajusta afinidad de IRQ / política RPS/XPS y asegúrate de que las colas de la NIC estén distribuidas. Si lo dejas, escalarás núcleos y seguirás atascado en CPU0.

Task 16: Revisar ancho de banda de memoria vs presión de latencia (heurístico rápido)

cr0x@server:~$ sudo perf stat -a -e cpu/mem-loads/,cpu/mem-stores/ -- sleep 5
 Performance counter stats for 'system wide':

       882,112,334      cpu/mem-loads/
       401,122,110      cpu/mem-stores/

       5.000912345 seconds time elapsed

Significado: Altos recuentos de load/store pueden sugerir comportamiento intensivo en memoria; combínalo con métricas de cache-miss para inferir si esto golpea caché o va a DRAM/remota.

Decisión: Si las operaciones de memoria son altas y el IPC bajo, probablemente tienes un servicio limitado por memoria. Favorece CPUs con caches más grandes, mejor prefetch y subsistema de memoria, no solo GHz.

Tres microhistorias del mundo corporativo

1) Incidente causado por una suposición equivocada: “Más GHz igual a API más rápida”

Una empresa SaaS mediana tenía una API de checkout que se ralentizaba cada vez que una campaña de marketing pegaba. Ingeniería marcó saturación de CPU en la capa de app. Compras propuso un plan: actualizar a CPUs “más rápidas” con relojes anunciados más altos. La migración se programó, los dashboards aprobaron y todos esperaban una victoria.

Tras el corte, la utilización promedio de CPU bajó un poco. La latencia no. La latencia tail empeoró. El postmortem empezó con una diapositiva incómoda: “Compramos piezas de 3.9 GHz, ¿por qué vamos más lentos?”

Al final midieron IPC y fallos de caché. Las nuevas CPUs tenían turbo más alto pero menor caché last-level por núcleo y una topología de memoria diferente. Su servicio estaba dominado por stalls por fallos de caché causados por un gran catálogo en memoria y un montón de feature flags. El IPC bajó bajo carga porque el conjunto de trabajo salía de caché con más frecuencia y el subsistema de memoria estaba más ocupado.

La solución fue poco glamorosa: reducir el conjunto de trabajo (quitar flags muertos, comprimir estructuras de datos calientes), mejorar la localidad (almacenar IDs contiguos, evitar mapas con punteros), y fijar los procesos más calientes a memoria NUMA local. El hardware no era “malo”. La suposición sí.

Conservaron las máquinas, pero las reasignaron a jobs por lotes donde la frecuencia ayudaba. La flota de checkout cambió a CPUs con caché mayor y mejor rendimiento sostenido con todos los núcleos, no la pegatina de turbo más alta.

2) Optimización que salió mal: “Fijemos todo para mejor localidad de caché”

Otra organización ejecutaba un servicio de ingestión de logs sensible a latencia. Alguien notó que las migraciones de CPU eran altas en pico. Decidieron fijar agresivamente los hilos principales de ingestión a un subconjunto de núcleos usando cpusets, esperando mejorar localidad de caché y reducir cambios de contexto.

Por un día, los gráficos se vieron mejor: menos migraciones, latencia promedio ligeramente menor. Luego cambió el tráfico. Un cliente nuevo envió cargas mayores, aumentando costos de parsing y asignaciones de memoria. Los núcleos fijados llegaron al 100% de utilización mientras el resto del sistema permanecía infrautilizado. La latencia tail se disparó y la cola creció.

El problema raíz no era “GHz insuficiente”. Fue hambre autoinfligida por planificación. Fijar quitó la capacidad del kernel para distribuir carga y además concentró interrupciones y trabajo del kernel en los mismos núcleos. El IPC en los núcleos fijados bajó porque pasaban más tiempo en rutas del kernel y esperando memoria por las cargas mayores.

La solución fue fijar sólo lo necesario (unos pocos hilos sensibles), distribuir IRQs correctamente y dejar margen para variabilidad. También introdujeron backpressure y limitaron concurrencia por conexión para evitar que stalls de CPU se convirtieran en colapso de colas.

Broma #2: Fijar CPUs es como tatuarte el nombre en un asiento del aeropuerto: te da seguridad hasta que cambia la puerta de embarque.

3) Práctica aburrida pero correcta que salvó el día: “Mide antes de tocar”

Un equipo fintech ejecutaba un clúster OLTP con SLOs de latencia estrictos. Durante una prueba de carga trimestral, la latencia p99 aumentó 40% en un shard. La primera reacción fue escalar el tipo de instancia y seguir. En cambio, el de guardia siguió un runbook aburrido: capturar contadores base, confirmar frecuencia, revisar throttling, validar localidad NUMA y luego inspeccionar E/S.

Encontraron que la frecuencia de CPU estaba bien. IPC estaba bien. Los fallos de caché eran estables. Pero iowait subió durante la misma ventana. iostat mostró latencias de escritura elevadas y alta utilización del dispositivo. El shard había caído en un host donde otra carga ruidosa saturaba el mismo NVMe. No era un problema de CPU.

La práctica “aburrida” fue simple: captura obligatoria de perf stat, vmstat y iostat durante incidentes, almacenadas con marcas de tiempo. Eso hizo el patrón obvio y evitó una costosa y errónea mejora de CPU.

Lo arreglaron aislando el almacenamiento para la base de datos y ajustando la planificación para que vecinos ruidosos no compartieran dispositivos. La prueba de carga pasó con la clase de CPU existente. Nadie ganó un trofeo, pero el SLO siguió verde, que es el único trofeo que importa.

Errores comunes (síntoma → causa raíz → solución)

1) “La CPU está al 60%, así que no estamos limitados por CPU”

Síntoma: Latencia alta, CPU no pegada, hilos se acumulan.

Causa raíz: Unos pocos hilos calientes están estancados por memoria/bloqueos mientras otros núcleos están ociosos; la CPU agregada oculta cuellos por hilo.

Solución: Usa profiling por hilo y contadores perf; reduce contención; mejora localidad de datos; limita concurrencia para evitar colas.

2) “Necesitamos GHz más altos”

Síntoma: Rendimiento topeado; compras propone SKUs de “reloj más rápido”.

Causa raíz: IPC bajo por fallos de caché, mispredicciones de rama, syscalls o overhead del runtime. La frecuencia no es el multiplicador limitante.

Solución: Mide IPC y tasas de fallos; optimiza caminos calientes; elige CPUs con caché y rendimiento de memoria que encajen con la carga.

3) “Turbo dice 4.2 GHz, por lo tanto tenemos 4.2 GHz”

Síntoma: Benchmarks varían mucho entre ejecuciones; producción más lenta que pruebas de dev.

Causa raíz: Turbo es oportunista; la frecuencia sostenida con todos los núcleos es menor bajo carga real; límites de potencia/thermal cambian el comportamiento.

Solución: Mide Bzy_MHz bajo carga representativa; revisa límites de potencia en BIOS; asegura refrigeración; evita evaluar CPUs solo con microbenchmarks de un hilo.

4) “Optimizamos fijando hilos, ahora está peor”

Síntoma: Latencia promedio mejoró pero tail y backlog empeoran tras un cambio en el patrón de tráfico.

Causa raíz: El sobre-fixado redujo la flexibilidad del scheduler y concentró interrupciones; optimizaciones locales crearon inanición global.

Solución: Fija con moderación; distribuye IRQs; deja núcleos de reserva; valida con múltiples mezclas de tráfico.

5) “Nuestra CPU es lenta en la nube”

Síntoma: CPU parece ocupada pero la salida de trabajo es baja; picos de latencia periódicos.

Causa raíz: Tiempo de steal, throttling o vecinos ruidosos; en realidad no te están ejecutando cuando crees.

Solución: Revisa %steal y estadísticas de throttle de cgroup; elige familias de instancia diferentes; usa hosts dedicados si es necesario.

6) “IPC es bajo, entonces la CPU es mala”

Síntoma: perf muestra IPC <1 y cunde el pánico.

Causa raíz: La carga está limitada por latencia de memoria (seguir punteros), o la medición cubre tiempo de kernel del sistema no relevante al progreso de la app.

Solución: Mide por proceso, correlaciona con fallos de caché y desglose de stalls, y considera cambios en algoritmos/estructuras antes de culpar el silicio.

7) “Actualizamos la CPU y vamos más lentos”

Síntoma: Regresiones en hardware nuevo con carga real.

Causa raíz: Diferentes tamaños de caché, topología NUMA, velocidades de memoria o comportamiento microarquitectónico; también posibles cambios en defaults de BIOS.

Solución: Reajusta NUMA, ajustes de BIOS y parámetros del kernel; re-baselinea contadores perf; valida con pruebas de carga similares a producción.

Listas de verificación / plan paso a paso

Al elegir CPUs (o tipos de instancia): qué hacer y qué evitar

  1. Clasifica la carga: bound por cómputo, latencia de memoria, ancho de banda de memoria, bound por E/S o bound por contención. Si no lo sabes, mide.
  2. Usa benchmarks representativos: tu servicio real, formas de datos reales, concurrencia real, configuraciones reales de GC. Tests sintéticos sirven para sanity, no para compras.
  3. Compara comportamiento sostenido con todos los núcleos: mide bajo carga por 10–30 minutos, no 30 segundos de gloria turbo.
  4. Revisa caché por núcleo: especialmente para servicios en memoria y bases de datos. La caché suele ser una ganancia mayor que +300 MHz.
  5. Valida ajuste NUMA: ¿la carga cabe en un socket? Si no, planifica localidad de memoria y costes de interconexión.
  6. Cuenta mitigaciones de seguridad: asegúrate de comparar manzanas con manzanas en kernel y microcódigo.
  7. Busca riesgo de throttling: limitación de potencia, racks densos, créditos CPU en cloud, cuotas de cgroup.
  8. Evita comprar por GHz pico: prefiere rendimiento por núcleo publicado en condiciones sostenidas y tus propias líneas base de IPC/contadores.

Durante un incidente: flujo mínimo seguro

  1. Captura vmstat 1 5, mpstat 1 3, iostat -x 1 3.
  2. Revisa throttling: cgroups, frecuencia y tiempo de steal.
  3. Mide IPC rápidamente con perf stat (a nivel sistema y para el PID caliente).
  4. Decide: CPU compute vs stalls de memoria vs E/S vs contención. Elige uno.
  5. Toma una acción reversible: reduce concurrencia, mueve una carga ruidosa, ajusta cuota o revierte el cambio que amplió el conjunto de trabajo.
  6. Apunta los contadores y la ventana de tiempo. Tú futuro los necesitará.

Después del incidente: hazlo más barato la próxima vez

  1. Crea un dashboard base: proxy de IPC (instrucciones/ciclo), fallos de caché, cambios de contexto, fallos mayores, iowait, tiempo de steal.
  2. Codifica el runbook con comandos y “qué luce bien”.
  3. Corre una prueba trimestral de “realidad del hardware”: relojes sostenidos, térmicas y checks de throttling bajo carga.
  4. Enseña a compras una frase: “rendimiento sostenido con todos los núcleos y contadores de carga”. Repite hasta obtener financiación.

Preguntas frecuentes

1) ¿Qué número de IPC es “bueno”?

Depende del CPU y la carga. Muchos servicios reales corren alrededor de ~0.5–2.0 IPC. Bucles numéricos cerrados pueden subir más. Seguir punteros puede bajar más. Lo que importa es la tendencia: si el IPC cayó después de un deploy, algo cambió en el comportamiento (conjunto de trabajo, ramas, syscalls, bloqueos).

2) Si el IPC es bajo, ¿significa que necesito un CPU mejor?

Usualmente no. IPC bajo suele significar que estás esperando por memoria, E/S o contención. Un “mejor CPU” ayuda si tiene un subsistema de memoria más fuerte, caches más grandes o mejor predicción de ramas para tu código. Pero la primera ganancia suele ser software: reduce fallos, reduce bloqueos, reduce asignaciones.

3) ¿Por qué los benchmarks muestran grandes ganancias con GHz más altos y la producción no?

Los benchmarks a menudo caben en caché, corren single-thread y evitan E/S y contención de bloqueos. La producción tiene conjuntos de trabajo mayores, más ramas, más syscalls, más interrupciones y más vecinos. GHz ayuda cuando la CPU es el factor limitante, no cuando está esperando.

4) ¿Más núcleos vence a mayor IPC?

Sólo si tu carga escala bien. Muchos servicios están limitados por recursos compartidos: bloqueos, conexiones BD, ancho de banda de memoria o una cola única. Si el escalado se atasca en 16 hilos, comprar 64 núcleos no lo arregla. Mejorar rendimiento por núcleo (mayor IPC) puede ser la mejor inversión.

5) ¿El IPC es comparable entre Intel y AMD? ¿Entre x86 y ARM?

Entre CPUs de la misma ISA y época, las comparaciones de IPC pueden ser útiles. Entre ISAs diferentes se vuelve inestable porque la mezcla de instrucciones difiere. Usa benchmarks de throughput/latencia end-to-end y contadores para entender por qué, en lugar de tratar al IPC como una puntuación universal.

6) ¿Cómo se relaciona el tamaño de caché con el IPC?

Más caché puede mejorar IPC al convertir accesos a DRAM en aciertos de caché. Pero no es sólo tamaño: la latencia, asociatividad, comportamiento de prefetch y topología core-cache importan. Para muchas cargas de servidor, la última caché por núcleo es una especificación crítica.

7) ¿Puede un despliegue cambiar el IPC sin cambiar mucho el uso de CPU?

Sí. Puedes mantener CPU al 60% y aun así ir más lento si la nueva versión aumenta el conjunto de trabajo, provoca más fallos de caché, añade ramas, incrementa syscalls o introduce contención. La utilización de CPU es un instrumento toscamente; IPC y los indicadores de stalls te dicen qué logró ese tiempo de CPU.

8) ¿“GHz no importa” es la conclusión correcta?

No. GHz importa cuando estás verdaderamente limitado por cómputo y retirando instrucciones eficientemente. El punto es que GHz no es suficiente y, a menudo, ni siquiera es estable. Trata la frecuencia como una entrada más y valida con contadores y pruebas de carga reales.

9) ¿Cuál es la forma más rápida de explicar IPC a un stakeholder no técnico?

“La velocidad de reloj es cuán rápido late el CPU. IPC es cuánto trabajo hace por latido. Nuestro servicio está limitado por cuánto espera, no por cuán rápido late.”

Conclusión: próximos pasos que harás

Si vas a cambiar un hábito: deja de tratar los GHz como una garantía de rendimiento. Empieza a tratarlos como una variable.

Haz esto a continuación:

  1. Elige un servicio crítico y captura una línea base bajo carga típica: turbostat, perf stat, vmstat, iostat y estadísticas de throttle de cgroup.
  2. Anota tres números que importan: MHz ocupados sostenidos, IPC y ratio de fallos de caché. Ahora tienes una huella.
  3. Cuando el rendimiento retroceda, compara huellas antes de debatir hardware. Si IPC bajó, busca la espera. Si MHz bajó, busca el throttle. Si ambos están bien, mira E/S, bloqueos y red.
  4. Cuando tengas que comprar hardware, cómpralo para el cuello de botella que mediste: caché y comportamiento de memoria para servicios bound por memoria; frecuencia sostenida para compute-bound; y programación predecible para trabajo sensible a latencia.

No necesitas ser arquitecto de CPU. Solo debes dejar de sorprenderte por los mismos cuellos de botella. IPC es la forma más rápida de dejar de que te engañe un gran número de GHz en letra pequeña.

← Anterior
Fallo en el handshake TLS de SMTP: arregla certificados, cifrados y cadenas correctamente
Siguiente →
Cuellos de botella sin histeria: Cómo medir el límite real

Deja un comentario