10 - Micro-optimizaciones en C

Una vez elegido el algoritmo correcto, quedan mejoras pequeñas para aprovechar CPU y memoria. No cambian el Big-O, pero reducen constantes y evitan trampas frecuentes.

10.1 Costo de comparar enteros vs comparar structs

Comparar enteros es una sola instrucción; comparar structs implica revisar varios campos o usar memcmp, que recorre bytes.

typedef struct {
  int x, y;
  int activo;
} Punto;

int iguales(const Punto *a, const Punto *b) {
  return a->x == b->x && a->y == b->y && a->activo == b->activo; // O(1) pero más comparaciones
}

Si solo importa un campo para el orden, compara ese campo y evita copiar structs completos en swaps.

10.2 Costo de malloc en loops

Reservar memoria dentro de un ciclo agrega overhead y fragmenta el heap.

// Evitar: malloc en cada iteración
for (int i = 0; i < n; i++) {
  int *tmp = malloc(sizeof(int));
  *tmp = i;
  usar(tmp);
  free(tmp);
}

// Mejor: un solo buffer reutilizable
int *tmp = malloc(sizeof(int));
for (int i = 0; i < n; i++) {
  *tmp = i;
  usar(tmp);
}
free(tmp);

Cuando es posible, reserva una vez fuera del loop o usa pools.

10.3 Evitar funciones dentro del loop

Llamar funciones pequeñas puede impedir inlining automático o introducir sobrecarga de stack.

int cuadrado(int x) { return x * x; }

int suma_cuadrados(const int *v, int n) {
  int total = 0;
  for (int i = 0; i < n; i++) {
    int x = v[i];
    total += x * x; // en lugar de cuadrado(v[i]) si el compilador no inlinea
  }
  return total;
}

Los compiladores modernos inlinean mucho, pero en funciones críticas conviene escribir el cálculo directo o declarar static inline.

10.4 Movimiento de código común fuera del loop

Cualquier cálculo que no dependa del índice debe moverse fuera del ciclo.

double escala_vector(double *v, int n, double factor) {
  double k = 1.0 / factor; // calculado una vez
  for (int i = 0; i < n; i++) v[i] *= k;
  return k;
}

Esto reduce operaciones repetidas y mejora la localidad de datos.

10.5 Macros para operaciones simples

Las macros evitan overhead de llamada, pero requieren cuidado para no duplicar efectos laterales.

#define MIN(a, b) ((a) < (b) ? (a) : (b))

int menor_de_tres(int a, int b, int c) {
  return MIN(MIN(a, b), c);
}

Usa paréntesis y limita las macros a expresiones puras; si hay riesgo de evaluar dos veces, prefiere funciones static inline.

10.6 Uso inteligente de punteros

  • Recorrer con punteros: evita recalcular índices y puede mejorar la predicción de saltos.
  • Restrict (C99): indicar que punteros no se solapan (restrict) permite mejores optimizaciones.
  • Evitar aliasing innecesario: menos dependencias mejora vectorización.
int suma_punteros(const int *inicio, const int *fin) {
  int total = 0;
  for (const int *p = inicio; p != fin; p++) total += *p;
  return total;
}