GOGOGOLLC
Volver al Blog
EngineeringMay 19, 20269 min read

Qué pasa realmente cuando tu código corre.

El código fuente no es lo mismo que lo que la máquina ejecuta. Cada línea que escribís baja por cinco capas de traducción, terminando en electrones en un transistor. Acá va la escalera, capa por capa, en el lenguaje más concreto que puedo.

Atakan Özalan

Atakan Özalan

Cofundador & lead de ingeniería, GOGOGO LLC

Qué pasa realmente cuando tu código corre.

Esta pregunta me llega más de lo que esperaría, usualmente de gente que escribe código todo el día pero nunca se asoma a qué pasa después de apretar run. La respuesta honesta toma cinco capas. Cada capa esconde la de abajo. La mayoría del tiempo eso es una gentileza, pero saber cómo funciona cambia cómo escribís la capa de arriba. Esta es la escalera, en el lenguaje más concreto que puedo.

Capa 1 — Código fuente

La capa de arriba es lo que tipeás. if x > 5: print('big') en Python, o console.log("hi") en JavaScript. Parece inglés con algo de puntuación. No es lo que la máquina corre. Es una descripción de lo que querés, en una notación que los humanos diseñaron para que sea legible. La CPU nunca leyó tu código fuente y no lo va a leer.

Lo que esta capa hace bien: coincidir con la forma en que los humanos piensan. Loops, variables, funciones nombradas — corresponden a modelos mentales que ya tenemos. El trabajo del compilador / intérprete es demoler ese match limpiamente en algo que la máquina pueda usar.

Capa 2 — Bytecode / representación intermedia

La mayoría de los lenguajes modernos no compilan source directo a machine code. Primero traducen a una representación intermedia — bytecode, IR, AST-pero-plana. Los archivos .pyc de Python son esto. Los .class de Java son esto. Los motores de JavaScript tienen un IR escondido llamado Ignition. Hasta C pasa por el IR de LLVM antes de llegar al machine code.

El bytecode es un set de instrucciones virtuales de bajo nivel — operaciones más simples que tu source pero todavía no específicas a una CPU real. LOAD_FAST, CALL_FUNCTION, BINARY_ADD, JUMP_IF_FALSE. Unas docenas de operaciones distintas, nombres de variables ya no quedan, ni espacios ni comentarios. Esto es lo que un intérprete de Python realmente camina.

Por qué existe esta capa: es portable. El mismo bytecode corre en cualquier máquina que tenga la VM correcta instalada. El compilador/intérprete tiene un solo trabajo — traducir source a bytecode — y la VM hace el resto.

Capa 3 — Machine code

El bytecode es portable pero lento, porque cada instrucción de bytecode usualmente mapea a múltiples instrucciones reales de CPU. Para ir más rápido, la VM (o un compilador JIT — Just-In-Time) traduce los caminos calientes de bytecode hacia abajo a machine code — las instrucciones binarias que tu arquitectura específica de CPU entiende. MOV, ADD, CMP, JMP en x86-64; sus equivalentes en ARM64 en tu teléfono o Mac serie M.

El machine code es lo que se fetchea desde memoria por la CPU y se ejecuta. Es un stream de bytes — el mnemónico del assembler es una versión legible, pero la CPU ve algo como 48 89 e5 (que es x86-64 para mov rbp, rsp). La CPU no tiene idea de que esto vino de tu script de Python. Solo ejecuta bytes.

Capa 4 — Ciclos de CPU

Las instrucciones de machine code no se ejecutan instantáneamente. Una CPU moderna corre a ~3-4 mil millones de ciclos por segundo. Cada instrucción toma entre 1 ciclo (un ADD simple) y ~100+ ciclos (un cache miss que tiene que ir a buscar a RAM). La CPU tiene pipelines — múltiples instrucciones están en vuelo a la vez, en distintas etapas de fetch / decode / execute / write-back. Hace ejecución fuera de orden — las instrucciones se reordenan por throughput cuando sus dependencias lo permiten. Hace predicción de rama — adivina qué camino va a tomar un if y empieza a ejecutar especulativamente, retrocediendo si se equivocó.

