ELAI S.r.l.

El modelo lee un búfer: ¿el sensor ya lo está sobrescribiendo?

Un ejemplo reproducible explica sobrescrituras, doble y triple búfer, propiedad de los datos y descartes: un tiempo medio corto no basta.

El modelo lee un búfer: ¿el sensor ya lo está sobrescribiendo?

Un resultado plausible a partir de datos que nunca existieron

Un pequeño dispositivo observa las vibraciones de una máquina. Cada diez milisegundos reúne un bloque de medidas y lo entrega a un modelo de inteligencia artificial. El modelo parece suficientemente rápido si miramos su tiempo medio. Sin embargo, algunas respuestas son inexplicables. El problema puede aparecer antes de cualquier error estadístico: mientras el modelo lee las medidas, el circuito de adquisición ya las está sustituyendo por las siguientes. El modelo recibe fragmentos de dos instantes distintos sin que ninguna instrucción tenga que generar un error.

Resumen. Estudiamos cuándo puede reutilizarse una región de memoria sin modificar una entrada que todavía se está leyendo. Construimos una cronología sintética, deducimos una condición de vida útil de los datos, comparamos dos y tres búferes e introducimos la propiedad explícita. El resultado central es concreto: más memoria puede absorber un retraso, pero un protocolo de uso impide sobrescribir un bloque ocupado; si faltan recursos, la pérdida debe hacerse explícita. Todas las cifras proceden de una simulación Python, no de medidas en una placa ni en un producto EL-AI.

Dos actividades que avanzan a la vez

Un búfer es una región de memoria que conserva datos temporalmente. Un productor lo llena y un consumidor lo lee. Aquí el productor es una adquisición asistida por DMA, acceso directo a memoria: un dispositivo transfiere datos sin pedir a la CPU que copie cada muestra. Mientras tanto, la CPU, el procesador que ejecuta nuestro cálculo, puede realizar la inferencia, es decir, aplicar el modelo a los datos. La concurrencia resulta útil, pero ambos actores pueden acceder a la misma memoria.

Imaginemos dos bandejas: una se llena mientras se lee la otra. Esta es la intuición del doble búfer, a menudo llamado ping-pong. La analogía tiene un límite importante: en memoria no hay una mano que avise automáticamente de que la bandeja sigue ocupada. Una dirección válida continúa siendo válida después de cambiar su contenido. Conservar un puntero recuerda dónde leer, pero no congela lo que hay allí. Necesitamos que el bloque entero permanezca estable durante todas las lecturas que dependen de él.

El experimento: ocho bloques y un retraso aislado

Nuestro sensor hipotético produce un canal a 16.000 muestras por segundo. Agrupamos 160 muestras, representadas con dos bytes cada una. Un bloque contiene 320 bytes y tarda 160/16.000 segundos, es decir, 10 ms, en adquirirse. Elegimos estos parámetros para facilitar la lectura; no son especificaciones de hardware probado. Numeramos los bloques desde cero: el bloque 0 se escribe entre 0 y 10 ms, el bloque 1 entre 10 y 20 ms, y así sucesivamente.

El consumidor procesa un bloque a la vez y en orden de llegada. Los tiempos asignados a los ocho bloques son 6, 6, 14, 6, 6, 6, 6 y 6 ms: la media es 7 ms. Suponemos que la entrada debe permanecer disponible durante todo el procesamiento, incluida la espera antes de que empiece la CPU. No añadimos interrupciones, copias, cachés ni costes de sincronización. No estimamos la velocidad de una red: aislamos la relación entre la vida útil de su entrada y la reutilización de memoria.

Con dos búferes cíclicos, uno contiene los bloques pares y el otro los impares. El bloque 2 termina de llegar a los 30 ms; la CPU lo lee entre 30 y 44 ms. Sin embargo, el bloque 4 empieza a llenar el mismo búfer a los 40 ms. Durante cuatro milisegundos se solapan lectura y sobrescritura. Una media inferior a 10 ms no evita este evento: la corrección afecta a cada bloque, no al comportamiento medio de la secuencia.

¿Cuánto tiempo hay realmente antes de reutilizar?

Para calcular ese intervalo, definimos T como el periodo de adquisición, B como el número de búferes y k como el índice del bloque. El bloque k se completa en rₖ=(k+1)T. Su búfer se sobrescribe cuando empieza el bloque k+B, en dₖ=(k+B)T. Aquí d indica el vencimiento de la disponibilidad de los datos, no un plazo clínico ni de control. La diferencia es el tiempo útil después de adquirirlos:

dₖ − rₖ = (B − 1)T Wₖ + Cₖ ≤ (B − 1)T

Cₖ es el tiempo de procesamiento del bloque y Wₖ la espera antes de empezar; ambos se expresan en milisegundos, como T. La segunda línea indica que espera y procesamiento deben terminar antes de la reutilización. Con B=2 solo quedan 10 ms: el búfer que se está llenando no es espacio libre para esperar. Para el bloque 2 obtenemos 0+14≤10, que es falso. Con B=3 pasa a ser 14≤20: ese retraso cabe en la ventana disponible.

