Esta pregunta me da vueltas en la cabeza desde hace tiempo.
Supongamos que yo hago una estructura del tipo:
struct MyIntEspecial {
int mValue;
.. aqui defino operadores y otras funciones pero ninguna otra variable,
.. todas inline y nada de polimorfismo ( rtti esta desactivado ).
};
Se dará cuenta el compilador que solo es un int que quiero tratar diferente?
Hará las optimizaciones necesarias, como por ejemplo alojarlo en un registro para calculos sucesivos o para pasarlo a una función y todas esas cosas?
He visto que los procesadores de hoy en dia trabajan con el stack en la l2 igual de rapido que con los registros.
Ya no deberia preocuparme entonces por esto?
Saludos y gracias
Creo haber leido que una estructura formada por un solo campo int, no ocupa un solo int en la memoria, aunque hace mucho tiempo y no me acuerdo donde. No obstante es algo muy concreto, quizá podrías preguntarlo en los foros del compilador que estés utilizando...
Lo unico garantizable es que sizeof (MyIntEspecial) == el tamaño de la estructura. Que ese tamaño sea igual que su contenido, pues no creo que los compiladores lo garanticen. Yo no he escrito ningun compilador, pero si escribiera uno, no respeteria eso, aunque sea solo por joder. Por por ejemplo para alinear los datos a factores multiplos de dos, entonces tu estructura tiene un "hueco" sin usar. O cosas asi. De todos modos quizas esto es raro, y todos los compiladores respeten el tamaño del contenido.
Pero que mas da.. primero, no bases tu codigo en estas especulaciones, que esto no esta en los specs (bueno, no lo se, pero no lo creo).
Segundo sizeof( MyIntEspecial) == sizeof(int) => este simple codigo te dira si es o no del mismo tamaño :D
Mas que nada no es por el tamaño, sino por la optimización que generan los compiladores. Al llamar a una función pasandolo por valor o como devolución de una función, se dará cuenta que es solo un registro? me lo pasará en eax? o hara toda la maraña? De que dependerá esto?
No puedes garantizar que sizeof(MyIntEspecial) == sizeof(int).
El compilador es libre de insertar el padding que considere oportuno después de una variable miembro.
Aunque los compiladores definen extensiones específicas (no estándares) para personalizar las reglas de alineamiento (#pragma pack en el caso de VS).
Cita de: "Pogacha"Mas que nada no es por el tamaño, sino por la optimización que generan los compiladores. Al llamar a una función pasandolo por valor o como devolución de una función, se dará cuenta que es solo un registro? me lo pasará en eax? o hara toda la maraña? De que dependerá esto?
No merece la pena calentarse la cabeza por eso, ¿que vas a ahorrar uno o dos ciclos de un procesador a 2Ghz? En la epoca de los 8 bits tenia sentido pero ahora...
Por lo general los compiladores si una funcion tiene pocos parametros, los copia a los registros, siempre que quepan dentro de un registro.
No son uno o dos ciclos ... de eso estoy hablando, necesita optimizarse ya que es para un inner loop.
Cita de: "Pogacha"No son uno o dos ciclos ... de eso estoy hablando, necesita optimizarse ya que es para un inner loop.
Pues ponle "inline" delante de la declaracion, asi insertara el codigo y te ahorraras el salto y la vuelta de la rutina, que es mas costoso para los pipelines.
CitarNo puedes garantizar que sizeof(MyIntEspecial) == sizeof(int).
El compilador es libre de insertar el padding que considere oportuno después de una variable miembro.
Aunque los compiladores definen extensiones específicas (no estándares) para personalizar las reglas de alineamiento (#pragma pack en el caso de VS).
Es así como dice Davur.
Si quieres que tus estrucutras no presenten padding y ocupen lo mínimo posible puedes utilizar "
union". Ahora bien, es más dificil acceder a ellas. Aquí tienes ejemplos en castellano: http://c.conclase.net/curso/index.php?cap=016
Y esto algunos ejemplos de un pdf de mis apuntes del año pasado:
● Syntactically similar to structures
● However, all member variables occupy the same location in
memory
● You are responsible for accessing the right members at the
right time
● Union size is size of the largest member
Ejemplo 1
union UBlah {
char x;
int y;
char z;
};
Ejemplo 2
union {
char x;
int y;
char* z;
} utype;
utype.x = 'c';
printf("%c\n", utype.x);
utype.z = "Hello";
printf("%s\n", utype.z);
printf("%d\n", utype.y); /* Undefined! */
OK,
Voy a poner un ejemplo para que se entienda un poco mas.
typedef unsigned char byte;
typedef unsigned int dword;
// pragma empaquetar a 4 bytes ...
struct I32RGBAColor {
private:
static inline int saturate(int a) { return a>255 ? 255: a<0?0:a; }
public:
union {
struct { byte r,g,b,a; };
dword composed;
};
I32RGBAColor(byte _r, byte _g, byte _b, byte _a) : r(_r),g(_g),b(_b),a(_a) { }
I32RGBAColor operator+=(const I32RGBAColor& a, const I32RGBAColor &b) {
return I32RGBAColor( sat(a.r+b.r), sat(a.g+b.g), sat(a.b+b.b), sat(a.a+b.a));
}
};
void blend_add(I32RGBAColor* a, I32RGBAColor *b, I32RGBAColor* r, int l)
{
while(l--) *r++= (*a++) + (*b++);
}
Que tan bien compilaria esto?
contra:
inline int saturate(int a) { return a>255 ? 255: a<0?0:a; }
void blend_add(int* a, int *b, int* r, int l)
{
byte* u = reinterpret_cast<byte*> a;
byte* v = reinterpret_cast<byte*> b;
byte* w = reinterpret_cast<byte*> r;
l*=4;
while(l--) *w++ = saturate( static_cast<int>(*u++) + static_cast<int>(*v++) );
}
En realidad ambos codigos no tienen sentido, hay mejores formas de hacer lo mismo y no seria la mejor forma de organizar el codigo, pero para el ejemplo creo que sirven.
humm...
¿Porque no haces una prueba de rendimiento? Haz unos cuantos miles de operaciones con una y con otra y guarda los tiempos.
¿Porque no haces una prueba de rendimiento?
Pues por lo que dije!
Casi que no tengo dudas de que esos dos codigos compilarian muy parecidos, pero el caso está en que puedo hacer una prueba y en ella todo va a andar bien, pero luego cuando arme toda la estructura del codigo con muchos templates y demas, habrá casos en que podría no compilar de la forma que quiero. No tengo garantias de que esa estructura sencilla se va a reconocer como un int especial nada mas en un codigo mas complejo donde llamadas a funciones no puedan hacerse inline y cosas así pero igual sea critico el factor de optimización.
Igual puedo buscar algunos workarounds como pasar la estructura como un int y luego volver a castearla y cosas así pero dentro de templates eso ya se complica ...
Igual gracias a todos, seguiré precindiendo de estas estructuras y buscaré otra forma de flexibilizar el codigo optimizado.
A mi me parece mejor la version con I32RGBAColor, solo asegurate de sobrecargar el operator= y en el haces la copia usando el campo composed y no los campos r,g,b,a. Asi estaras copiando un entero.
Cita de: "Pogacha"Se dará cuenta el compilador que solo es un int que quiero tratar diferente?
Hará las optimizaciones necesarias, como por ejemplo alojarlo en un registro para calculos sucesivos o para pasarlo a una función y todas esas cosas?
Depende del compilador, vas a tener que mirar el assembler generado por tu compilador para determinar eso.
Cita de: "Pogacha"He visto que los procesadores de hoy en dia trabajan con el stack en la l2 igual de rapido que con los registros.
Ya no deberia preocuparme entonces por esto?
Saludos y gracias
Hmm, no se a que te réferis con esto. Ningún tipo de arquitectura que yo conozca permite trabajar con el L2 directamente (por lo menos no es el caso en los x86, el Xenon o el Cell) y el costo de subir información del L2 al L1 depende de la arquitectura pero generalmente ronda los 40 ciclos. La realidad es que en general no te tendrías que preocupar por eso, en todo caso antes de preocuparte hace profiling y determina si realmente es un problema o no. Yo personalmente me canso de ver programadores hacerse problema por el assembler generado por el compilador cuando los problemas de performance que tienen 99% de las veces están relacionados con los algoritmos en sí.
Cita de: "Pablo Zurita"Yo personalmente me canso de ver programadores hacerse problema por el assembler generado por el compilador cuando los problemas de performance que tienen 99% de las veces están relacionados con los algoritmos en sí.
frase para enmarcar
Que se lo digan a Michael Abrash :twisted:
Cita de: "senior wapo"Que se lo digan a Michael Abrash :twisted:
Abrash estaría de acuerdo conmigo probablemente. A diferencia de muchos programadores, Abrash no solo sabe muy bien assembler y las arquitecturas sino que además es muy bueno seleccionando y creando algoritmos. Además los proyectos en los que él trabaja (como Pixomatic) requieren tanta performance del CPU que es sumamente necesario elegir o crear los algoritmos más efectivos y optimizar el assembler al máximo. Abrash debe ser uno de los programadores más talentosos con los que he podido intercambiar información.
Va más por Ethernet, que ya sabemos de que pie cojea (con todo el cariño) :P
Citarrequieren tanta performance del CPU que es sumamente necesario elegir o crear los algoritmos más efectivos y optimizar el assembler al máximo
Ahi quería llegar yo: "Y".
En tratamiento de imagenes, no te queda otra, muchos de los algoritmos son lineales pero de todas formas el volumen de información a tratar es alto.
Una clase como esta:template<typename T, int B> class Fixed {
T t_;
public:
Fixed<T,B> operator+(const Fixed<T,B>& f) { return Fixed<T,B>(f.t_+t_); }
...
};
Podria compilar bien en algunos casos pero en otros no estoy seguro y la ganancia minima que tendrias frente al float la perderias por problemas de compilación.
Estaba buscando un ejemplo de codigo con el que me estuve peleando hace mas de un año pero no lo encontré.
Era un inner loop que tenia muchas variables, (12 o 13 quizas) y el compilador (VC6.0 que no es gran cosa), me generaba codigo que usaba solo 3 o 4 registros y las demas variables todas en el stack. Cuando lo ví me horrorizé y lo rescribí para que las variables mas usadas esten en registros, también baje en como 5 o 6 intrucciones al loop acomodandolo para que haya pairing (que las instrucciones continuas no tengan dependencias) y mi sorpresa fue que andaba ligeramente mas lento y me tuve que resignar, supongo que mi error habrá estado en como accedia a la memoria de datos y cuando escribía en ella, pero no tengo información sobre esto.
En realidad no es que me muera de angustia por optimizar nada, pero me gustaria saber que pasa con estas clases, pues siempre dudo en como estructurar el codigo y termino buscando otra solución alternativa a esto.
Según parece entonces no hay garantias de como valla esto a ser compilado, por mas que lo que quiero es sobrecargar un tipo interno.
De todas formas, tengo un mejor panorama y probablemente pruebe en algunos casos a ver que sale.
Saludos
Cita de: "senior wapo"Va más por Ethernet, que ya sabemos de que pie cojea (con todo el cariño) :P
Citarrequieren tanta performance del CPU que es sumamente necesario elegir o crear los algoritmos más efectivos y optimizar el assembler al máximo
Ahi quería llegar yo: "Y".
Para casos muy muy concretos, ya sabes el dicho de la excepción que confirma la regla.
A lo largo de mi vida como programador (que no es mucha) he visto muchísimas personas que valoran mucho aspectos técnicos del lenguaje, "si hago tal o cual cosa con java optimizo un 40%" es una frase típica y con esa corta experiencia me he dado cuenta que las personas que piensan demasiado en eso terminan pegándosela en los proyectos.
Optimizar es difícil y solo se puede hacer cuando se tiene mucha experiencia en algo. En absolutamente todas las áreas funciona así, no creo que los ingenieros que diseñan los coches piensen en optimizar al máximo el consumo desde el comienzo... el problema es que los informáticos se creen superhombres :)
Pogacha, siento el offtopic :)
Yo creo que hoy dia con el hardware actual al que accede cualquier persona de a pie y las herramientas super-asistidas y automatizadas de las que disponemos para programar, la optimizacion a tan bajo nivel en muchos casos no nos deberia preocupar, o en todo caso, dejar dicha tarea para el final, por que quizas luego puede que no merezca la pena llegar a tal punto.
Salu2...
puedes probar a ver el codigo ensamplador que genera tu compilador de esa estructura, al pasarla por parametro. creo que eso despejara toda duda. y comprobar si hay cambios al hacer una cosa u otra.
puedes probar a ver el codigo ensamplador que genera tu compilador de esa estructura, al pasarla por parametro. creo que eso despejara toda duda. y comprobar si hay cambios al hacer una cosa u otra.
OK, pero lo que no quiero es escribir 2 semanas de codigo y luego decir ok no anduvo ... o peor aun, escribir 2 sermanas de codigo, que todo ande bien, y que dos meses despues a unos dias de un milestone, en la ultima ampliación de su funcionalidad me tire abajo todo.
Yo quiero saber y estar seguro de lo que hago.
Saludos!