*(p + i) vs p[i]

1, 2, 3, 4, 5
Narf
Troz
1.762 mensajes
desde ene 2004
Yo siempre he accedido a arrays en C usando *(p + i) en lugar de p[i]. Supongo que leí hace un montón de años algo sobre el tema y se me quedó grabado en la cabeza. Hace poco un colega me preguntó que por qué lo hacía al ver mi código lleno de paréntesis y asteriscos XD y no supe qué responderle.
Me puse a buscar por internet acerca de optimización en C y sólo encontré una referencia, que dice que hay que hacer justo lo contrario a lo que yo hago: usar p[i] en lugar de *(p + i), para facilitar la tarea de optimización al compilador (los punteros están menos restringidos). Así que modifiqué el código que ya tenía (bendito replace regexp) e hice algunas pruebas... y obtengo que mi código original, usando *(p + i), es un 10% más rápido que usando p[i] (ambos con CXXFLAGS="-O3 -ffast-math -funroll-loops").

Así que... ¿cuál es la respuesta correcta? ¿O es uno de esos casos en los que depende del código y no hay respuesta clara?

Saludos :).
"Creo", que el precompilador lo que hace es p[i] -> *(p+i), asique teoricamente no deberia haber diferencias o_O
A ver si algun guru de la programación en C nos aclara... x)
Salu2!
bastian
crackhead
1.434 mensajes
desde jul 2004
en back@home
e-Minguez escribió:"Creo", que el precompilador lo que hace es p[i] -> *(p+i), asique teoricamente no deberia haber diferencias o_O

Yo por lo que he leído, esa la idea que tenía, pero tampoco soy un experto ni mucho menos.

Un saludo.
Narf
Troz
1.762 mensajes
desde ene 2004
e-Minguez escribió:"Creo", que el precompilador lo que hace es p[i] -> *(p+i), asique teoricamente no deberia haber diferencias o_O
A ver si algun guru de la programación en C nos aclara... x)
Salu2!


Nop, no lo hace. Estas son las diferencias entre los dos códigos:

$ diff detectSphere.cc detectSphere2.cc
49,51c49,51
<   for (int i=0;i<uMLE.numel();i++) *(uMLEC + i)=uMLE(i);
<   for (int i=0;i<u.numel();i++) *(uC + i)=u(i);
<   for (int i=0;i<constell.numel();i++) *(constellC + i)=constell(i);
---
>   for (int i=0;i<uMLE.numel();i++) uMLEC[i]=uMLE(i);
>   for (int i=0;i<u.numel();i++) uC[i]=u(i);
>   for (int i=0;i<constell.numel();i++) constellC[i]=constell(i);
64,65c64,65
<   for (int i=0;i<distances.numel();i++) distances(i)=*(distancesD + i);
<   for (int i=0;i<symbols.numel();i++) symbols(i)=*(symbolsD + i);
---
>   for (int i=0;i<distances.numel();i++) distances(i)=distancesD[i];
>   for (int i=0;i<symbols.numel();i++) symbols(i)=symbolsD[i];
98c98
<             *(distancesD + distancesInd + j) = INFINITY;
---
>             distancesD[distancesInd + j] = INFINITY;
122c122
<       sumatC += *(uC + uInd + j) * *(diffCandC + j);
---
>       sumatC += uC[uInd + j] * diffCandC[j];
127,129c127,129
<       *(diffCandC + antenna) = *(constellC + i) - *(uMLEC + uMLEInd + antenna);
<       double uii = (*(uC + uInd + antenna)).real();
<       Complex resultC = *(diffCandC + antenna) + sumatC / uii;
---
>       diffCandC[antenna] = constellC[i] - uMLEC[uMLEInd + antenna];
>       double uii = (uC[uInd + antenna]).real();
>       Complex resultC = diffCandC[antenna] + sumatC / uii;
133c133
<         *(candidateVector + antenna) = i;
---
>         candidateVector[antenna] = i;
141c141
<             double worstDistance = *(distancesD + distancesInd);
---
>             double worstDistance = distancesD[distancesInd];
145c145
<                 if (*(distancesD + distancesInd + j) > worstDistance)
---
>                 if (distancesD[distancesInd + j] > worstDistance)
147c147
<                     worstDistance = *(distancesD + distancesInd + j);
---
>                     worstDistance = distancesD[distancesInd + j];
154c154
<                 *(distancesD + distancesInd + worstCand) = distance;
---
>                 distancesD[distancesInd + worstCand] = distance;
157c157
<                     *(symbolsD + symbolsInd + j) = *(candidateVector + j);
---
>                     symbolsD[symbolsInd + j] = candidateVector[j];