Para incluir el efecto sobre los bloques siguientes, calculamos el inicio sₖ como el mayor entre la llegada del bloque y el final fₖ₋₁ del anterior. Después sumamos Cₖ. Es un procesador serie sin desalojo: no interrumpe un bloque para ejecutar otro:

sₖ = max(rₖ, fₖ₋₁) fₖ = sₖ + Cₖ Wₖ = sₖ − rₖ mₖ = dₖ − fₖ

El margen mₖ es positivo si los datos dejan de necesitarse antes de reutilizarlos, y negativo si las duraciones se solapan. El bloque 3 llega a los 40 ms, pero espera hasta los 44; termina a los 50, justo cuando el mismo búfer debe recibir el bloque 5. Su margen es cero. La simulación resuelve esa igualdad con un orden ideal de eventos; un sistema real no debe convertirla en garantía, porque no queda margen para fluctuaciones temporales, sincronización ni el último acceso efectivo.

BloqueLlegada msInicio msFin msReuso con B=2 msMargen ms
0101016204
1202026304
230304440-4
3404450500
4505056604
Simulación determinista: cada fila representa un bloque, no un búfer físico. El rombo marca el inicio de reutilización; la lectura roja termina después. Tres búferes retrasan la reutilización sin acelerar la CPU.
Simulación determinista: cada fila representa un bloque, no un búfer físico. El rombo marca el inicio de reutilización; la lectura roja termina después. Tres búferes retrasan la reutilización sin acelerar la CPU.

La figura se lee de izquierda a derecha: gris es adquisición, azul lectura dentro de la ventana y rojo lectura que supera el instante de reutilización. Los tiempos de CPU son idénticos en ambos paneles. Solo cambia cuándo vuelve el productor a la misma memoria. La tabla también muestra la espera del bloque 3: comparar únicamente cada Cₖ con T ocultaría el efecto del trabajo pendiente.

Qué puede leer realmente el modelo

Un solapamiento no demuestra automáticamente cuánto cambiará la predicción: depende del orden de lectura del modelo. Sí podemos construir un contraejemplo mínimo de coherencia. Consideremos cuatro posiciones representativas, en los índices 0, 40, 80 y 120. El nuevo bloque 4 empieza a sobrescribirlas a los 40, 42,5, 45 y 47,5 ms, respectivamente. A los 44 ms, una instantánea ideal contiene las etiquetas de generación [4, 4, 2, 2]. Indican de qué bloque procede cada valor; no son amplitudes medidas por el sensor.

El dispositivo podría construir características de una señal que nunca se adquirió como tal. No hemos ejecutado una red neuronal ni atribuimos al ejemplo una tasa de error diagnóstico. Demostramos algo más limitado pero fundamental: se incumple el contrato «esta inferencia utiliza el bloque 2». Mejorar la precisión del modelo sobre un conjunto limpio no repara ese contrato.

Dar un propietario a cada búfer

Cambiemos la regla: el productor ya no elige automáticamente la siguiente dirección cíclica, sino que solo puede utilizar un búfer libre. Distinguimos cuatro estados. FREE significa disponible; FILLING reservado para adquisición; READY completo y en espera; READING reservado para el consumidor. El recorrido normal es FREE → FILLING → READY → READING → FREE. «Listo» no significa «libre»: los datos pendientes ya pertenecen a un trabajo que todavía debe leerlos.

La segunda simulación implementa estos estados mediante una cola de eventos. A los 40 ms, con dos búferes, el bloque 2 sigue en lectura y el bloque 3 acaba de quedar listo. No hay ningún búfer libre para el bloque 4. La política elegida descarta por completo el bloque entrante y registra su índice. Se completan los bloques 0, 1, 2, 3, 5, 6 y 7: no se permite sobrescribir, pero existe una pérdida explícita. Con tres búferes no se pierde ningún bloque en esta misma traza finita.

El resultado separa dos requisitos que suelen confundirse: integridad de los datos entregados y completitud de la adquisición. Podemos cumplir el primero y perder el segundo. Un contador secuencial permite detectar el salto de 3 a 5, pero no reconstruye la medida ausente. En un análisis temporal, el hueco debe conservar su posición temporal: concatenar silenciosamente los bloques restantes inventaría una continuidad que el sensor no proporcionó.

Por qué tres búferes no resuelven todos los retrasos

Una reserva absorbe una perturbación transitoria, no un desequilibrio permanente. Repetimos el cálculo cíclico con procesamientos de 12 ms y T todavía igual a 10 ms. Tras el primer bloque, cada llegada añade 2 ms de espera: Wₖ=2k ms. Con tres búferes la ventana es de 20 ms y el margen queda en 20−(2k+12)=8−2k ms. El bloque 4 alcanza margen cero; el bloque 5, sexto de la secuencia, supera la reutilización en 2 ms. El programa verifica exactamente ese primer exceso.

