¿El lenguaje ensamblador en línea es más lento que el código nativo de C ++?

Traté de comparar el rendimiento del lenguaje ensamblador en línea y el código C ++, así que escribí una función que agrega dos arreglos de tamaño 2000 por 100000 veces. Aquí está el código:

#define TIMES 100000 void calcuC(int *x,int *y,int length) { for(int i = 0; i < TIMES; i++) { for(int j = 0; j < length; j++) x[j] += y[j]; } } void calcuAsm(int *x,int *y,int lengthOfArray) { __asm { mov edi,TIMES start: mov esi,0 mov ecx,lengthOfArray label: mov edx,x push edx mov eax,DWORD PTR [edx + esi*4] mov edx,y mov ebx,DWORD PTR [edx + esi*4] add eax,ebx pop edx mov [edx + esi*4],eax inc esi loop label dec edi cmp edi,0 jnz start }; } 

Aquí está main() :

 int main() { bool errorOccured = false; setbuf(stdout,NULL); int *xC,*xAsm,*yC,*yAsm; xC = new int[2000]; xAsm = new int[2000]; yC = new int[2000]; yAsm = new int[2000]; for(int i = 0; i < 2000; i++) { xC[i] = 0; xAsm[i] = 0; yC[i] = i; yAsm[i] = i; } time_t start = clock(); calcuC(xC,yC,2000); // calcuAsm(xAsm,yAsm,2000); // for(int i = 0; i < 2000; i++) // { // if(xC[i] != xAsm[i]) // { // cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl; // errorOccured = true; // break; // } // } // if(errorOccured) // cout<<"Error occurs!"<<endl; // else // cout<<"Works fine!"<<endl; time_t end = clock(); // cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"\n"; cout<<"time = "<<end - start<<endl; return 0; } 

Luego ejecuto el progtwig cinco veces para obtener los ciclos del procesador, lo que podría verse como el tiempo. Cada vez que llamo solo a una de las funciones mencionadas arriba.

Y aquí viene el resultado.

Función de la versión de assembly:

 Debug Release --------------- 732 668 733 680 659 672 667 675 684 694 Average: 677 

Función de la versión C ++:

 Debug Release ----------------- 1068 168 999 166 1072 231 1002 166 1114 183 Average: 182 

El código de C ++ en modo de lanzamiento es casi 3.7 veces más rápido que el código de ensamblaje. ¿Por qué?

Supongo que el código de ensamblaje que escribí no es tan efectivo como los generados por GCC. Es difícil para un progtwigdor común como yo escribir código más rápido que su oponente generado por una comstackción. ¿Eso significa que no debería confiar en el rendimiento del lenguaje ensamblador escrito por mis manos, centrarme en C ++ y olvidarme del lenguaje ensamblador?

Sí, la mayoría de las veces.

En primer lugar, se parte de la suposición errónea de que un lenguaje de bajo nivel (ensamblado en este caso) siempre producirá un código más rápido que el lenguaje de alto nivel (C ++ y C en este caso). No es verdad. ¿El código C siempre es más rápido que el código Java? No porque hay otra variable: progtwigdor. La forma en que escribe el código y el conocimiento de los detalles de la architecture influye enormemente en el rendimiento (como vio en este caso).

Siempre puedes producir un ejemplo en el que el código ensamblado a mano es mejor que el código comstackdo, pero generalmente es un ejemplo ficticio o una rutina única, no un verdadero progtwig de más de 500,000 líneas de código C ++. Creo que los comstackdores producirán un mejor código de ensamblado el 95% de veces y , a veces, solo en raras ocasiones, es posible que necesite escribir código de ensamblaje para pocas rutinas de rendimiento crítico breves o muy utilizadas o cuando tenga que acceder a las características de su lenguaje de alto nivel favorito no expone ¿Quieres un toque de esta complejidad? Lea esta increíble respuesta aquí en SO.

¿Por qué esto?

En primer lugar porque los comstackdores pueden hacer optimizaciones que ni siquiera podemos imaginar (ver esta breve lista ) y las harán en segundos (cuando podamos necesitar días ).

Cuando codifica en ensamblaje, debe realizar funciones bien definidas con una interfaz de llamada bien definida. Sin embargo, pueden tener en cuenta la optimización de todo el progtwig y la optimización entre procedimientos , como la asignación de registros , la propagación constante , la eliminación común de subexpresiones , la progtwigción de instrucciones y otras optimizaciones complejas y no obvias ( modelo Polytope , por ejemplo). En la architecture RISC , los chicos dejaron de preocuparse por esto hace muchos años (la progtwigción de instrucciones, por ejemplo, es muy difícil de sintonizar a mano ) y las modernas CPU CISC también tienen tuberías muy largas.

Para algunos microcontroladores complejos, incluso las bibliotecas del sistema se escriben en C en lugar de ensamblarse porque sus comstackdores producen un código final mejor (y fácil de mantener).

Los comstackdores a veces pueden usar automáticamente algunas instrucciones de MMX / SIMDx , y si no las usa, simplemente no pueden comparar (otras respuestas ya revisaron su código de ensamblado muy bien). Solo para bucles, esta es una breve lista de optimizaciones de bucle de lo que comúnmente comprueba un comstackdor (¿crees que podrías hacerlo por ti mismo cuando tu agenda se haya decidido para un progtwig C #?) Si escribes algo en el ensamblaje, piense que debe considerar al menos algunas optimizaciones simples . El ejemplo de libro de escuela para matrices es para desenrollar el ciclo (su tamaño se conoce en tiempo de comstackción). Hazlo y ejecuta tu prueba nuevamente.

En la actualidad, también es poco común tener que usar el lenguaje ensamblador por otra razón: la gran cantidad de CPU diferentes . ¿Quieres apoyarlos a todos? Cada uno tiene una microarchitecture específica y algunos conjuntos de instrucciones específicas . Tienen diferente número de unidades funcionales y las instrucciones de ensamblaje deben organizarse para mantenerlas ocupadas . Si escribe en C, puede usar PGO, pero en el ensamblaje necesitará un gran conocimiento de esa architecture específica (y repensar y rehacer todo para otra architecture ). Para tareas pequeñas, el comstackdor generalmente lo hace mejor, y para tareas complejas, generalmente el trabajo no se paga (y el comstackdor puede hacerlo mejor de todos modos).

Si te sientas y echas un vistazo a tu código probablemente verás que ganarás más para rediseñar tu algoritmo que para traducirlo a ensamblaje (lee esta gran publicación aquí en SO ), hay optimizaciones de alto nivel (y sugerencias al comstackdor) puede aplicar efectivamente antes de que necesite recurrir al lenguaje ensamblador. Probablemente valga la pena mencionar que, a menudo, al utilizar intrínsecos obtendrás una ganancia de rendimiento que estás buscando y el comstackdor aún podrá realizar la mayoría de sus optimizaciones.

Dicho todo esto, incluso cuando puede producir un código de ensamblaje 5 ~ 10 veces más rápido, debe preguntar a sus clientes si prefieren pagar una semana de su tiempo o comprar una CPU 50 $ más rápida . La mayoría de nosotros simplemente no requiere la optimización extrema más a menudo (y especialmente en aplicaciones LOB).

Su código de ensamblaje es excepcionalmente pobre, ligeramente inferior al óptimo y puede mejorarse:

  • Está presionando y mostrando un registro ( EDX ) en su bucle interno. Esto debería moverse fuera del ciclo.
  • Vuelve a cargar los punteros de matriz en cada iteración del ciclo. Esto debería moverse fuera del ciclo.
  • Utiliza la instrucción de loop , que se sabe que es muy lenta en la mayoría de las CPU modernas (posiblemente como resultado de utilizar un antiguo libro de ensamblaje *)
  • No aprovecha ninguna ventaja del desenrollado manual de bucles.
  • No usas instrucciones SIMD disponibles.

Por lo tanto, a menos que mejore ampliamente su conjunto de habilidades con respecto al ensamblador, no tiene sentido que escriba código de ensamblador para el rendimiento.

* Por supuesto, no sé si realmente recibiste la instrucción de loop de un libro de ensamblaje antiguo. Pero casi nunca lo ves en el código del mundo real, ya que cada comstackdor es lo suficientemente inteligente como para no emitir loop , solo lo ves en los libros malos y obsoletos de la IMHO.

Incluso antes de profundizar en el ensamblaje, existen transformaciones de código que existen en un nivel superior.

 static int const TIMES = 100000; void calcuC(int *x, int *y, int length) { for (int i = 0; i < TIMES; i++) { for (int j = 0; j < length; j++) { x[j] += y[j]; } } } 

puede transformarse en via Loop Rotation :

 static int const TIMES = 100000; void calcuC(int *x, int *y, int length) { for (int j = 0; j < length; ++j) { for (int i = 0; i < TIMES; ++i) { x[j] += y[j]; } } } 

que es mucho mejor en lo que respecta a la localidad de memoria.

Esto podría optimizarse aún más, hacer a += b X veces es equivalente a hacer a += X * b así que obtenemos:

 static int const TIMES = 100000; void calcuC(int *x, int *y, int length) { for (int j = 0; j < length; ++j) { x[j] += TIMES * y[j]; } } 

sin embargo, parece que mi optimizador favorito (LLVM) no realiza esta transformación.

[edit] Descubrí que la transformación se realiza si teníamos el restrict calificador para y . De hecho, sin esta restricción, x[j] y y[j] podrían alias a la misma ubicación que hace que esta transformación sea errónea. [editar final]

De todos modos, esta es, creo, la versión C optimizada. Ya es mucho más simple. En base a esto, aquí está mi crack en ASM (dejo que Clang lo genere, soy inútil al respecto):

 calcuAsm: # @calcuAsm .Ltmp0: .cfi_startproc # BB#0: testl %edx, %edx jle .LBB0_2 .align 16, 0x90 .LBB0_1: # %.lr.ph # =>This Inner Loop Header: Depth=1 imull $100000, (%rsi), %eax # imm = 0x186A0 addl %eax, (%rdi) addq $4, %rsi addq $4, %rdi decl %edx jne .LBB0_1 .LBB0_2: # %._crit_edge ret .Ltmp1: .size calcuAsm, .Ltmp1-calcuAsm .Ltmp2: .cfi_endproc 

Me temo que no entiendo de dónde provienen todas esas instrucciones, sin embargo siempre puedes divertirte y probar y ver cómo se compara ... pero igual usaría la versión C optimizada en lugar de la de ensamblaje, en código, mucho más portátil.

Respuesta corta:

Respuesta larga: sí, a menos que realmente sepa lo que está haciendo y tenga una razón para hacerlo.

He arreglado mi código asm:

  __asm { mov ebx,TIMES start: mov ecx,lengthOfArray mov esi,x shr ecx,1 mov edi,y label: movq mm0,QWORD PTR[esi] paddd mm0,QWORD PTR[edi] add edi,8 movq QWORD PTR[esi],mm0 add esi,8 dec ecx jnz label dec ebx jnz start }; 

Resultados para la versión de lanzamiento:

  Function of assembly version: 81 Function of C++ version: 161 

El código de ensamblado en modo de lanzamiento es casi 2 veces más rápido que C ++.

¿Eso significa que no debería confiar en el rendimiento del lenguaje ensamblador escrito por mis manos?

Sí, eso es exactamente lo que significa, y es cierto para todos los idiomas. Si no sabe cómo escribir un código eficiente en el lenguaje X, entonces no debe confiar en su capacidad para escribir código eficiente en X. Por lo tanto, si desea un código eficiente, debe usar otro idioma.

El assembly es particularmente sensible a esto, porque, bueno, lo que ves es lo que obtienes. Usted escribe las instrucciones específicas que desea que ejecute la CPU. Con los lenguajes de alto nivel, hay un comstackdor entre ellos, que puede transformar su código y eliminar muchas ineficiencias. Con el ensamblaje, estás solo.

La única razón para utilizar el lenguaje ensamblador hoy en día es usar algunas funciones a las que no puede acceder el idioma.

Esto aplica a:

  • Progtwigción Kernel que necesita acceder a ciertas características de hardware como la MMU
  • Progtwigción de alto rendimiento que utiliza instrucciones vectoriales o multimedia muy específicas no admitidas por su comstackdor.

Pero los comstackdores actuales son bastante inteligentes, incluso pueden reemplazar dos declaraciones separadas como d = a / b; r = a % b; d = a / b; r = a % b; con una sola instrucción que calcula la división y el rest de una vez si está disponible, incluso si C no tiene dicho operador.

Es cierto que un comstackdor moderno hace un trabajo increíble en la optimización de código, sin embargo, aún así lo alentaría a seguir aprendiendo ensamblaje.

En primer lugar, claramente no te sientes intimidado , eso es una gran ventaja: a continuación, estás en el camino correcto mediante el perfil para validar o descartar las suposiciones de velocidad , estás pidiendo la opinión de personas con experiencia , y tú tener la mejor herramienta de optimización conocida por la humanidad: un cerebro .

A medida que aumente su experiencia, aprenderá cuándo y dónde usarlo (generalmente los bucles más cerrados y más internos en su código, después de que haya sido profundamente optimizado en un nivel algorítmico).

Para inspirarte, te recomendaría buscar artículos de Michael Abrash (si no has tenido noticias suyas, es un gurú de la optimización, ¡incluso colaboró ​​con John Carmack en la optimización del renderizador de software Quake!)

“no existe el código más rápido” – Michael Abrash

He cambiado el código asm:

  __asm { mov ebx,TIMES start: mov ecx,lengthOfArray mov esi,x shr ecx,2 mov edi,y label: mov eax,DWORD PTR [esi] add eax,DWORD PTR [edi] add edi,4 dec ecx mov DWORD PTR [esi],eax add esi,4 test ecx,ecx jnz label dec ebx test ebx,ebx jnz start }; 

Resultados para la versión de lanzamiento:

  Function of assembly version: 41 Function of C++ version: 161 

El código de ensamblado en modo de lanzamiento es casi 4 veces más rápido que C ++. IMHo, la velocidad del código de ensamblaje depende del Progtwigdor

La mayoría de los comstackdores de idiomas de alto nivel están muy optimizados y saben lo que están haciendo. Puede probar y volcar el código de desassembly y compararlo con su ensamblaje original. Creo que verás algunos buenos trucos que está usando tu comstackdor.

Solo por ejemplo, incluso si no estoy seguro de que sea correcto :):

Obra:

 mov eax,0 

cuesta más ciclos que

 xor eax,eax 

que hace lo mismo

El comstackdor conoce todos estos trucos y los usa.

El comstackdor te gana. Lo probaré, pero no daré ninguna garantía. Asumiré que la “multiplicación” de TIMES pretende hacer que sea una prueba de rendimiento más relevante, que x están alineados en 16, y que la length es un múltiplo no nulo de 4. Probablemente todo sea cierto de todos modos.

  mov ecx,length lea esi,[y+4*ecx] lea edi,[x+4*ecx] neg ecx loop: movdqa xmm0,[esi+4*ecx] paddd xmm0,[edi+4*ecx] movdqa [edi+4*ecx],xmm0 add ecx,4 jnz loop 

Como dije, no hago ninguna garantía. Pero me sorprendería si se puede hacer mucho más rápido: el cuello de botella aquí es el rendimiento de la memoria, incluso si todo es un golpe L1.

es un tema muy interesante!
He cambiado el MMX por SSE en el código de Sasha
Aquí están mis resultados:

 Function of C++ version: 315 Function of assembly(simply): 312 Function of assembly (MMX): 136 Function of assembly (SSE): 62 

El código de ensamblaje con SSE es 5 veces más rápido que C ++

Simplemente implementando ciegamente el mismo algoritmo, instrucción por instrucción, en ensamble se garantiza que será más lento de lo que el comstackdor puede hacer.

Es porque incluso la optimización más pequeña que hace el comstackdor es mejor que su código rígido sin ninguna optimización en absoluto.

Por supuesto, es posible vencer al comstackdor, especialmente si se trata de una parte pequeña y localizada del código, incluso tuve que hacerlo yo mismo para obtener una aprox. 4 veces más rápido, pero en este caso tenemos que confiar mucho en el buen conocimiento del hardware y en numerosos trucos aparentemente contraintuitivos.

Es exactamente lo que significa. Deje las micro-optimizaciones al comstackdor.

Me encanta este ejemplo porque demuestra una lección importante sobre el código de bajo nivel. Sí, puede escribir el ensamblaje tan rápido como su código C. Esto es tautológicamente cierto, pero no significa necesariamente nada. Claramente, alguien puede, de lo contrario, el ensamblador no sabría las optimizaciones adecuadas.

Del mismo modo, el mismo principio se aplica a medida que sube la jerarquía de la abstracción del lenguaje. Sí, puede escribir un analizador sintáctico en C que sea tan rápido como un script de perl rápido y sucio, y muchas personas lo hacen. Pero eso no significa que porque usaste C, tu código será rápido. En muchos casos, los idiomas de alto nivel hacen optimizaciones que quizás nunca hayas considerado.

Como comstackdor, reemplazaría un bucle con un tamaño fijo por muchas tareas de ejecución.

 int a = 10; for (int i = 0; i < 3; i += 1) { a = a + i; } 

Producirá

 int a = 10; a = a + 0; a = a + 1; a = a + 2; 

y eventualmente sabrá que "a = a + 0"; es inútil por lo que eliminará esta línea. Esperemos que algo en tu cabeza ahora esté dispuesto a adjuntar algunas opciones de optimización como comentario. Todas esas optimizaciones muy efectivas harán que el lenguaje comstackdo sea más rápido.

En muchos casos, la forma óptima de realizar alguna tarea puede depender del contexto en el que se realiza la tarea. Si una rutina está escrita en lenguaje ensamblador, generalmente no será posible variar la secuencia de instrucciones según el contexto. Como un simple ejemplo, considere el siguiente método simple:

 inline void set_port_high(void) { (*((volatile unsigned char*)0x40001204) = 0xFF); } 

Un comstackdor de código ARM de 32 bits, dado lo anterior, probablemente lo renderice como algo así como:

 ldr r0,=0x40001204 mov r1,#0 strb r1,[r0] [a fourth word somewhere holding the constant 0x40001204] 

o quizás

 ldr r0,=0x40001000 ; Some assemblers like to round pointer loads to multiples of 4096 mov r1,#0 strb r1,[r0+0x204] [a fourth word somewhere holding the constant 0x40001000] 

Eso podría optimizarse ligeramente en el código ensamblado a mano, ya sea como:

 ldr r0,=0x400011FF strb r0,[r0+5] [a third word somewhere holding the constant 0x400011FF] 

o

 mvn r0,#0xC0 ; Load with 0x3FFFFFFF add r0,r0,#0x1200 ; Add 0x1200, yielding 0x400011FF strb r0,[r0+5] 

Ambos enfoques ensamblados a mano requerirían 12 bytes de espacio de código en lugar de 16; este último reemplazaría una “carga” con un “agregar”, que en un ARM7-TDMI ejecutaría dos ciclos más rápido. Si el código se iba a ejecutar en un contexto donde r0 no sabía / no importa, las versiones en lenguaje ensamblador serían algo mejores que la versión comstackda. Por otro lado, supongamos que el comstackdor sabía que algún registro [por ejemplo, r5] iba a contener un valor que estaba dentro de 2047 bytes de la dirección deseada 0x40001204 [por ejemplo, 0x40001000], y además sabía que algún otro registro [por ejemplo, r7] iba para mantener un valor cuyos bits bajos eran 0xFF. En ese caso, un comstackdor podría optimizar la versión C del código simplemente:

 strb r7,[r5+0x204] 

Mucho más corto y más rápido que incluso el código de ensamblaje optimizado a mano. Además, supongamos que set_port_high ocurrió en el contexto:

 int temp = function1(); set_port_high(); function2(temp); // Assume temp is not used after this 

Nada inverosímil cuando se codifica un sistema integrado. Si set_port_high está escrito en código ensamblador, el comstackdor tendría que mover r0 (que retiene el valor devuelto de la function1 ) en otro lugar antes de invocar el código ensamblador, y luego mover ese valor a r0 después (ya que function2 esperará su primer parámetro en r0), por lo que el código de ensamblado “optimizado” necesitaría cinco instrucciones. Incluso si el comstackdor no conocía ningún registro que tenga la dirección o el valor para almacenar, su versión de cuatro instrucciones (que podría adaptarse para usar cualquier registro disponible, no necesariamente r0 y r1) vencería al ensamblado “optimizado” -la versión del lenguaje. Si el comstackdor tenía la dirección y los datos necesarios en r5 y r7 como se describió anteriormente, la function1 no alteraría esos registros, y por lo tanto podría reemplazar set_port_high con una sola instrucción strb cuatro instrucciones más pequeñas y más rápidas que el ensamblado “optimizado a mano” código.

Tenga en cuenta que el código ensamblador optimizado a mano puede superar a un comstackdor en casos donde el progtwigdor conoce el flujo preciso del progtwig, pero los comstackdores brillan en casos donde se escribe un código antes de que se conozca su contexto, o donde una parte del código fuente puede ser invocado desde contextos múltiples [si set_port_high se usa en cincuenta lugares diferentes en el código, el comstackdor podría decidir independientemente para cada uno de ellos la mejor manera de expandirlo].

En general, sugeriría que el lenguaje ensamblador es capaz de producir las mayores mejoras de rendimiento en aquellos casos en que cada fragmento de código se puede abordar desde un número muy limitado de contextos, y es probable que sea perjudicial para el rendimiento en lugares donde una pieza de el código puede ser abordado desde muchos contextos diferentes. Curiosamente (y convenientemente), los casos en los que el ensamblaje es más beneficioso para el rendimiento son a menudo aquellos en los que el código es más directo y fácil de leer. Los lugares en los que el código de lenguaje ensamblador se convertiría en un lío pegajoso son a menudo aquellos en los que escribir en ensamblaje ofrecería el menor beneficio de rendimiento.

[Nota secundaria: hay algunos lugares donde el código de ensamblaje se puede utilizar para producir un desastre pegajoso hiper-optimizado; for example, one piece of code I did for the ARM needed to fetch a word from RAM and execute one of about twelve routines based upon the upper six bits of the value (many values mapped to the same routine). I think I optimized that code to something like:

 ldrh r0,[r1],#2! ; Fetch with post-increment ldrb r1,[r8,r0 asr #10] sub pc,r8,r1,asl #2 

The register r8 always held the address of the main dispatch table (within the loop where the code spend 98% of its time, nothing ever used it for any other purpose); all 64 entries referred to addresses in the 256 bytes preceding it. Since the primary loop had in most cases a hard execution-time limit of about 60 cycles, the nine-cycle fetch and dispatch was very instrumental toward meeting that goal. Using a table of 256 32-bit addresses would have been one cycle faster, but would have gobbled up 1KB of very precious RAM [flash would have added more than one wait state]. Using 64 32-bit addresses would have required adding an instruction to mask off some bits from the fetched word, and would still have gobbled up 192 more bytes than the table I actually used. Using the table of 8-bit offsets yielded very compact and fast code, but not something I would expect a compiler would ever come up with; I also would not expect a compiler to dedicate a register “full time” to holding the table address.

The above code was designed to run as a self-contained system; it could periodically call C code, but only at certain times when the hardware with which it was communicating could safely be put into an “idle” state for two roughly-one-millisecond intervals every 16ms.

In recent times, all the speed optimisations that I have done were replacing brain damaged slow code with just reasonable code. But for things were speed was really critical and I put serious effort into making something fast, the result was always an iterative process, where each iteration gave more insight into the problem, finding ways how to solve the problem with fewer operations. The final speed always depended on how much insight I got into the problem. If at any stage I used assembly code, or C code that was over-optimised, the process of finding a better solution would have suffered and the end result would be slower.

C++ is faster unless you are using assembly language with deeper knowledge with the correct way.

When I code in ASM, I reorganize the instructions manually so the CPU can execute more of them in parallel when logically possible. I barely use RAM when I code in ASM for example: There could be 20000+ lines of code in ASM and I not ever once used push/pop.

You could potentially jump in the middle of the opcode to self-modify the code and the behavior without the possible penalty of self-modifying code. Accessing registers takes 1 tick(sometimes takes .25 ticks) of the CPU.Accessing the RAM could take hundreds.

For my last ASM adventure, I never once used the RAM to store a variable(for thousands of lines of ASM). ASM could be potentially unimaginably faster than C++. But it depends on a lot of variable factors such as:

 1. I was writing my apps to run on the bare metal. 2. I was writing my own boot loader that was starting my programs in ASM so there was no OS management in the middle. 

I am now learning C# and C++ because i realized productivity matters!! You could try to do the fastest imaginable programs using pure ASM alone in the free time. But in order to produce something, use some high level language.

For example, the last program I coded was using JS and GLSL and I never noticed any performance issue, even speaking about JS which is slow. This is because the mere concept of programming the GPU for 3D makes the speed of the language that sends the commands to the GPU almost irrelevant.

The speed of assembler alone on the bare metal is irrefutable. Could it be even slower inside C++? – It could be because you are writing assembly code with a compiler not using an assembler to start with.

My personal council is to never write assembly code if you can avoid it, even though I love assembly.

All the answers here seem to exclude one aspect: sometimes we don’t write code to achieve a specific aim, but for the sheer fun of it. It may not be economical to invest the time to do so, but arguably there is no greater satisfaction than beating the fastest compiler optimized code snippet in speed with a manually rolled asm alternative.

A c++ compiler would, after optimization at the organizational level, produce code that would utilize the built in functions of the targeted cpu. HLL will never outrun or out-perform assembler for several reasons; 1.) HLL will be compiled and output with Accessor code, boundary checking and possibly built in garbage collection (formerly addressing scope in the OOP mannerism) all requiring cycles (flips and flops). HLL does an excellent job these days (including newer C++ and others like GO), but if they outperform assembler (namely your code) you need to consult the CPU Documentation -comparisons with sloppy code are most certainly inconclusive and compiled langs like assembler all resolve down to op-code HLL abstracts the details and does not eliminate them else you app isn’t going to run if it’s even recognize by the host OS.

Most assembler code (primarily objects) are output as “headless” for inclusion into other executable formats with far less processing required hence it will be much faster, but far more unsecure; if an executable is output by the assembler (NAsm, YAsm; etc.) it will still run faster until it completely matches the HLL code in functionality then results may be accurately weighed.

Calling an assembler based code object from HLL in any format will inherently add processing overhead as well in addition to memory space calls using globally allocated memory for variable/constant data types (this applies to both LLL and HLL). Remember that the final output is using the CPU ultimately as its api and abi relative to the hardware (opcode) and both, assemblers and “HLL compilers” are essentially/fundamentally identical with the only true exception being readability (grammatical).

Hello world console application in assembler using FAsm is 1.5 KB (and this is in Windows even smaller in FreeBSD and Linux) and outperforms anything GCC can throw out on its best day; reasons are implicit padding with nops, access validation and boundary checking to name a few. The real goal is clean HLL libs and an optimizable compiler that targets a cpu in a “hardcore” manner and most do these days (finally). GCC is not better than YAsm -it is the coding practices and understanding of the developer that are in question and “optimization” comes after novice exploration and interim training & experience.

Compilers have to link and assemble for output in the same opcode as an assembler because those codes are all that a CPU will except (CISC or RISC [PIC too]). YAsm optimized and cleaned up a great deal on early NAsm ultimately speeding up all output from that assembler, but even then YAsm still, like NAsm, produce executables with external dependencies targeting OS libraries on behalf of the developer so mileage may vary. In closing C++ is at a point that is incredible and far more safe than assembler for 80+ percent especially in the commercial sector…

Assembly could be faster if your compiler generates a lot of OO support code.

Editar:

To downvoters: the OP wrote “should I … focus on C++ and forget about assembly language?” and I stand by my answer. You always need to keep an eye on the code OO generates, particularly when using methods. Not forgetting about assembly language means that you will periodically review the assembly your OO code generates which I believe is a must for writing well-performing software.

Actually, this pertains to all compileable code, not just OO.