Es decir, sólo punteros por arrays. Y en cambio:

franjva@tay64 ~/octave/detection $ rm *.o
franjva@tay64 ~/octave/detection $ rm *.oct
franjva@tay64 ~/octave/detection $ CXXFLAGS="-O0" mkoctfile detectSphere.cc
franjva@tay64 ~/octave/detection $ CXXFLAGS="-O0" mkoctfile detectSphere2.cc
franjva@tay64 ~/octave/detection $ cmp detectSphere.o detectSphere2.o
detectSphere.o detectSphere2.o differ: byte 41, line 1


Es decir, ni siquiera desactivando las optimizaciones resulta el mismo código. El resultado sí es el mismo, pero con los punteros es más rápido.
Yo también creía que a[i] -> *(a+i). De hecho en compiladores antiguos, esto es 'válido':

int 5["array"];


Ya que la conversión era 'ciega'. Ahora GCC llora si lo intentas :)

Mis pruebas dicen que es lo mismo; veamos:

[ $ ~/prueba-punteros ] cat arrays.c
#include <stdio.h>

int main (int argc, char **argv)
{
   int arr[10];
   int i;

   for ( i = 0 ; i < 10 ; i++ )
      arr[i] = i;

   for ( i = 0 ; i < 10 ; i++ )
      printf("%d\n",arr[i]);

   return 0;
}
[ $ ~/prueba-punteros ] cat punteros.c
#include <stdio.h>

int main (int argc, char **argv)
{
   int arr[10];
   int i;

   for ( i = 0 ; i < 10 ; i++ )
      *(arr+i) = i;

   for ( i = 0 ; i < 10 ; i++ )
      printf("%d\n",*(arr+i));

   return 0;
}
[ $ ~/prueba-punteros ] for i in *.c ; do gcc -S -o ${i/.c/.s} ${i} ; done
[ $ ~/prueba-punteros ] diff -u arrays.s punteros.s
--- arrays.s   2005-10-09 00:14:23.000000000 +0200
+++ punteros.s   2005-10-09 00:14:23.000000000 +0200
@@ -1,4 +1,4 @@
-   .file   "arrays.c"
+   .file   "punteros.c"
   .section   .rodata
.LC0:
   .string   "%d\n"


Veamos con alguna optimización:

[ $ ~/prueba-punteros ] for i in *.c ; do gcc -O3 -S -o ${i/.c/.s} ${i} ; done
[ $ ~/prueba-punteros ] diff -u arrays.s punteros.s
--- arrays.s   2005-10-09 00:19:24.000000000 +0200
+++ punteros.s   2005-10-09 00:19:24.000000000 +0200
@@ -1,4 +1,4 @@
-   .file   "arrays.c"
+   .file   "punteros.c"
   .section   .rodata.str1.1,"aMS",@progbits,1
.LC0:
   .string   "%d\n"


mmmm parece generar lo mismo.

Todo apunta a que puede ser algo de los tipos que usas para los arrays, o del propio C++, o específico de tu código. O que se te haya pasado algo en las pruebas...

Un Saludo.Ferdy
Narf
Troz
1.762 mensajes
desde ene 2004
Editado 1 vez. Última: 9/10/2005 - 01:14:34 por Narf.
Jum, se me pasó el -S [tomaaa]

