programar online ejecutar dev compilar compilador c++ performance assembly compiler-optimization inlining

c++ - online - compilar y ejecutar c en windows



¿Por qué el compilador en línea produce un código más lento que la creación manual? (2)

Respuesta corta:

Su matriz asd se declara así:

int *asd=new int[16];

Por lo tanto, use int como el tipo de retorno en lugar de bool.
Alternativamente, cambie el tipo de matriz a bool .

En cualquier caso, haga que el tipo de retorno de la función de test coincida con el tipo de la matriz.

Pase al pie de página para más detalles.

Respuesta larga:

En la versión insertada manualmente, el "núcleo" de una iteración se ve así:

xor eax,eax mov edx,ecx and edx,0Fh mov dword ptr [ebp+edx*4],eax mov eax,dword ptr [esp+1Ch] movss xmm0,dword ptr [eax] movss xmm1,dword ptr [edi] cvtps2pd xmm0,xmm0 cvtps2pd xmm1,xmm1 comisd xmm1,xmm0

La versión impresa del compilador es completamente idéntica, excepto por la primera instrucción.

Donde en lugar de:

xor eax,eax

Tiene:

xor eax,eax movzx edx,al

De acuerdo, entonces es una instrucción adicional. Ambos hacen lo mismo: poner a cero un registro. Esta es la única diferencia que veo ...

La instrucción movzx tiene una latencia de ciclo único y rendimiento recíproco de ciclo de 0.33 en todas las arquitecturas más nuevas. Entonces no puedo imaginar cómo esto podría hacer una diferencia del 10%.

En ambos casos, el resultado de la puesta a cero se usa solo 3 instrucciones más adelante. Entonces, es muy posible que esto pueda estar en la ruta crítica de la ejecución.

Si bien no soy un ingeniero de Intel, esta es mi suposición:

La mayoría de los procesadores modernos se ocupan de las operaciones de reducción a cero (como xor eax,eax ) mediante el cambio de nombre de registro a un banco de cero registros. Desvía por completo las unidades de ejecución. Sin embargo, es posible que este manejo especial pueda causar una burbuja en la tubería cuando se accede al registro (parcial) a través de movzx edi,al .

Además, también existe una falsa dependencia de eax en la versión en línea del compilador:

movzx edx,al mov eax,ecx // False dependency on "eax".

Si la ejecución fuera de orden es capaz o no de resolver esto, me supera.

De acuerdo, esto se está convirtiendo básicamente en una cuestión de ingeniería inversa del compilador de MSVC ...

Aquí voy a explicar por qué se genera tanto movzx adicional como por qué se mantiene.

La clave aquí es el valor de retorno bool . Aparentemente, los tipos de datos bool son probablemente como valores almacenados de 8 bits dentro de la representación interna de MSVC. Por lo tanto, cuando conviertes implícitamente de bool a int aquí:

asd[j%16] = a.test(b); ^^^^^^^^^ ^^^^^^^^^ type int type bool

hay una promoción de entero de 8 bits -> 32 bits. Esta es la razón por la cual MSVC genera la instrucción movzx .

Cuando la alineación se realiza manualmente, el compilador tiene suficiente información para optimizar esta conversión y mantiene todo como un IR de tipo de datos de 32 bits.

Sin embargo, cuando el código se coloca en su propia función con un valor de retorno bool , el compilador no puede optimizar el tipo de datos intermedio de 8 bits. Por lo tanto, el movzx queda.

Cuando haces que ambos tipos de datos sean iguales (ya sea int o bool ), no se necesita conversión. Por lo tanto, el problema se evita por completo.

Fondo

El siguiente bucle crítico de una pieza de software numérico, escrito en C ++, básicamente compara dos objetos por uno de sus miembros:

for(int j=n;--j>0;) asd[j%16]=a.e<b.e;

b son de clase ASD :

struct ASD { float e; ... };

Estaba investigando el efecto de poner esta comparación en una función miembro liviana:

bool test(const ASD& y)const { return e<y.e; }

y usándolo así:

for(int j=n;--j>0;) asd[j%16]=a.test(b);

El compilador está haciendo hincapié en esta función, pero el problema es que el código de ensamblado será diferente y causará> 10% de sobrecarga de tiempo de ejecución. Tengo que preguntar:

Preguntas

  1. ¿Por qué el compilador produce diferentes códigos de ensamblado?

  2. ¿Por qué el montaje producido es más lento?

EDIT: la segunda pregunta ha sido respondida mediante la implementación de la sugerencia de @ KamyarSouri (j% 16). El código de ensamblado ahora se ve casi idéntico (vea http://pastebin.com/diff.php?i=yqXedtPm ). Las únicas diferencias son las líneas 18, 33, 48:

000646F9 movzx edx,dl

Material

Esta tabla muestra los FLOP / s (hasta un factor de escala) durante 50 pruebas de mi código.

La secuencia de comandos gnuplot para generar la trama: http://pastebin.com/8amNqya7

Opciones del compilador:

/ Zi / W3 / WX- / MP / Ox / Ob2 / Oi / Ot / Oy / GL / D "WIN32" / D "NDEBUG" / D "_CONSOLE" / D "_UNICODE" / D "UNICODE" / Gm- / EHsc / MT / GS- / Gy / arch: SSE2 / fp: precise / Zc: wchar_t / Zc: forScope / Gd / analyse-

Opciones del enlazador: / INCREMENTAL: NO "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32. lib "" uuid.lib "" odbc32.lib "" odbccp32.lib "/ ALLOWISOLATION / MANIFESTUAC:" level = ''asInvoker'' uiAccess = ''false'' "/ SUBSYSTEM: CONSOLE / OPT: REF / OPT: ICF / LTCG / TLBID : 1 / DYNAMICBASE / NXCOMPAT / MACHINE: X86 / ERRORREPORT: QUEUE


lea esp,[esp] ocupa 7 bytes de i-cache y está dentro del ciclo. Algunas otras pistas hacen que parezca que el compilador no está seguro de si se trata de una versión de lanzamiento o una versión de depuración.

Editar:

The lea esp,[esp] no está en el circuito. La posición entre las instrucciones de los alrededores me confundió. Ahora parece que se desperdició intencionalmente 7 bytes, seguido de otro desperdicio de 2 bytes, para iniciar el bucle real en un límite de 16 bytes. Lo que significa que esto realmente acelera las cosas, como lo observó Johennes Gerer.

Sin embargo, el compilador aún no está seguro de si esto es una versión de depuración o versión.

Otra edición:

El diff pastabin es diferente del diff pastebin que vi antes. Esta respuesta podría eliminarse ahora, pero ya tiene comentarios, así que lo dejo.