Ningún número finito de búferes conserva indefinidamente todos los datos si este único consumidor sigue siendo más lento que el productor. La propiedad explícita protege los bloques ocupados, pero acaba exigiendo descartar, ralentizar la fuente si es posible o aumentar la capacidad de cálculo. Aquí «descartar un bloque» es una decisión abstracta: en un periférico real hay que verificar cómo detener, desviar o ignorar la adquisición sin que el DMA siga escribiendo en el búfer ocupado.

Copiar, retener o reducir el trabajo

Copiar inmediatamente la entrada a memoria privada puede acortar el tiempo durante el que el búfer del sensor debe permanecer intacto. En la condición de reutilización, Cₖ debe sustituirse por la duración hasta la última lectura del búfer original, no automáticamente por toda la inferencia. Pero la copia debe acabar antes de la sobrescritura, su destino debe permanecer ocupado mientras haga falta y la transferencia consume tiempo y ancho de banda de memoria. No medimos esos costes ni suponemos que copiar sea siempre la mejor solución.

Pasar de dos a tres búferes aumenta solo la memoria de bloques de 640 a 960 bytes: 320 bytes adicionales. No es el pico de RAM de la aplicación. Faltan pesos y activaciones del modelo, pila, colas, alineación y otras estructuras. «Copia cero», leer directamente la memoria adquirida, tampoco significa «espera cero»: elimina una copia, pero puede prolongar la ocupación del búfer. La elección depende del recurso limitante y de las pérdidas que admita la tarea.

De la simulación al firmware: lo que falta verificar

La documentación primaria de Zephyr sobre búferes circulares explica la separación entre acceder a memoria, transferir datos y confirmar los bytes producidos o consumidos. La sección de concurrencia aclara que no existe un control interno general de accesos concurrentes; distingue un productor y un consumidor de los casos múltiples y trata la visibilidad de escrituras entre CPU. Sirve para entender el contrato, no demuestra que nuestro simulador implemente ese núcleo. Leímos Concepts, Instantiation and Usage, Concurrency e Internal Operation; las API dependen de la versión de firmware elegida.

En el programa didáctico, los eventos simultáneos se ordenan así: final de CPU, final de adquisición e inicio de la nueva adquisición. Los estados cambian en un único proceso Python. Esto hace determinista el caso, pero no demuestra atomicidad, coherencia de caché ni orden de operaciones en un microcontrolador. Una prueba física exigiría identificar placa y periférico, versión del entorno, configuración DMA, ubicación de búferes, sincronización y una cronología de accesos. También habría que probar la saturación, no solo el funcionamiento normal.

Respuesta y reproducibilidad

El sensor puede sobrescribir datos que el modelo todavía está leyendo, aunque el procesamiento medio parezca rápido. Importa cuánto tiempo sigue siendo necesario cada bloque y quién puede reutilizar su memoria. En nuestro ejemplo, el tercer búfer absorbe un retraso aislado; la propiedad explícita evita la corrupción, pero con dos búferes exige un descarte. Ninguna observación establece una garantía general de tiempo real. La consecuencia práctica es diseñar juntos modelo, adquisición y política de pérdidas: una respuesta de IA solo tiene significado si sabemos qué datos la produjeron.

El código breve reproduce la cronología cíclica: max conserva la espera y reuse−end mide el margen. El archivo incluye también la simulación de eventos con propiedad, la traza completa, el contraejemplo de generaciones mezcladas y la sobrecarga persistente. Las aserciones verifican el bloque solapado, el descartado y el primer exceso con 12 ms constantes. No hay aleatoriedad y no se necesita semilla. El recorrido temporal es lineal en el número de bloques; la cola de eventos usa un montículo y cuesta O(n log n + nB), con memoria O(nB+n+B): n es el número de bloques y B el de búferes. Buscamos búferes libres y registramos los B estados en cada evento. Con B fijo, los costes son O(n log n) y O(n).

Fuente primaria y código

Zephyr Project Documentation — Ring Buffers.

T = 10  # milliseconds
costs = [6, 6, 14, 6, 6, 6, 6, 6]
for B in (2, 3):
    end = 0
    for k, C in enumerate(costs):
        release = (k + 1) * T
        start = max(release, end)
        end = start + C
        reuse = (k + B) * T
        print(B, k, start, end, reuse - end)

Código, datos e instrucciones · JSON. Cálculos didácticos ejecutados con Python 3.14.0; figuras con Matplotlib 3.11.2. Análisis asistido por IA, sin afirmar revisión por pares ni humana. Portada original ImageGen ilustrativa: no documenta personas, sedes ni instalaciones de EL-AI. Fuentes consultadas el 3 de octubre de 2026.