// falseshare.c — el costo de la coherencia de caché (false sharing).
//
// Uso: ./falseshare [millones de iteraciones]
//
// Dos hilos incrementan cada uno SU PROPIO contador: no comparten datos.
// Si los dos contadores están en la misma línea de caché (64 bytes), cada
// escritura invalida la copia del otro CPU y la línea "rebota" entre cachés.
// Con relleno (padding), cada contador vive en su propia línea.
// Se usa una suma atómica (como un contador compartido real): obliga a cada
// CPU a tener la línea en exclusiva en cada incremento.
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define LINE 64
#define ALIGNED __attribute__((aligned(LINE)))

// Juntos: dos long seguidos, en la misma línea.
static struct {
    long a;
    long b;
} ALIGNED together;

// Separados: cada contador ocupa una línea completa.
static struct {
    long a;
    char pad[LINE - sizeof(long)];
    long b;
} ALIGNED apart;

struct arg {
    long *counter;
    long iters;
    int cpu;
};

static void *worker(void *p) {
    struct arg *a = p;
    // Fijar el hilo a un CPU (afinidad), para que sean dos cachés distintas.
    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(a->cpu, &set);
    pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
    for (long i = 0; i < a->iters; i++)
        __atomic_fetch_add(a->counter, 1, __ATOMIC_RELAXED);
    return NULL;
}

static double now(void) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return t.tv_sec + t.tv_nsec / 1e9;
}

static double run(long *x, long *y, long iters) {
    pthread_t t1, t2;
    struct arg a1 = {x, iters, 0}, a2 = {y, iters, 1};
    double t0 = now();
    pthread_create(&t1, NULL, worker, &a1);
    pthread_create(&t2, NULL, worker, &a2);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return now() - t0;
}

int main(int argc, char *argv[]) {
    long iters = (argc > 1 ? atol(argv[1]) : 100) * 1000000L;
    if (sysconf(_SC_NPROCESSORS_ONLN) < 2) {
        fprintf(stderr, "se necesitan al menos 2 CPUs\n");
        return 1;
    }
    printf("distancia entre contadores: juntos %ld bytes, separados %ld bytes\n",
           (long)((char *)&together.b - (char *)&together.a),
           (long)((char *)&apart.b - (char *)&apart.a));

    double t_together = run(&together.a, &together.b, iters);
    double t_apart = run(&apart.a, &apart.b, iters);

    printf("misma línea:      %.3f s\n", t_together);
    printf("líneas distintas: %.3f s\n", t_apart);
    printf("relación:         %.1fx\n", t_together / t_apart);
    return 0;
}