Casi ningún ingeniero de software piensa en esta capa día a día. Pero es por qué tu código es rápido o lento. El pipeline solo se mantiene lleno si las ramas son predecibles. La localidad de memoria importa porque los cache misses cuestan 100× un hit a registro. Las instrucciones vectoriales SIMD procesan 4-8 números en paralelo cuando el layout de datos está bien. Cuando alguien te dice que escribas código cache-friendly, esta es la capa de la que habla.

Capa 5 — Física

Debajo del ciclo de CPU está el evento físico real. Un registro de CPU es un set chico de circuitos biestables hechos de transistores — un par de miles de millones de ellos en el chip. Un transistor es un interruptor — corriente fluye o no, según el voltaje en su gate. ADD a nivel de machine code corresponde a electrones moviéndose por una red de interruptores arreglada como un sumador binario, asentándose en un patrón nuevo que representa la suma. Cada transistor se conmuta en menos de un nanosegundo. La señal de reloj — el latido del chip — coordina miles de millones de estas conmutaciones por segundo.

Debajo de los transistores está la mecánica cuántica. El tuneleo de electrones a través de óxidos de gate es lo que limita cuán chico puede ser un transistor. La disipación de calor — el principio de Landauer dice que borrar un bit tiene un costo termodinámico mínimo — es lo que limita cuán rápido puede correr el chip. Los chips modernos en proceso de 3nm están empezando a chocar con estos pisos físicos, que es por qué las frecuencias de reloj se plataforaron alrededor de 5 GHz en 2010 y el progreso ahora viene del paralelismo, no de frecuencia bruta.

Por qué importa la escalera

Podés escribir software funcional sin saber nada de esto. Decenas de millones de ingenieros lo hacen todos los días. Pero los ingenieros en los que más confío pueden caer abajo por la escalera cuando hace falta. ¿Bug de performance raro? Bajá a la Capa 4 — cuál es el patrón de cache, cuál es la tasa de misprediction de ramas. ¿Pregunta a nivel bytecode? Capa 2. ¿Resultado raro de floating-point? Capa 5 — IEEE 754, modos de redondeo, denormales. La escalera no es conocimiento exótico; es el sustrato que hace que la capa de arriba se comporte como se comporta.

En GOGOGO LLC paso la mayor parte del tiempo en Capa 1, con TypeScript y Python. El runtime multi-agente es código Capa 1 de cabo a rabo. Pero pienso en Capa 4 cada vez que veo un número de costo por llamada que no coincide con mi modelo mental. Pienso en Capa 5 cada vez que alguien pregunta por qué nuestro piso de latencia de agente es ~30ms aunque el modelo vuelva en ~5ms (respuesta: el round-trip de red es física; velocidad de la luz por fibra es una restricción real).

El código es descripción. La CPU es física. Cada capa de abstracción entre ellos es una deuda que vas a pagar cuando algo en la cima se comporte raro. Conocer la escalera es la diferencia entre adivinar y razonar.

Lo que le diría a un ingeniero más joven

No te quedes en Capa 1 para siempre. No tenés que escribir Assembly para ser buen ingeniero — yo no escribí x86 voluntariamente en más de una década — pero tendrías que poder leerlo diez minutos y reconocer qué está pasando. Lo mismo para bytecode. Lo mismo para la física básica de un transistor. Cada una de estas capas eventualmente te va a salvar una semana de debugging. La inversión es chica. El interés compuesto es enorme.

Si querés caminar más abajo en cualquiera de las capas — pipelines, IRs, jerarquías de cache, el I-Ching de los prefetchers de CPU — soy fácil de encontrar. atakanozalan.com.

¿Lo quieres para tu negocio?

Cuéntanos qué flujo construirías primero. Te respondemos con un plan de 4 fases y los agentes que encajan.