🔎 Buscar

🖥️ Cómo funciona una computadora

De los bits a la ejecución: binario, representación de datos, arquitectura von Neumann, la CPU y su ciclo, jerarquía de memoria, ensamblador, compilación y optimización. Teoría y práctica.

Fundamentos CS📖 Contenido

Cómo funciona una computadora

Todo el código que escribes —Python, Go, PHP, SQL— termina ejecutándose como instrucciones de máquina sobre una CPU. Esta página te enseña qué pasa realmente: cómo se guardan los datos en bits, cómo la CPU los procesa, y qué es la jerarquía de memoria. Es la base que explica por qué un programa es rápido o lento, y por qué un byte no es un carácter.

Bits, bytes y sistema binario

Una computadora trabaja con bits: un bit es un 0 o un 1 (una diferencia de voltaje en un transistor). 8 bits = 1 byte. Con 1 byte representas 256 valores (2⁸).

1 byte = 8 bits:  1 0 1 1 0 0 0 1  →  177 en decimal

bits:     128  64  32  16   8   4   2   1
          1    0   1   1    0   0   0   1   = 128+32+16+1 = 177
Unidad Tamaño
1 bit 0 o 1
1 byte 8 bits (256 valores)
1 KB 1024 bytes
1 MB 1024 KB ≈ 1 millón de bytes
1 GB 1024 MB ≈ 1.000 millones de bytes

💡 ¿Por qué 1024 y no 1000? Porque el hardware usa potencias de 2: 2¹⁰ = 1024. La industria de discos duros usa 1000 (por eso un disco “de 1 TB” muestra ~931 GB en el sistema).

Representación de números y caracteres

  • Enteros: binario directo. Con n bits representas de -2^(n-1) a 2^(n-1)-1 (si usas signo). El complemento a dos es cómo se representan los negativos (el bit más significativo es el signo).
  • Flotantes: se guardan en IEEE 754: signo + exponente + mantisa. Por eso 0.1 + 0.2 ≠ 0.3 (visto en Programación).
  • Caracteres: un carácter es un número. ASCII usa 7 bits (128 caracteres: A=65, a=97). UTF-8 es el estándar moderno: usa 1-4 bytes por carácter y es compatible con ASCII.
'A' = 65 decimal = 0x41 = 01000001 binario
'ñ' en UTF-8 = 2 bytes: 11000011 10110001

⚠️ Terminología: 0x41 es hexadecimal (base 16, dígitos 0-9 y A-F). Los ingenieros usan hex porque 1 dígito hex = 4 bits exactos. 0b1011 es binario, 0101 octal. En C/Go/JS: 0x41, en Python hex(65).

La arquitectura de von Neumann

Casi toda computadora moderna sigue el modelo de von Neumann: una unidad central de procesamiento (CPU) conectada a una memoria que guarda tanto los datos como el programa.

                ┌─────────────────────────────────────┐
                │              CPU                    │
                │  ┌───────┐  ┌───────┐  ┌──────────┐ │
 Datos  ◀───────▶ │  ALU   │  │Regis- │  │ Control  │ │
                │  │(opera)│  │tros   │  │ (CU)     │ │
                │  └───────┘  └───────┘  └──────────┘ │
                └──────┬──────────────────────┬────────┘
                       │ (bus de datos/dir.)   │
                ┌──────▼──────────────────────▼────────┐
                │              MEMORIA (RAM)            │
                │   programa (instrucciones) + datos    │
                └───────────────────────────────────────┘
  • ALU (Unidad Aritmético-Lógica): hace las operaciones (sumar, restar, comparar, AND/OR).
  • Registros: almacenamiento ultrarrápido dentro de la CPU (decenas de bytes).
  • CU (Unidad de Control): decodifica las instrucciones y coordina todo.
  • Reloj: cada «tic» permite una etapa del procesamiento. La velocidad se mide en GHz (miles de millones de tics por segundo).

El ciclo de ejecución (fetch-decode-execute)

La CPU ejecuta las instrucciones en un ciclo infinito:

  1. Fetch (buscar): lee la siguiente instrucción de la memoria (apuntada por el PC — Program Counter / puntero de instrucción).
  2. Decode (decodificar): descifra qué instrucción es y qué operandos usa.
  3. Execute (ejecutar): la ALU hace la operación, escribe el resultado.
PC → memoria: leer instrucción → decodificar → ejecutar → PC++ → repetir

Cada instrucción de máquina es trivialísima: «suma el registro 1 al registro 2». Un programa es millones de estas instrucciones ejecutadas por segundo.

Lenguaje ensamblador y lenguaje de máquina

La CPU solo entiende lenguaje de máquina: bytes (opcodes) que codifican operaciones. El ensamblador es la representación legible de ese lenguaje.

Este código C:

int suma(int a, int b) {
    return a + b;
}

Se compila a ensamblador x86-64 así:

suma:
        push   rbp
        mov    rbp, rsp
        mov    DWORD PTR [rbp-4], edi    ; a
        mov    DWORD PTR [rbp-8], esi    ; b
        mov    eax, DWORD PTR [rbp-4]
        add    eax, DWORD PTR [rbp-8]    ; eax = a + b
        pop    rbp
        ret                              ; devuelve eax

