# # Benchmarking y Perfilado: Mediciones Precisas del Rendimiento del Código
Optimizar código sin datos objetivos lleva a errores. Una función hash reescrita, supuestamente para mejorar la localidad de caché, ralentizó la ejecución en un 15 %. La intuición es engañosa: se necesitan mediciones precisas de tiempo y eventos del procesador.
Un marco de benchmarking resuelve el problema: múltiples ejecuciones, análisis estadístico, integración con perf. Esto permite identificar los verdaderos cuellos de botella.
Métodos de Temporización de Alta Precisión
La función estándar time() proporciona una resolución de 1 segundo, inaceptable para microoptimizaciones.
clock_gettime()
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
run_test();
clock_gettime(CLOCK_MONOTONIC, &end);
long ns = (end.tv_sec - start.tv_sec) * 1000000000L +
(end.tv_nsec - start.tv_nsec);
Ventajas: resolución de nanosegundos, resistente a ajustes de hora del sistema, portabilidad POSIX.
Contadores de Ciclos de CPU (recomendado)
RISC-V:
static inline uint64_t rdcycle(void) {
uint64_t cycles;
asm volatile ("rdcycle %0" : "=r" (cycles));
return cycles;
}
x86_64:
static inline uint64_t rdtsc(void) {
uint32_t lo, hi;
asm volatile ("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) | lo;
}
ARM64:
static inline uint64_t rdcycle(void) {
uint64_t val;
asm volatile("mrs %0, pmccntr_el0" : "=r"(val));
return val;
}
Resolución de 1 ciclo, sin sobrecarga de llamada al sistema. Desventajas: específico de arquitectura, afectado por cambios de frecuencia.
Análisis Estadístico de Resultados
Una sola ejecución es inútil debido a variaciones de caché, interrupciones del SO, ramificaciones.
Estadísticas Básicas
#define ITERATIONS 1000
uint64_t times[ITERATIONS];
// ... fill times ...
uint64_t min = times[0], max = times[0], sum = 0;
for (int i = 0; i < ITERATIONS; i++) {
if (times[i] < min) min = times[i];
if (times[i] > max) max = times[i];
sum += times[i];
}
uint64_t mean = sum / ITERATIONS;
Estadísticas Avanzadas
La mediana es robusta ante valores atípicos, la desviación estándar indica estabilidad:
qsort(times, ITERATIONS, sizeof(uint64_t), compare_uint64);
uint64_t median = times[ITERATIONS / 2];
double variance = 0;
for (int i = 0; i < ITERATIONS; i++) {
double diff = (double)times[i] - (double)mean;
variance += diff * diff;
}
double stddev = sqrt(variance / ITERATIONS);
Métricas Clave:
- Mínimo: mejor caso (caché caliente)
- Mediana: rendimiento típico
- Desviación estándar: variabilidad
- Máximo: peor caso
Marco de Benchmarking
Interfaz universal para reutilización:
typedef struct {
const char *name;
void (*setup)(void);
void (*run)(void);
void (*teardown)(void);
} benchmark_t;
void benchmark_run(benchmark_t *bench, int iterations);
La implementación incluye:
- Ejecución de calentamiento
- Múltiples mediciones
- Informe estadístico
Ejemplo:
int array[1000];
void setup_array(void) {
for (int i = 0; i < 1000; i++) {
array[i] = i;
}
}
void test_sequential_access(void) {
volatile int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += array[i];
}
}
benchmark_t bench = {
.name = "Sequential Array Access",
.setup = setup_array,
.run = test_sequential_access,
.teardown = NULL
};
benchmark_run(&bench, 1000);
Análisis de Caché con perf
perf stat rastrea contadores de hardware:
$ perf stat -e cache-references,cache-misses ./program
1,234,567 cache-references
12,345 cache-misses # 1.00% miss rate
Eventos Útiles:
cache-references/misses: todos los nivelesL1-dcache-loads/misses: datos L1LLC-loads/misses: último nivel
Comparación de Estructuras: array: 1,2K fallos de caché, lista enlazada: 45K fallos de caché (37 veces peor).
Integrando perf en el Marco
typedef struct {
uint64_t cycles;
uint64_t cache_references;
uint64_t cache_misses;
uint64_t l1_loads;
uint64_t l1_misses;
} perf_counters_t;
Recopilación y agregación automática de contadores de perf_event_open.
Problemas Comunes y Soluciones
Optimizaciones del Compilador
// Malo: bucle eliminado
int sum = 0;
for (...) sum += array[i];
// Bueno
volatile int sum = 0;
Caché Fría vs Caché Caliente
La primera ejecución es más lenta. Solución: calentamiento + métrica separada.
Sobrecarga de Medición
rdcycle() toma ~10 ciclos. Restar o usar pruebas largas.
Ruido del Sistema
- Muchas iteraciones
- Mediana en lugar de media
cpupower frequency-set -g performancetaskset -c 0nice -n -20
Lo que Importa
- Contadores de CPU: rdtsc/rdcycle: estándar de oro para precisión
- Estadísticas: mediana + desviación estándar más fiable que la media
- Eventos de perf: fallos de caché revelan problemas de localidad
- Calentamiento de Caché: esencial para mediciones realistas
- Volatile: evita optimización de código muerto
— Editorial Team
Aún no hay comentarios.