¿Es la llamada de función una barrera de memoria efectiva para las plataformas modernas?


En una base de código que revisé, encontré el siguiente modismo.

void notify(struct actor_t act) {
    write(act.pipe, "M", 1);
}
// thread A sending data to thread B
void send(byte *data) {
    global.data = data;
    notify(threadB);
}
// in thread B event loop
read(this.sock, &cmd, 1);
switch (cmd) {
    case 'M': use_data(global.data);break;
    ...
}

"Sostenlo", le dije al autor, un miembro senior de mi equipo, "¡no hay barrera de memoria aquí! Usted no garantiza que global.data será vaciado de la caché a la memoria principal. Si el hilo A y el hilo B se ejecutarán en dos procesadores diferentes, este esquema podría fallar".

El programador senior sonrió, y explicó lentamente, como si explicara a su niño de cinco años cómo atarse los cordones: "Escucha, muchacho, visto aquí muchos errores relacionados con el hilo, en pruebas de alta carga, y en clientes reales", se detuvo para rascarse la barba larga,"pero nunca hemos tenido un error con este modismo".

"Pero, dice en el libro..."

"¡Silencio!", me silenció rápidamente, " Tal vez teóricamente, no está garantizado, pero en la práctica, el hecho de que se utiliza una llamada a la función es efectivamente una barrera de memoria. El compilador no reordenará la instrucción global.data = data, ya que no puede saber si alguien la usa en la llamada a la función, y la arquitectura x86 se asegurará de que las otras CPU verán esta pieza de datos globales en el momento en que thread B lea el comando desde la tubería. Tenga la seguridad de que tenemos amplios problemas del mundo real de los que preocuparse. No necesitamos invertir un esfuerzo extra en problemas teóricos falsos.

"Tenga la seguridad de mi hijo, con el tiempo usted entenderá para separar el verdadero problema de la I-necesidad-de-obtener-un-PhD no-problemas."

¿Está en lo cierto? Es que realmente un no-problema en la práctica (decir x86, x64 y BRAZO)?

Está en contra de todo lo que aprendí, pero él tiene una barba larga y un aspecto realmente inteligente!

Puntos extra si me puede mostrar un pedazo de código que demuestra que se equivoca!

Author: Craig McQueen, 2012-05-22

4 answers

Las barreras de memoria no son solo para evitar el reordenamiento de instrucciones. Incluso si las instrucciones no se reordenan, puede causar problemas con la coherencia de la caché. En cuanto al reordenamiento, depende de su compilador y configuración. La CPI es particularmente agresiva con el reordenamiento. MSVC w / optimización de todo el programa puede ser, también.

Si su variable de datos compartida se declara como volatile, a pesar de que no está en la especificación la mayoría de los compiladores generarán una variable de memoria alrededor de lecturas y escrituras desde la variable y evitar el reordenamiento. Esta no es la forma correcta de usar volatile, ni para qué estaba destinado.

(Si me quedara algún voto, +1 tu pregunta para la narración.)

 10
Author: Mahmoud Al-Qudsi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-22 08:21:34

En la práctica, una llamada a una función es una barrera compilador, lo que significa que el compilador no moverá los accesos globales de memoria más allá de la llamada. Una advertencia a esto son las funciones de las que el compilador sabe algo, por ejemplo, builtins, funciones inlineadas (tenga en cuenta IPO!) sucesivamente.

Así que, en teoría, se necesita una barrera de memoria del procesador (además de una barrera del compilador) para que esto funcione. Sin embargo, ya que estás llamando a read and write, que son llamadas de sistema que cambian el estado global, estoy bastante seguro que el kernel emite barreras de memoria en algún lugar en la implementación de las mismas. Sin embargo, no hay tal garantía, por lo que en teoría necesita las barreras.

 8
Author: janneb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-22 08:24:32

La regla básica es: el compilador debe hacer que el estado global parezca exactamente como lo codificaste, pero si puede probar que una función dada no usa variables globales, entonces puede implementar el algoritmo de cualquier manera que elija.

El resultado es que los compiladores tradicionales siempre trataban las funciones en otra unidad de compilación como una barrera de memoria porque no podían ver dentro de esas funciones. Cada vez más, los compiladores modernos están creciendo "programa completo" o " enlace tiempo " estrategias de optimización que rompen estas barreras y hará que el código mal escrito falle, a pesar de que ha estado funcionando bien durante años.

Si la función en cuestión está en una biblioteca compartida, entonces no podrá ver dentro de ella, pero si la función es una definida por el estándar C, entonces no necesita -- ya sabe lo que hace la función also por lo que también debe tener cuidado con eso. Tenga en cuenta que un compilador no reconocer un núcleo llamar por lo que es, pero el mismo acto de insertar algo que el compilador no puede reconocer (ensamblador en línea, o una llamada a una función a un archivo ensamblador) creará una barrera de memoria en sí mismo.

En su caso, notify o bien será una caja negra que el compilador no puede ver dentro (una función de biblioteca) o bien contendrá una barrera de memoria reconocible, por lo que es muy probable que esté seguro.

En la práctica, tienes que escribir muy código malo para caer sobre esto.

 2
Author: ams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-22 09:34:07

En la práctica, tiene razón y en este caso específico se implica una barrera de memoria.

Pero el punto es que si su presencia es "discutible", el código ya es demasiado complejo y poco claro.

Realmente chicos, utilizar un mutex u otras construcciones adecuadas. Es la única forma segura de lidiar con subprocesos y escribir código mantenible.

Y tal vez veas otros errores, como que el código es impredecible si se llama a send() más de una vez.

 1
Author: amadvance,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-28 16:32:12