💡 Los registros son los «dedos» de la CPU: eax, rbp, rsp… El compilador decide cómo encajar tu código en los registros y la memoria. Puedes verlo tú mismo: gcc -S programa.c genera el .s con el ensamblador.

Instrucciones fundamentales

Tipo Ejemplos Qué hace
Movimiento de datos mov, load, store Copiar entre memoria y registros
Aritmética add, sub, mul, imul Operaciones matemáticas
Lógica and, or, xor, shl Operaciones de bits
Control jmp, jne, call, ret Saltar, decidir, llamar funciones
Comparación cmp, test Comparar (fija banderas/eflags)

ISA: el contrato del hardware

Una ISA (Instruction Set Architecture) es el contrato entre el software y el hardware: qué instrucciones existen. El mismo código fuente se compila distinto para cada ISA.

ISA Dónde vive
x86-64 CPUs Intel/AMD (la mayoría de servidores y PCs)
ARM64 / AArch64 Móviles, M1/M2/M3 de Apple, Nube (Graviton)
RISC-V Open source, creciendo en todo

⚠️ CISC vs RISC: x86 es CISC (instrucciones complejas y largas); ARM/RISC-V son RISC (instrucciones simples y uniformes). RISC permite pipeline y consumo eficiente — por eso domina móviles y la nube moderna.

La jerarquía de memoria

La memoria no es un solo bloque: es una pirámide de velocidades y tamaños. Lo más rápido es lo más caro y pequeño.

            Velocidad            Capacidad
Registros     ~1 ciclo           ~100 B        ← lo más rápido
Caché L1      ~1-4 ciclos        ~64 KB
Caché L2      ~10 ciclos         ~1 MB
Caché L3      ~40 ciclos         ~10-40 MB
RAM           ~100 ns            ~16-512 GB
SSD/Disk      ~0.1-10 ms         ~TB            ← lo más lento

💡 Regla de oro: la CPU es decenas de miles de veces más rápida que la RAM. Un cache miss (ir a RAM) puede costar lo mismo que cientos de operaciones aritméticas. Los programas rápidos son los que aprovechan la caché: acceso contiguo a la memoria (arrays) en vez de saltos aleatorios (listas enlazadas).

Localidad

Los programas eficientes tienen localidad:

  • Localidad temporal: si accedes a un dato, probablemente lo vuelvas a acceder pronto (usa el mismo dato en un bucle).
  • Localidad espacial: si accedes a una dirección, probablemente accederás a las vecinas (recorrer un array en orden).
# Lento: acceso salteado (malo para caché)
for i in range(0, n, 64):
    total += arr[i]

# Rápido: acceso contiguo (bueno para caché)
for x in arr:
    total += x

Compilación y optimización

El compilador traduce tu código de alto nivel a lenguaje de máquina y, de paso, lo optimiza.

gcc -O0 programa.c   # sin optimizar (fiel al código)
gcc -O2 programa.c   # optimizado (rápido)
gcc -O3 programa.c   # optimización agresiva

Optimizaciones clásicas que el compilador hace solas:

  • Inlining: sustituye la llamada a una función pequeña por su cuerpo.
  • Eliminación de código muerto: borra código que nunca se usa.
  • Loop unrolling: duplica el cuerpo del bucle para reducir saltos.
  • Reordenación: mueve operaciones para mejorar el pipeline.

⚠️ Por eso medir importa: -O2 puede hacer tu programa 10× más rápido sin tocar una línea. Y por eso los benchmarks mal hechos te engañan (el compilador puede «optimizar» tu código de prueba y eliminarlo entero).

Ejercicio práctico: mira tu código por dentro

  1. Escribe un programa en C simple y compílalo viendo el ensamblador:
gcc -S -O0 programa.c -o programa.s    # ensamblador sin optimizar
gcc -S -O2 programa.c -o programa_o2.s # ensamblador optimizado
diff programa.s programa_o2.s          # mira cuánto cambió
  1. Ejecuta y mide la diferencia:
gcc -O0 programa.c -o p0 && time ./p0
gcc -O3 programa.c -o p3 && time ./p3
  1. Inspecciona el binario:
file programa              # qué tipo de binario es
objdump -d programa | head  # desensambla el binario

Terminología que debes dominar

Término Significado
Bit / byte Unidad mínima / 8 bits
Endianness Orden de los bytes en memoria (little = byte menos significativo primero; x86 es little)
Registro Almacenamiento interno de la CPU
PC / puntero de instrucción Dónde va la CPU en el programa
ISA Contrato de instrucciones del hardware
Ensamblador Lenguaje legible de la máquina
Opcodes Bytes que codifican las instrucciones
Caché Memoria rápida entre CPU y RAM
Cache miss / hit Fallo / acierto de caché
Pipeline Ejecutar etapas de varias instrucciones en paralelo
Clock (reloj) Tics que sincronizan la CPU (GHz)
Overclocking / throttling Subir / limitar la frecuencia del reloj
Little endian Orden de bytes típico de x86

Para profundizar

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.