PDA

Ver la versión completa : Las optimizaciones "cascan" el programa



Puck2099
09/04/2006, 03:27
Hola,

¿Alguien sabe en qué afectan las optimizaciones que se le pasan al compilador?

Me explico. En mi port del Fenix, si compilo con un -O1 todo funciona bien, pero si cambio este -O1 por un -O2 o -O3, aunque parece que funciona bien, en realidad las funciones matemáticas (seno, coseno, valor absoluto, incluso el exponencial y la raiz cuadrada) me devuelven 0.

Supongo que el problema no estará en la propia llamada a la función de C que realiza la función matemática, sino en la "mierda" que hace antes.

Os pego el código de unas funciones para que os hagáis una idea:



static int fxi_sqrt (INSTANCE * my, int * params)
{
float res = (float)sqrt(*(float *)&params[0]) ;
return *(int *)&res ;
}

static int fxi_sin (INSTANCE * my, int * params)
{
float param = *(float *)&params[0] ;
float result = (float)sin((double)(param*M_PI/180000.0)) ;
return *(int *)&result ;
}


La verdad es que tanta conversión de tipos me parece una chapuza...

Saludos

WinterN
09/04/2006, 04:03
Que código más ofuscado [Ahhh]

No quiero ni imaginar la de horas que habrás echado para descubrir lo de la optimización.

A mi me parece que ese código está bien, aunque esos cast de (float*) a (int*)... ¿no debería multiplicarse antes por un factor?

Siento no poder ser de más ayuda.

miq01
09/04/2006, 04:11
Puck, ni idea pero sorprende que pase esto. Yo hice alguna prueba con raíces cuadradas en GP2X para calcular distancias y seguro que compilaba con -O2 (porque es lo que tengo puesto en los Makefile, que voy copiando de un proyecto a otro), y juraría que no tuve ningún problema, así que huele a problemas en la conversión.