franjva@tay64 ~/octave/detection $ CXXFLAGS="-S -O0" mkoctfile -c detectSphere.cc
franjva@tay64 ~/octave/detection $ CXXFLAGS="-S -O0" mkoctfile -c detectSphere2.cc
franjva@tay64 ~/octave/detection $ diff detectSphere.o detectSphere2.o
1c1
<       .file   "detectSphere.cc"
---
>       .file   "detectSphere2.cc"
2335,2337c2335
<       movq    distancesInd@GOTPCREL(%rip), %rax
<       movl    (%rax), %eax
<       movslq  %eax,%rdx
---
>       movq    distancesInd@GOTPCREL(%rip), %rdx
2338a2337
>       addl    (%rdx), %eax
2340d2338
<       leaq    (%rdx,%rax), %rax
2435,2436d2432
<       movl    -32(%rbp), %eax
<       movslq  %eax,%rdx
2437a2434
>       addl    -32(%rbp), %eax
2439d2435
<       leaq    (%rdx,%rax), %rax
2471,2473c2467
<       movq    uMLEInd@GOTPCREL(%rip), %rax
<       movl    (%rax), %eax
<       movslq  %eax,%rdx
---
>       movq    uMLEInd@GOTPCREL(%rip), %rdx
2474a2469
>       addl    (%rdx), %eax
2476d2470
<       leaq    (%rdx,%rax), %rax
2500,2501d2493
<       movl    -32(%rbp), %eax
<       movslq  %eax,%rdx
2502a2495
>       addl    -32(%rbp), %eax
2504d2496
<       leaq    (%rdx,%rax), %rax
2562a2555
>       movq    candidateVector@GOTPCREL(%rip), %rcx
2564,2566c2557
<       cltq
<       leaq    0(,%rax,4), %rcx
<       movq    candidateVector@GOTPCREL(%rip), %rdx
---
>       movslq  %eax,%rdx
2568c2559
<       movl    %eax, (%rcx,%rdx)
---
>       movl    %eax, (%rcx,%rdx,4)
2603,2605c2594
<       movq    distancesInd@GOTPCREL(%rip), %rax
<       movl    (%rax), %eax
<       movslq  %eax,%rdx
---
>       movq    distancesInd@GOTPCREL(%rip), %rdx
2606a2596
>       addl    (%rdx), %eax
2608d2597
<       leaq    (%rdx,%rax), %rax
2618,2620c2607
<       movq    distancesInd@GOTPCREL(%rip), %rax
<       movl    (%rax), %eax
<       movslq  %eax,%rdx
---
>       movq    distancesInd@GOTPCREL(%rip), %rdx
2621a2609
>       addl    (%rdx), %eax
2623d2610
<       leaq    (%rdx,%rax), %rax
2642,2644c2629
<       movq    distancesInd@GOTPCREL(%rip), %rax
<       movl    (%rax), %eax
<       movslq  %eax,%rdx
---
>       movq    distancesInd@GOTPCREL(%rip), %rdx
2645a2631
>       addl    (%rdx), %eax
2647d2632
<       leaq    (%rdx,%rax), %rax
2667,2668d2651
<       movl    -180(%rbp), %eax
<       movslq  %eax,%rdx
2669a2653
>       addl    -180(%rbp), %eax
2671,2672c2655
<       leaq    (%rdx,%rax), %rax
<       leaq    0(,%rax,8), %rcx
---
>       leaq    0(,%rax,8), %rsi
2674c2657,2658
<       movq    (%rax), %rsi
---
>       movq    (%rax), %rcx
>       movq    candidateVector@GOTPCREL(%rip), %rdx
2677,2680c2661,2662
<       leaq    0(,%rax,4), %rdx
<       movq    candidateVector@GOTPCREL(%rip), %rax
<       cvtsi2sd        (%rdx,%rax), %xmm2
<       movsd   %xmm2, (%rcx,%rsi)
---
>       cvtsi2sd        (%rdx,%rax,4), %xmm2
>       movsd   %xmm2, (%rsi,%rcx)


Pese a que el único ensamblador que conozco es el del MIPS, parece claro que con punteros utiliza unos registros diferentes que al usar arrays. Si es preferible usar rax o rdx ya tendrá que decirlo alguien que controle de x86 (o x86-64, para ser exactos) XD.

Las pruebas están requeterepetidas, no hay fallo. El código generado por detectSphere es más rápido que el de detectSphere2.

(edit) Por cierto, no pongo las diferencias con -O3 -funroll-loops -ffast-math porque no caben en la página XD.
¿ Has comprobado que te pasa lo mismo con código C ? Yo no se apenas C++ como para hacer una prueba simple. Como ves en mi caso ha generado lo mismo con -O3 que sin optimizaciones.

Mañana lo pruebo en un alpha... ahora me voy al sobre.

Saludos.Ferdy
Narf
Troz
1.762 mensajes
desde ene 2004
Ferdy escribió:¿ Has comprobado que te pasa lo mismo con código C ? Yo no se apenas C++ como para hacer una prueba simple. Como ves en mi caso ha generado lo mismo con -O3 que sin optimizaciones.

Mañana lo pruebo en un alpha... ahora me voy al sobre.

Saludos.Ferdy

