7

Tengo una pregunta con respecto a las optimizaciones que el compilador puede hacer.Optimización del compilador, ¿Thread Safe?

El código siguiente hablará por sí mismo (esto es un ejemplo):

typedef struct test 
{ 
    short i; 
}    s_test; 

int function1(char *bin) 
{ 
    s_test foo; 

    lock(gmutex); 
    foo.i = *(int*)bin * 8; 
    unlock(gmutex); 

    sleep(5); 
    // 
    // Here anything can happen to *bin in another thread 
    // an inline example here could be: *(volatile int *)bin = 42; 
    // 

    int b = foo.i + sizeof(char*); 

    return (b > 1000); 
} 

Podría el compilador nunca sustituir a las últimas líneas con

return ((*(int*)bin * 8 + sizeof(char*)) > 1000); 

No parecía ser el caso utilizando -O2 o -O3 con gcc 4.4 pero ¿podría ser el caso con otros compiladores y con otros indicadores de compilación?

+5

Reemplazar una variable por una expresión no es exactamente una optimización, ¿o sí? – EJP

+1

Para algunos problemas relacionados con la optimización del compilador y la seguridad de hilos, consulte http: // stackoverflow.com/questions/2001913/c0x-memory-model-and-speculative-loads-stores y, en particular, los documentos vinculados allí. – janneb

+0

Los cambios en * bin también pueden ocurrir durante su exclusión mutua, a menos que proteja todos los accesos a _ALL_ objetos de memoria * a los que bin pueda apuntar con el gmutex. Lo único que está protegido por su mutex es la variable local foo, que quizás no sea lo que usted quería. – slartibartfast

Respuesta

5

No creo que el compilador haga ese tipo de optimización.

unlock(gmutex) 

esta es la función, el compilador no puede asumir que el valor señalado por Bin se cambiará en función de desbloqueo o no.

por ejemplo, tal vez la basura viene de un globo. Entonces la optimización para bin no puede cruzar la llamada a la función.

+1

El compilador ciertamente no puede hacer esta optimización. De lo contrario, no habría una forma segura de implementar una función de bloqueo. – ugoren

+2

@ugoren, el modelo de bloqueo e hilo solo viene con C11. Antes de que todas las apuestas se suspendan por confiar en el estándar C en sí mismo con respecto a cualquier motivación con este respecto. Entonces, la programación de subprocesos con C antes de C11 necesita cuidados especiales. En particular, el aliasing como se hace en el ejemplo no es una buena idea. Aquí probablemente esté bien, ya que 'bin' es' char * ', pero supongo que Dpp no ​​controla muy bien este aspecto de C :) –

+0

A menos que tengas el indicador" no asumir ningún aliasing across functions "(y de hecho tener una función que puede cambiar una variable local). Afortunadamente, no veo esta opción documentada más en MSDN. – selbie

4

Su ejemplo es innecesariamente complicado porque está leyendo bin a través de un tipo diferente al que está declarado. Las reglas de aliasing son bastante complicadas, char es incluso especial, no comentaría sobre eso.

Supongamos que su declaración sería int* bin (y por lo tanto no tendría que lanzar el puntero) un compilador no tendría el derecho de reordenar sentencias a través de llamadas a funciones, sino que forman los denominados puntos de secuencia. El valor de *bin antes y después de la llamada a unlock podría ser diferente, por lo que tiene para cargar el valor después de la llamada.

Editar: como señaló Slartibartfast, por este motivo, es esencial que unlock es (o contiene) una llamada de función y no es sólo una macro que se resuelve en una secuencia de operaciones por el compilador.

+0

Perdón por interceptar nuevamente: incluso si unlock() es solo una declaración, contendría un ";" que es un punto de secuencia como una llamada a función que intentas hacer frente a la cortina, es solo que esto no ayudará en ese caso. Para hacerse una idea de las complejidades de C multiproceso junto con los estándares hasta C99, eche un vistazo a los archivos comp.lang.c y comp.std.c. – slartibartfast

1

Como dije en la respuesta directa: su mutex no está protegiendo nada en mi humilde opinión. La matriz de caracteres a la que * bin apunta puede modificarse durante el acceso interno, por lo que, dependiendo de tu máquina, ni siquiera obtendrás una vista uniforme de la memoria a la que querías acceder. Volviendo a su pregunta: un compilador no transformará el código fuente en la secuencia que visualizó PERO puede muy bien producir un lenguaje de máquina que en efecto se comportará de la manera que lo hace su código fuente. Si es capaz de inspeccionar las funciones de bloqueo, desbloqueo y suspensión (que parecen ser macros de todos modos) y puede deducir que no hay ningún efecto secundario para las ubicaciones de memoria involucradas Y no hay un significado definido de implementación para las llamadas a, p. sleep() que haría que el valor temporal ("caché" aunque el estándar no use este término) no sea válido, entonces tiene derecho a generar una secuencia de instrucciones como la que proporcionó. C (hasta C99) es intrínsecamente de subproceso único y el compilador puede emplear cualquier estrategia de optimización que desee, siempre que el código se comporte "como si" se ejecutaría en la máquina hipotética C ideal. Los puntos de secuencia que Jens mencionó no afectan la corrección en condiciones de subproceso único: el compilador podría mantener foo en un registro durante toda la función o podría incluso alias foo.i con la ubicación de memoria apuntada por * bin, por lo que este código es intrínsecamente peligroso (aunque creo que no mostrará este comportamiento en la mayoría de los compiladores).

+0

Tiene razón si está asumiendo que la función 'unlock' puede no ser una llamada a función sino una macro que se expande a una secuencia de operaciones que el compilador conoce. Estaba asumiendo el otro caso, que esto es efectivamente una llamada a la función. –

+2

Incluso entonces, esto no lo salvará. Si el compilador puede examinar la función, puede muy bien optimizarla. Tener foo como una automática es una premisa un tanto desafortunada para este ejemplo. – slartibartfast

+0

No hay problema si 'lock' y' unlock' no tienen errores. Implementarlos en una macro, o una función en el mismo ámbito, que no impide de alguna manera el reordenamiento es una de las muchas formas sutiles de escribir primitivas de bloqueo con errores. Además, el bloqueo protege los accesos a '* bin' si se mantiene cada vez que se accede a esta memoria. Bloquear en un solo lugar no tiene valor. – ugoren