Mírate esto (http://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Optimize-Options.html), que explica exactamente qué opciones de compilación activa -O1, -O2 y -O3.

Estopero
09/04/2006, 07:01
Buenos pues estoy con titerote, el problema de que te devuelvan cero, es porque los pasas a intenger, y al pasar a integer se trunca y se queda nada mas que con la parte entera, con estos rollos estoy teniendo yo problemas para hacer mi jueguecillo :P

BuD
09/04/2006, 07:35
Diria que con GCC 4 hay hasta -O4. Segun tengo entendido, pocos programas consiguen ser ejecutados decentemente despues de ser compilados con -O4.
Lo que me extraña es que Franxis haya conseguido compilar el mame 1.9 (y superiores) con -O4 y le tire perfectamente. [Ahhh]
Me da que Franxis te podria ayudar bastante en este tema...

WinterN
09/04/2006, 10:06
Buenos pues estoy con titerote, el problema de que te devuelvan cero, es porque los pasas a intenger, y al pasar a integer se trunca y se queda nada mas que con la parte entera, con estos rollos estoy teniendo yo problemas para hacer mi jueguecillo :P

No os confundais. No está convirtiendo un float en un int, sino un puntero a float a un puntero a int, que no es exactamente lo mismo.

Creo que algún gurú de C pueda decirnos que pasa cuando haces esa conversión (y es posible que dependiendo del tipo de optimización se obtengan resultados distintos).

He encontrado algún enlace interesante sobre el tema:
http://www.crasseux.com/books/ctutorial/Pointer-types.html

Uncanny
09/04/2006, 11:03
Que código más "intuitivo", al rico casting oiga!! xDDD

En el caso de que de que lo que pasa es que una de las opciones que están agrupadas en O2 (porque si están en O2, están en O3, pero sea cual sea no está en O1) está afectando de algún modo haciendo que se redondee a cero y se trunque al hacer el cast de puntero a float/double a puntero a integer podría ser que se solucionase con las opciones -fno-rounding-math o -frounding-math (esta desactiva las optimizaciones a la hora de hacer las conversiones por defecto de floats/doubles a enteros) aunque se supone que la primera se usa por defecto, de todas formas prueba -ffast-math (que entre otras opciones que agrupa está la contraria a la anterior -fno-rounding-math pero tiene otras optimizaciones relacionas con las operaciones matemáticas) con -O2 u -O3 a ver si no hay problema.

fatkitten
09/04/2006, 13:09
Hola, no se si será por las optimizaciones. Tu código me llama un poco la atención.
Por ejemplo, cuando haces:

float param = *(float *)&params[0] ;

Daría lo mismo poner:

float param = params[0];

¿Por qué lo haces así?. Lo mismo para los return. Podrías poner:

return result

Puesto que como tú lo pones no estás haciendo el cast de float a int. Entiendo que pases los argumentos por referencia para ahorrar memoria y ganar en velocidad pero no entiendo por qué haces esa cosa tan rara para delvolver el resultado. Si lo que quieres es devolverlo también por referencia no lo puedes hacer así puesto que estás devolviendo una variable declarada dentro de la función y cuando se sale de la función estas variables se liberan y si accedes a esa posición de memoria puede que encuentres el valor correcto o no.

Ya me cuentas.


Salu2!

hermes PS2R
09/04/2006, 17:53
Hola,

¿Alguien sabe en qué afectan las optimizaciones que se le pasan al compilador?

Me explico. En mi port del Fenix, si compilo con un -O1 todo funciona bien, pero si cambio este -O1 por un -O2 o -O3, aunque parece que funciona bien, en realidad las funciones matemáticas (seno, coseno, valor absoluto, incluso el exponencial y la raiz cuadrada) me devuelven 0.

Supongo que el problema no estará en la propia llamada a la función de C que realiza la función matemática, sino en la "mierda" que hace antes.

Os pego el código de unas funciones para que os hagáis una idea:



static int fxi_sqrt (INSTANCE * my, int * params)
{
float res = (float)sqrt(*(float *)&params[0]) ;
return *(int *)&res ;
}

static int fxi_sin (INSTANCE * my, int * params)
{
float param = *(float *)&params[0] ;
float result = (float)sin((double)(param*M_PI/180000.0)) ;
return *(int *)&result ;
}


La verdad es que tanta conversión de tipos me parece una chapuza...

Saludos


Pero vamos a ver... ¿como no te va a devolver 0 si el valor de retorno es un float que pasas a int y el resultado esta entre 0 y 1 (sin llegar a 1)?

ademas de eso utilizas punteros a la pila que debido a las optimizaciones, puede hacer que al ser una funcion corta se haga inline y estar recogiendo un valor diferente al previsto.

Si el valor es un float entre 0 y 1 y lo pasas a int, lo normal es que se suprima la parte decimal. Asi si tienes un 0.9999 te devolvera 0.


Para evitarlo, la forma mas sencilla es esta;

return ((int) (result+0.5f));

Ese 0.5f hace que se redondee el numero de la forma correcta.


Tambien te recomiendo estos flags: -msoft-float -ffast-math por si el fallo viene de las funciones sqrt, sin ,cos, tambien.

mortimor
09/04/2006, 18:18
Pero vamos a ver... ¿como no te va a devolver 0 si el valor de retorno es un float que pasas a int y el resultado esta entre 0 y 1 (sin llegar a 1)?

ademas de eso utilizas punteros a la pila que debido a las optimizaciones, puede hacer que al ser una funcion corta se haga inline y estar recogiendo un valor diferente al previsto.

Si el valor es un float entre 0 y 1 y lo pasas a int, lo normal es que se suprima la parte decimal. Asi si tienes un 0.9999 te devolvera 0.


Para evitarlo, la forma mas sencilla es esta;

return ((int) (result+0.5f));

Ese 0.5f hace que se redondee el numero de la forma correcta.


Tambien te recomiendo estos flags: -msoft-float -ffast-math por si el fallo viene de las funciones sqrt, sin ,cos, tambien.

Lamento disentir, pero como ha dicho alguien antes esta convirtiendo un puntero de float a uno de int, por lo tanto no se redondea nada, solo cambia la interpretacion de los 32 bits a los que apunta.

Creo que al cambiar la optimizacion del codigo puede modificarse la forma de interpretar los operadores al generar el codigo objeto. Prueba a añadir algun parentesis mas, Puck:


return *((int *)&res);

Puck2099
09/04/2006, 18:31
Muchas gracias a todos por vuestras sugerencias, iré probando con lo que me habéis dicho a ver si consigo algo que funcione :)

En cuanto al código en sí, no es mio, sino de la gente que hizo el Fenix. Mi opinión personal es que es bastante chapucero (no os imagináis la cantidad de warnings que saltan al compilar) y ofuscado, pero vamos, que no me voy a quejar de un código ajeno si yo no soy capaz de hacerlo por mi mismo.

Saludos

hermes PS2R
09/04/2006, 18:49
Lamento disentir, pero como ha dicho alguien antes esta convirtiendo un puntero de float a uno de int, por lo tanto no se redondea nada, solo cambia la interpretacion de los 32 bits a los que apunta.

Creo que al cambiar la optimizacion del codigo puede modificarse la forma de interpretar los operadores al generar el codigo objeto. Prueba a añadir algun parentesis mas, Puck:


return *((int *)&res);


Pues tienes razon en una cosa, que ahora que me fijo el resultado de la funcion debe ser devolver el contenido del float en un .valor de 32 bits

Pero estoy seguro que esto:

return *(int *)&res ;

Se queda en esto

(int) (*(float *) &res;)

por el tema de la optimizacion.