Sí, lo primero que probé, antes de hacer el cambio *()->[], fue un ejemplo sencillo en C, similar al que has puesto. Los objetos generados eran idénticos. Por eso me puse a cambiar (a mí me da igual, llevo años usando punteros y estoy más que acostumbrado, pero si comparto el código es más sencillo de ver la notación de arrays).

Por cierto, bajo x86 (antes era x86-64) las diferencias son bastante menores:
franjva@tay64 ~/octave/detection $ CXXFLAGS="-S -O0" mkoctfile -c detectSphere.cc
franjva@tay64 ~/octave/detection $ CXXFLAGS="-S -O0" mkoctfile -c detectSphere2.cc
franjva@tay64 ~/octave/detection $ diff detectSphere.o detectSphere2.o
1c1
<       .file   "detectSphere.cc"
---
>       .file   "detectSphere2.cc"
2888,2890c2888,2889
<       movl    16(%ebp), %eax
<       leal    0(,%eax,4), %ecx
<       movl    candidateVector@GOT(%ebx), %edx
---
>       movl    candidateVector@GOT(%ebx), %ecx
>       movl    16(%ebp), %edx
2892c2891
<       movl    %eax, (%ecx,%edx)
---
>       movl    %eax, (%ecx,%edx,4)
2990c2989
<       leal    0(,%eax,8), %ecx
---
>       leal    0(,%eax,8), %esi
2992c2991,2992
<       movl    (%eax), %esi
---
>       movl    (%eax), %ecx
>       movl    candidateVector@GOT(%ebx), %edx
2994,2997c2994,2995
<       leal    0(,%eax,4), %edx
<       movl    candidateVector@GOT(%ebx), %eax
<       fildl   (%edx,%eax)
<       fstpl   (%ecx,%esi)
---
>       fildl   (%edx,%eax,4)
>       fstpl   (%esi,%ecx)

Aunque siga habiendo diferencias en el código, la diferencia de tiempo en este caso es nula.
Tiempos en x86 (CXXFLAGS="-O3 -ffast-math -funroll-loops")
detectSphere:  ans = 10.024
detectSphere2: ans = 10.022

Tiempos en x86-64, mismos CXXFLAGS (esos registros extra no le han venido mal al x86-64, ganancia de >30% en el mismo ordenador :)):
detectSphere:  ans = 6.7558
detectSphere2: ans = 7.5135

Espero tus resultados en alpha :). Tengo curiosidad por saber si definitivamente es mejor usar punteros, como parece, o arrays.

Aun así tengo que probarlo en C, pero antes tendré que limpiar algunas clases de octave del código para que compile.
Pues la misma 'chorrada' de ejemplo en alpha genera dos objetos iguales y el mismo código ensamblador (ningún -mcpu hace que sean distintos)

[ $ ~/prueba-punteros ] for i in *.c ; do alpha-unknown-linux-gnu-gcc -O2 -mieee -S -o ${i/.c/.s} ${i} ; done
[ $ ~/prueba-punteros ] for i in *.c ; do alpha-unknown-linux-gnu-gcc -O2 -mieee -c -o ${i/.c/.o} ${i} ; done
[ $ ~/prueba-punteros ] sha1sum *.o *.s
e5ee7b263db3826451abd6eb0f239319bb683232  arrays.o
e5ee7b263db3826451abd6eb0f239319bb683232  punteros.o
c0da51261acf27fed18790236cd0c14d6307c562  arrays.s
c0da51261acf27fed18790236cd0c14d6307c562  punteros.s


No tengo muchas más ideas... si luego pillo a algún 'gcc-ninja' le preguntaré.

De todas formas habría que ver qué líneas de código C generan cambios en el asm; eso quizá nos ayudaría a ver por qué el compilador hace cosas distintas.

Saludos.Ferdy
jyck
.....
580 mensajes
desde nov 2000
en Lejos.....
Hola, a ver, desempolvando mis apuntes de estructura de datos, y segun mi profesor que es muy friki para esto, es practicamente equivalente, lo que hace el compilador es pasar p[i] a *(p + i), nada mas, luego el compilador lo que hace es esta operacion, valor de p + i x numero de bits del dato que contienes p, para poder acceder a esa posicion de memoria, solo eso. Creo que es asi, en un programa corto no creo que importe mucho ese cambio de notacion, supongo que en un programa algo mas sobrecargado ganará algo de velocidad.

Salu2
1, 2, 3, 4, 5