De todas formas creo que las funciones sqrt,sin , etc, trabajaran bien cuando le ponga el flag: -msoft-float

Puck2099
09/04/2006, 19:37
Bueno, he probado a compilar con los -msoft-float -ffast-math que dice Hermes y esto sigue igual...

Ya que con -O2 casca y con -O1 no, lo que voy a probar ahora es a usar el -O1 e ir probando uno a uno todos los flags que añade el -O2 a ver cual es el que casca...

Me estoy temiendo que el problema no venga de las funciones matemáticas, sino de los casts que comentáis...

mortimor
09/04/2006, 19:46
Pues tienes razon en una cosa, que ahora que me fijo el resultado de la funcion debe ser devolver el contenido del float en un .valor de 32 bits

Pero estoy seguro que esto:

return *(int *)&res ;

Se queda en esto

(int) (*(float *) &res;)

por el tema de la optimizacion.


De todas formas creo que las funciones sqrt,sin , etc, trabajaran bien cuando le ponga el flag: -msoft-float

¿Seguro que el compilador lo transforma en eso al optimizar?? Yo no soy ningun experto en construccion de compiladores, pero eso es una cagada como un piano ya que se reduce a un cast de float a int (de ahi el 0 como resultado). No creo que ese sea el problema la verdad, seria una error demasiado flagrante en el compilador y no creo que sea el caso.

No pretendo ofenderte Hermes, pero yo sigo pensando que al ejecutar el -02 hace lo siguiente:

return *(int *)&res; -> return (int)res;

Para optimizar se reduce el numero de operadores (en este caso evitando realizar 3 redirecciones) basandose en el orden de prioridad y en caso de mismo nivel (como son * e &) por orden de derecha a izquierda. Por eso decia lo del parentesis, para forzar el orden de operadores y evitar esta optimizacion. A mi me ha funcionado en algunos casos :)

Por otro lado... tienes razon en que con -msoft-float funcionan bien sqrt, pow... y, la verdad, no se si estas funciones para convertir serviran de algo. Seria mejor una cast directo sobre el resultado de sqrt o pow en vez de una llamada a funcion, a no ser que sean inline, o una macro que simplifique ese cast para evitar ofuscar el codigo.

Ya nos contara Puck :)

Puck2099
09/04/2006, 20:50
Para optimizar se reduce el numero de operadores (en este caso evitando realizar 3 redirecciones) basandose en el orden de prioridad y en caso de mismo nivel (como son * e &) por orden de derecha a izquierda. Por eso decia lo del parentesis, para forzar el orden de operadores y evitar esta optimizacion. A mi me ha funcionado en algunos casos :)

Por otro lado... tienes razon en que con -msoft-float funcionan bien sqrt, pow... y, la verdad, no se si estas funciones para convertir serviran de algo. Seria mejor una cast directo sobre el resultado de sqrt o pow en vez de una llamada a funcion, a no ser que sean inline, o una macro que simplifique ese cast para evitar ofuscar el codigo.

Ya nos contara Puck :)

Nada, he probado a ponerle los paréntesis que sugerías, mortimor, pero la cosa sigue igual...

Empezaré a probar a ir añadiendo flags, lo que pasa es que es un coñazo...

mortimor
09/04/2006, 20:59
Nada, he probado a ponerle los paréntesis que sugerías, mortimor, pero la cosa sigue igual...

Empezaré a probar a ir añadiendo flags, lo que pasa es que es un coñazo...

Pues es algo extraño. Espero que lo soluciones.

Por cierto, ¿¿pusiste el parentesis en el *(float *)&params[0] del mismo modo???. A lo mejor era por eso :)

Puck2099
09/04/2006, 22:50
Por cierto, ¿¿pusiste el parentesis en el *(float *)&params[0] del mismo modo???. A lo mejor era por eso :)

Pues no lo puse, pero acabo de hacerlo y sigue fallando igualmente... :(

< - >
Bueno, tras probar 26 ejecutables diferentes, cada uno con el -O1 más uno de los flags que añade el -O2, el único que ha dado problemas como los de antes es -fstrict-aliasing

Luego probaré a compilar con el resto de flags combinados, aunque seguro que casca también por la combinación de algunos ellos...

hermes PS2R
10/04/2006, 00:00
Bueno, prueba esto:

static int fxi_sqrt (INSTANCE * my, int * params)
{
float * addr;
static float res;

addr=(float *) params;
res= (float)sqrt(addr[0]) ;
return *((int *)&res) ;
}

static int fxi_sin (INSTANCE * my, int * params)
{
float * addr;
float param;
static float result;
addr=(float *) params;

param= addr[0];

result = (float)sin((double)(param*M_PI/180000.0)) ;
return *((int *)&result) ;
}

Lo normal es que los primeros parametros de una funcion, son asignados a REGISTROS y esa es uno de los posibles problemas que puedes tener.

Tambien ahora hemos cambiado dos cosas mas: que antes en la definicion de variable se hacia una llamada a una funcion (sqrt, sin) y ahora primero se declara y luego se asigna mas adelante (tal vez haya un bug en el compilador que hace que con la optimizacion no calcule bien el espacio de pila) y por ultimo, pero no menos importante, ahora utilizamos una variable static para devolver el resultado. Esto ultimo puede que evite conflictos raros de caches, sobre todo si la optimizacion recurre a asignar variables a registros y luego no sabe lo que hacer cuando se le pide un valor utilizando un puntero (el (int) (*(float *) &res) que sugiero yo o el return (int)res; que dice mortimor, son EXACTAMENTE lo mismo, pero pudiera ser que la traduccion no se haga bien por que el compilador este usando un registro y no sepa interpretsrlo bien y lo haga asi)

< - >
Por cierto, procura compilar todo el proyecto desde el principio
para evitar heredar codigo precompilado y si aun asi tienes problemas, tal vez el problema esté en la asignacion de parametros a esa funciones o sea un problema de cast entre double y float (podrias usar sinf, cosf y sqrtf, la version float de estas funciones)

mortimor
10/04/2006, 00:18
Bueno, prueba esto:

static int fxi_sqrt (INSTANCE * my, int * params)
{
float * addr;
static float res;

addr=(float *) params;
res= (float)sqrt(addr[0]) ;
return *((int *)&res) ;
}

static int fxi_sin (INSTANCE * my, int * params)
{
float * addr;
float param;
static float result;
addr=(float *) params;

param= addr[0];

result = (float)sin((double)(param*M_PI/180000.0)) ;
return *((int *)&result) ;
}

Lo normal es que los primeros parametros de una funcion, son asignados a REGISTROS y esa es uno de los posibles problemas que puedes tener.

Tambien ahora hemos cambiado dos cosas mas: que antes en la definicion de variable se hacia una llamada a una funcion (sqrt, sin) y ahora primero se declara y luego se asigna mas adelante (tal vez haya un bug en el compilador que hace que con la optimizacion no calcule bien el espacio de pila) y por ultimo, pero no menos importante, ahora utilizamos una variable static para devolver el resultado. Esto ultimo puede que evite conflictos raros de caches, sobre todo si la optimizacion recurre a asignar variables a registros y luego no sabe lo que hacer cuando se le pide un valor utilizando un puntero (el (int) (*(float *) &res) que sugiero yo o el return (int)res; que dice mortimor, son EXACTAMENTE lo mismo, pero pudiera ser que la traduccion no se haga bien por que el compilador este usando un registro y no sepa interpretsrlo bien y lo haga asi)

< - >
Por cierto, procura compilar todo el proyecto desde el principio
para evitar heredar codigo precompilado y si aun asi tienes problemas, tal vez el problema esté en la asignacion de parametros a esa funciones o sea un problema de cast entre double y float (podrias usar sinf, cosf y sqrtf, la version float de estas funciones)

Yo no lo habia pensado, pero esa declaracion como static puede dar resultado :) Me uno integramente a Hermes en su planteamiento y en la recomendacion de recompilar todo desde cero (que mira que pasan cosas raras con los objetos precompilados).

hermes PS2R
10/04/2006, 00:29
Yo no lo habia pensado, pero esa declaracion como static puede dar resultado :) Me uno integramente a Hermes en su planteamiento y en la recomendacion de recompilar todo desde cero (que mira que pasan cosas raras con los objetos precompilados).

Si, si ya lo dice el refran "mas sabe el diablo por viejo que por diablo". Cosas mas raras me han pasado a mi compilando programas para PS2, bugs rarisimos del compilador y lo mejor es simplificar el codigo (nada de llamadas a funcion en declaraciones de variable) y recurrir a statics que fuercen como variable en memoria y cosas asi, sobre todo en retornos como el que nos ocupa ;)

Puck2099
10/04/2006, 07:25
Bueno, acabo de llegar de un cumpleaños y no he podido resistirme a compilar una vez más con todos los flags del -O2 excepto el -fstrict-aliasing que daba problemas :D

El resultado final es que no me da el problema con las funciones matemáticas que ocurría antes y la pérdida de rendimiento respecto a la versión con el -O2 ha sido mínima (de 1 fps en la mayoría de casos y 4 frames sobre 62 en el peor de ellos).

Voy a probar los flags de la -O3 a ver si así consigo que funcione y aumenta en algo el rendimiento... :D

< - >
Nada, con los 2 flags "extras" de la -O3 encima se empeora algo el rendimiento, así que ya tenemos un ganador :D

Jejeje, me voy a la cama, que ya es tarde y encima he colgado la consola al poner el Fenix a 266 Mhz :D