¿El compilador de C ++ elimina / optimiza paréntesis inútiles?

19

Será el código

int a = ((1 + 2) + 3); // Easy to read

corre más lento que

int a = 1 + 2 + 3; // (Barely) Not quite so easy to read

o son compiladores modernos lo suficientemente inteligentes como para eliminar / optimizar paréntesis "inútiles".

Puede parecer una preocupación de optimización muy pequeña, pero elegir C ++ sobre C # / Java / ... se trata de optimizaciones (en mi humilde opinión).

Sarga
fuente
99
Creo que C # y Java también optimizarán eso. Creo que cuando analizan y crean AST, simplemente eliminarán cosas inútiles obvias como esa.
Farid Nouri Neshat
55
Todo lo que he leído apunta a que la compilación JIT facilita fácilmente la compilación anticipada, por lo que solo no es un argumento convincente. Aparece la programación del juego, la verdadera razón para favorecer la compilación anticipada es que es predecible, con la compilación JIT nunca se sabe cuándo el compilador entrará en acción e intentará comenzar a compilar el código. Pero me gustaría señalar que la compilación anticipada de código nativo no es mutuamente exclusiva con la recolección de basura, ver, por ejemplo, Standard ML y D. Y he visto argumentos convincentes de que la recolección de basura es más eficiente ...
Doval
77
... que RAII y punteros inteligentes, por lo que es más una cuestión de seguir la ruta bien establecida (C ++) frente a la ruta relativamente poco común de hacer programación de juegos en esos lenguajes. También me gustaría señalar que preocuparse por los paréntesis es una locura: veo de dónde vienes, pero eso es una microoptimización ridícula. La elección de estructuras de datos y algoritmos en su programa definitivamente dominará el rendimiento, no tales trivialidades.
Doval
66
Um ... ¿Qué tipo de optimización esperas exactamente? Si habla de análisis estático, en la mayoría de los idiomas que conozco, esto será reemplazado por el resultado estáticamente conocido (las implementaciones basadas en LLVM incluso lo hacen cumplir, AFAIK). Si está hablando del orden de ejecución, no importa, ya que es la misma operación y sin efectos secundarios. La adición necesita dos operandos de todos modos. Y si está utilizando esto para comparar C ++, Java y C # con respecto al rendimiento, parece que no tiene una idea clara de qué son las optimizaciones y cómo funcionan, por lo que debe centrarse en aprender eso.
Theodoros Chatzigiannakis
55
Me pregunto por qué a) consideras que la expresión entre paréntesis es más legible (para mí solo parece fea, engañosa (¿por qué enfatizan este orden en particular? ¿No debería ser útil aquí?) Y torpe) b) por qué pensarías sin entre paréntesis podría funcionar mejor (analizar claramente los parens es más fácil para una máquina que tener que razonar sobre las fijaciones del operador. Sin embargo, como dice Marc van Leuwen, esto no tiene ninguna influencia en el tiempo de ejecución).
Leftaroundabout

Respuestas:

87

El compilador en realidad nunca inserta ni elimina paréntesis; solo crea un árbol de análisis (en el que no hay paréntesis) correspondiente a su expresión, y al hacerlo debe respetar los paréntesis que escribió. Si pone entre paréntesis su expresión, entonces también estará inmediatamente claro para el lector humano qué es ese árbol de análisis; si llega al extremo de poner paréntesis descaradamente redundantes como int a = (((0)));entonces, causará un estrés inútil en las neuronas del lector y también desperdiciará algunos ciclos en el analizador, sin cambiar el árbol de análisis resultante (y, por lo tanto, el código generado ) el más mínimo detalle.

Si no escribe ningún paréntesis, el analizador aún debe hacer su trabajo de crear un árbol de análisis, y las reglas para la precedencia del operador y la asociatividad le dicen exactamente qué árbol de análisis debe construir. Puede considerar que esas reglas le dicen al compilador qué paréntesis (implícitos) debe insertar en su código, aunque el analizador en realidad nunca trata con paréntesis en este caso: simplemente se ha construido para producir el mismo árbol de análisis como si se tratara de paréntesis estuvieron presentes en ciertos lugares. Si coloca paréntesis exactamente en esos lugares, como en int a = (1+2)+3;(la asociatividad de +está a la izquierda), entonces el analizador llegará al mismo resultado por una ruta ligeramente diferente. Si pone entre paréntesis diferentes como enint a = 1+(2+3);entonces está forzando un árbol de análisis diferente, lo que posiblemente hará que se genere un código diferente (aunque tal vez no, ya que el compilador puede aplicar transformaciones después de construir el árbol de análisis, siempre y cuando el efecto de ejecutar el código resultante nunca sea diferente para eso). Suponiendo que hay una diferencia en el código de devolución, no se puede decir nada en general sobre cuál es más eficiente; el punto más importante es, por supuesto, que la mayoría de las veces los árboles de análisis no dan expresiones matemáticamente equivalentes, por lo que comparar su velocidad de ejecución no viene al caso: uno debe escribir la expresión que da el resultado adecuado.

Entonces, el resultado es: usar paréntesis según sea necesario para la corrección, y según se desee para la legibilidad; si son redundantes, no tienen ningún efecto en la velocidad de ejecución (y un efecto insignificante en el tiempo de compilación).

Y nada de esto tiene nada que ver con la optimización , que se produce mucho después de que se ha construido el árbol de análisis, por lo que no puede saber cómo se construyó el árbol de análisis. Esto se aplica sin cambios desde los compiladores más antiguos y estúpidos a los más inteligentes y modernos. Solo en un lenguaje interpretado (donde el "tiempo de compilación" y el "tiempo de ejecución" coinciden) podría haber una penalización por paréntesis redundantes, pero aun así creo que la mayoría de estos lenguajes están organizados de modo que al menos la fase de análisis se realiza solo una vez para cada declaración (almacenando alguna forma previamente analizada para su ejecución).

Marc van Leeuwen
fuente
s / oldes / más antiguo /. Buena respuesta, +1.
David Conrad
23
Advertencia total: "La pregunta no está muy bien planteada". No estoy de acuerdo, tomando "bien dicho" significa "claramente sugiriendo qué brecha de conocimiento quiere llenar el consultante". En esencia, la pregunta es "optimizar para X, elegir A o B, y por qué? ¿Qué sucede debajo?", Que al menos para mí sugiere muy claramente cuál es la brecha de conocimiento. La falla en la pregunta, que acertadamente señala y aborda muy bien, es que se basa en un modelo mental defectuoso.
Jonas Kölker
Para a = b + c * d;, a = b + (c * d);sería [inofensiva] redundante paréntesis. Si te ayudan a hacer que el código sea más legible, está bien. a = (b + c) * d;serían paréntesis no redundantes: en realidad cambian el árbol de análisis resultante y dan un resultado diferente. Perfectamente legal para hacer (de hecho, necesario), pero no son lo mismo que la agrupación predeterminada implícita.
Phil Perry
1
@OrangeDog es cierto, es una pena que el comentario de Ben sacara a algunas personas a las que les gusta afirmar que las máquinas virtuales son más rápidas que las nativas.
gbjbaanb
1
@ JonasKölker: Mi oración inicial en realidad se refiere a la pregunta formulada en el título: uno realmente no puede responder una pregunta sobre si el compilador inserta o elimina paréntesis, ya que eso se basa en una idea errónea de cómo funcionan los compiladores. Pero estoy de acuerdo en que está bastante claro qué brecha de conocimiento debe abordarse.
Marc van Leeuwen
46

Los paréntesis están ahí exclusivamente para su beneficio, no los compiladores. El compilador creará el código de máquina correcto para representar su declaración.

Para su información, el compilador es lo suficientemente inteligente como para optimizarlo por completo si puede. En sus ejemplos, esto se convertiría en int a = 6;tiempo de compilación.

gbjbaanb
fuente
99
Absolutamente: coloque tantos paréntesis como desee y deje que el compilador haga el trabajo duro de descubrir lo que quiere :)
gbjbaanb
23
La verdadera programación de @Serge se trata más de la legibilidad de su código que del rendimiento. Te odiarás el próximo año cuando necesites depurar un bloqueo y solo tengas un código "optimizado".
Ratchet Freak
1
@ratchetfreak, tienes razón, pero también sé cómo comentar mi código. int a = 6; // = (1 + 2) + 3
Serge
20
@Serge Sé que no se puede sostener después de un año de ajustes, en los comentarios y el código de tiempo va a ir fuera de sincronización y luego se termina conint a = 8;// = 2*3 + 5
monstruo de trinquete
21
o desde www.thedailywtf.com:int five = 7; //HR made us change this to six...
Mooing Duck
23

La respuesta a la pregunta que realmente hizo es no, pero la respuesta a la pregunta que quería hacer es sí. Agregar paréntesis no ralentiza el código.

Hiciste una pregunta sobre la optimización, pero los paréntesis no tienen nada que ver con la optimización. El compilador aplica una variedad de técnicas de optimización con la intención de mejorar el tamaño o la velocidad del código generado (a veces ambos). Por ejemplo, podría tomar la expresión A ^ 2 (A al cuadrado) y reemplazarla por A x A (A multiplicada por sí misma) si eso es más rápido. La respuesta aquí es no, el compilador no hace nada diferente en su fase de optimización, dependiendo de si los paréntesis están presentes o no.

Creo que querías preguntar si el compilador aún genera el mismo código si agregas paréntesis innecesarios a una expresión, en lugares que crees que podrían mejorar la legibilidad. En otras palabras, si agrega paréntesis, el compilador es lo suficientemente inteligente como para eliminarlos de nuevo en lugar de generar un código más pobre. La respuesta es sí, siempre.

Déjame decir eso con cuidado. Si agrega paréntesis a una expresión que son estrictamente innecesarios (no tienen ningún efecto sobre el significado o el orden de evaluación de una expresión), el compilador los descartará en silencio y generará el mismo código.

Sin embargo, existen ciertas expresiones donde los paréntesis aparentemente innecesarios en realidad cambiarán el orden de evaluación de una expresión y, en ese caso, el compilador generará código para poner en práctica lo que realmente escribió, que podría ser diferente de lo que pretendía. Aquí hay un ejemplo. ¡No hagas esto!

short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c;    // ok
int d = (-a + b) + c;  // ok, same code
int d = (-a + b + c);  // ok, same code
int d = ((((-a + b)) + c));  // ok, same code
int d = -a + (b + c);  // undefined behaviour, different code

¡Agregue paréntesis si lo desea, pero asegúrese de que realmente sean innecesarios!

Yo nunca. Existe el riesgo de error sin beneficio real.


Nota al pie: el comportamiento sin signo ocurre cuando una expresión entera con signo se evalúa a un valor que está fuera del rango que puede expresar, en este caso -32767 a +32767. Este es un tema complejo, fuera del alcance de esta respuesta.

david.pfx
fuente
El comportamiento indefinido en la última línea se debe a que un corto firmado solo tiene 15 bits después del signo, por lo que un tamaño máximo de 32767, ¿verdad? En ese ejemplo trivial, el compilador debería advertir sobre el desbordamiento, ¿verdad? +1 para un contraejemplo, de cualquier manera. Si fueran parámetros para una función, no recibiría una advertencia. Además, si arealmente no se puede firmar, liderar su cálculo también -a + bpodría desbordarse fácilmente, si afuera negativo y bpositivo.
Patrick M
@PatrickM: ver edición. El comportamiento indefinido significa que el compilador puede hacer lo que quiera, incluso emitir una advertencia o no. La aritmética sin signo no produce UB, pero se reduce el módulo a la siguiente potencia más alta de dos.
david.pfx
La expresión (b+c)en la última línea promoverá sus argumentos int, por lo que a menos que el compilador defina intque es de 16 bits (ya sea porque es antiguo o se dirige a un pequeño microcontrolador), la última línea sería perfectamente legítima.
supercat
@supercat: No lo creo. El tipo común y el tipo del resultado deben ser cortos int. Si eso no es algo que se le haya preguntado, ¿tal vez le gustaría publicar una pregunta?
david.pfx
@ david.pfx: Las reglas de las promociones aritméticas en C son bastante claras: todo lo más pequeño a lo que intse promociona a intmenos que ese tipo no pueda representar todos sus valores, en cuyo caso se promociona unsigned int. Los compiladores pueden omitir las promociones si todos los comportamientos definidos serían los mismos que si se incluyeran las promociones . En una máquina donde los tipos de 16 bits se comportan como un anillo algebraico abstracto envolvente, (a + b) + c y a + (b + c) serán equivalentes. intSin embargo, si fuera un tipo de 16 bits atrapado en el desbordamiento, entonces habría casos en los que una de las expresiones ...
supercat
7

Los corchetes solo están ahí para que pueda manipular el orden de precedencia del operador. Una vez compilados, los corchetes ya no existen porque el tiempo de ejecución no los necesita. El proceso de compilación elimina todos los corchetes, espacios y otro tipo de azúcar sintáctica que usted y yo necesitamos y cambia todos los operadores en algo [mucho] más simple para que la computadora lo ejecute.

Entonces, dónde tú y yo podríamos ver ...

  • "int a = ((1 + 2) + 3);"

... un compilador podría emitir algo más como esto:

  • Char [1] :: "a"
  • Int32 :: DeclareStackVariable ()
  • Int32 :: 0x00000001
  • Int32 :: 0x00000002
  • Int32 :: Agregar ()
  • Int32 :: 0x00000003
  • Int32 :: Agregar ()
  • Int32 :: AssignToVariable ()
  • void :: DiscardResult ()

El programa se ejecuta comenzando desde el principio y ejecutando cada instrucción a su vez.
La prioridad del operador ahora es "por orden de llegada".
Todo está fuertemente tipado, porque el compilador trabajó todo eso mientras desgarraba la sintaxis original.

OK, no se parece en nada a lo que tú y yo tratamos, ¡pero no lo estamos ejecutando!

Phill W.
fuente
44
No hay un solo compilador de C ++ que produzca algo ni remotamente como este. Generalmente producen código de CPU real, y el ensamblaje tampoco se ve así.
MSalters
3
La intención aquí era demostrar la diferencia en la estructura entre el código tal como está escrito y la salida compilada. incluso en este caso la mayoría de la gente no sería capaz de leer el código máquina o en el ensamblaje
dhall
@MSalters Nitpicking clang emitiría 'algo así' si trata a LLVM ISA como 'algo así' (es SSA no basado en pila). Dado que es posible escribir el backend JVM para LLVM y JVM ISA (AFAIK) está basado en pila clang-> llvm-> JVM se vería muy similar.
Maciej Piechotka
No creo que LLVM ISA tenga la capacidad de definir variables de pila de nombres usando literales de cadena de tiempo de ejecución (solo las dos primeras instrucciones). Esto está mezclando seriamente el tiempo de ejecución y el tiempo de compilación. La distinción es importante porque esta pregunta es exactamente acerca de esa confusión.
MSalters
6

Depende de si es de coma flotante o no:

  • En la aritmética de coma flotante, la adición no es asociativa, por lo que el optimizador no puede reordenar las operaciones (a menos que agregue el interruptor del compilador fastmath).

  • En operaciones enteras pueden reordenarse.

En su ejemplo, ambos se ejecutarán exactamente al mismo tiempo porque se compilarán con el mismo código exacto (la suma se evalúa de izquierda a derecha).

sin embargo, incluso Java y C # podrán optimizarlo, solo lo harán en tiempo de ejecución.

monstruo de trinquete
fuente
+1 por mencionar que las operaciones de coma flotante no son asociativas.
Doval
En el ejemplo de la pregunta, los paréntesis no alteran la asociatividad predeterminada (izquierda), por lo que este punto es discutible.
Marc van Leeuwen
1
Sobre la última oración, no lo creo. Tanto en java ayc # el compilador producirá bytecode / IL optimizado. El tiempo de ejecución no se ve afectado.
Stefano Altieri
IL no funciona en expresiones de este tipo, las instrucciones toman un cierto número de valores de una pila y devuelven un cierto número de valores (generalmente 0 o 1) a la pila. Hablar de este tipo de cosas que se optimizan en tiempo de ejecución en C # no tiene sentido.
Jon Hanna
6

El compilador típico de C ++ se traduce en código de máquina, no en C ++ en sí . Elimina parens inútiles, sí, porque para cuando termina, no hay parens en absoluto. El código de máquina no funciona de esa manera.

El más cuchara
fuente
5

Ambos códigos terminan codificados como 6:

movl    $6, -4(%rbp)

Comprueba por ti mismo aquí

Monohilo
fuente
2
¿Qué es esta asamblea.ynh.io cosita?
Peter Mortensen
Es un pseudocompilador que transforma el código C en el ensamblaje x86 en línea
MonoThreaded
1

No, pero sí, pero tal vez, pero tal vez a la inversa, pero no.

Como la gente ya ha señalado, (suponiendo un lenguaje donde la suma es asociativa a la izquierda, como C, C ++, C # o Java) la expresión ((1 + 2) + 3)es exactamente equivalente a1 + 2 + 3 . Son diferentes formas de escribir algo en el código fuente, que tendrían un efecto cero en el código de máquina resultante o el código de bytes.

De cualquier manera, el resultado será una instrucción para, por ejemplo, agregar dos registros y luego agregar un tercero, o tomar dos valores de una pila, agregarlo, empujarlo hacia atrás, luego tomarlo y otro y agregarlos, o agregar tres registros en una sola operación, o alguna otra forma de sumar tres números dependiendo de lo que sea más sensato en el siguiente nivel (el código de máquina o el código de byte). En el caso del código de bytes, eso a su vez probablemente sufrirá una reestructuración similar, por ejemplo, el equivalente IL de esto (que sería una serie de cargas en una pila, y apareciendo pares para agregar y luego retrasar el resultado) no daría como resultado una copia directa de esa lógica a nivel de código de máquina, sino algo más sensible para la máquina en cuestión.

Pero hay algo más en tu pregunta.

En el caso de cualquier compilador sano de C, C ++, Java o C #, esperaría que el resultado de ambas afirmaciones tenga exactamente los mismos resultados que:

int a = 6;

¿Por qué el código resultante debería perder tiempo haciendo cálculos matemáticos en literales? Ningún cambio en el estado del programa detendrá el resultado de 1 + 2 + 3ser 6, así que eso es lo que debería ir en el código que se ejecuta. De hecho, tal vez ni siquiera eso (dependiendo de lo que hagas con ese 6, tal vez podamos tirar todo por la borda; e incluso C # con su filosofía de "no optimizar en gran medida, ya que el jitter optimizará esto de todos modos" producirá el equivalente int a = 6o simplemente tirar todo como innecesario).

Sin embargo, esto nos lleva a una posible extensión de su pregunta. Considera lo siguiente:

int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;

y

int c;
if(d < 100)
  c = 0;
else
  c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);

(Tenga en cuenta que estos dos últimos ejemplos no son válidos en C #, mientras que todo lo demás aquí sí lo es, y son válidos en C, C ++ y Java).

Aquí nuevamente tenemos un código exactamente equivalente en términos de salida. Como no son expresiones constantes, no se calcularán en tiempo de compilación. Es posible que una forma sea más rápida que otra. ¿Cual es mas rápido? Eso dependería del procesador y quizás de algunas diferencias de estado bastante arbitrarias (particularmente porque si uno es más rápido, no es probable que sea mucho más rápido).

Y no están completamente ajenos a su pregunta, ya que se refieren principalmente a las diferencias en el orden en que se hace algo conceptualmente .

En cada uno de ellos, hay una razón para sospechar que uno puede ser más rápido que el otro. Los decrementos individuales pueden tener una instrucción especializada, por lo que (b / 2)--podrían ser más rápidos que (b - 2) / 2. d * 32tal vez podría producirse más rápido convirtiéndolo en d << 5algo d * 32 - dmás rápido que d * 31. Las diferencias entre los dos últimos son particularmente interesantes; uno permite omitir parte del procesamiento en algunos casos, pero el otro evita la posibilidad de una predicción errónea de la rama.

Entonces, esto nos deja con dos preguntas: 1. ¿Una es realmente más rápida que la otra? 2. ¿Un compilador convertirá lo más lento en lo más rápido?

Y la respuesta es 1. Depende. 2. Quizás.

O para expandirse, depende porque depende del procesador en cuestión. Ciertamente, han existido procesadores donde el equivalente de código de máquina ingenuo de uno sería más rápido que el equivalente de código de máquina ingenuo del otro. A lo largo de la historia de la computación electrónica, tampoco ha habido uno que sea siempre más rápido (el elemento de predicción errónea de la rama en particular no era relevante para muchos cuando las CPU no canalizadas eran más comunes).

Y tal vez, porque hay un montón de optimizaciones diferentes que harán los compiladores (y las fluctuaciones, y los motores de script), y aunque algunos pueden ser obligatorios en ciertos casos, generalmente podremos encontrar algunas piezas de código lógicamente equivalente que Incluso el compilador más ingenuo tiene exactamente los mismos resultados y algunas piezas de código lógicamente equivalente donde incluso el más sofisticado produce código más rápido para uno que para el otro (incluso si tenemos que escribir algo totalmente patológico solo para demostrar nuestro punto).

Puede parecer una preocupación de optimización muy pequeña,

No. Incluso con diferencias más complicadas que las que presento aquí, parece una preocupación absolutamente minuciosa que no tiene nada que ver con la optimización. En todo caso, es una cuestión de pesimismo, ya que sospecha que lo más difícil de leer ((1 + 2) + 3podría ser más lento que lo más fácil de leer 1 + 2 + 3.

pero elegir C ++ sobre C # / Java / ... se trata de optimizaciones (en mi humilde opinión).

Si de eso se trata realmente la elección de C ++ sobre C # o Java, diría que la gente debería grabar su copia de Stroustrup e ISO / IEC 14882 y liberar el espacio de su compilador de C ++ para dejar espacio para algunos MP3 más o algo así.

Estos idiomas tienen diferentes ventajas entre sí.

Una de ellas es que C ++ sigue siendo generalmente más rápido y más liviano en el uso de la memoria. Sí, hay ejemplos en los que C # y / o Java son más rápidos y / o tienen un mejor uso de la memoria de la aplicación, y estos se están volviendo más comunes a medida que las tecnologías involucradas mejoran, pero aún podemos esperar que el programa promedio escrito en C ++ sea un ejecutable más pequeño que hace su trabajo más rápido y usa menos memoria que el equivalente en cualquiera de esos dos idiomas.

Esto no es optimización.

La optimización a veces se usa para significar "hacer que las cosas vayan más rápido". Es comprensible, porque a menudo, cuando realmente estamos hablando de "optimización", en realidad estamos hablando de hacer que las cosas vayan más rápido, por lo que uno se ha convertido en una abreviatura para el otro y admito que mal uso la palabra de esa manera.

La palabra correcta para "hacer que las cosas vayan más rápido" no es optimización . La palabra correcta aquí es mejora . Si realiza un cambio en un programa y la única diferencia significativa es que ahora es más rápido, no está optimizado de ninguna manera, es simplemente mejor.

La optimización es cuando hacemos una mejora con respecto a un aspecto particular y / o caso particular. Ejemplos comunes son:

  1. Ahora es más rápido para un caso de uso, pero más lento para otro.
  2. Ahora es más rápido, pero usa más memoria.
  3. Ahora es más ligero en la memoria, pero más lento.
  4. Ahora es más rápido, pero más difícil de mantener.
  5. Ahora es más fácil de mantener, pero más lento.

Tales casos estarían justificados si, por ejemplo:

  1. El caso de uso más rápido es más común o se ve obstaculizado más severamente para empezar.
  2. El programa era inaceptablemente lento, y tenemos mucha RAM libre.
  3. El programa se estaba deteniendo porque usaba tanta RAM que pasaba más tiempo intercambiando que ejecutando su procesamiento súper rápido.
  4. El programa era inaceptablemente lento y el código más difícil de entender está bien documentado y es relativamente estable.
  5. El programa todavía es aceptablemente rápido, y la base de código más comprensible es más barata de mantener y permite realizar otras mejoras más fácilmente.

Pero, estos casos tampoco estarían justificados en otros escenarios: el código no se ha mejorado por una medida de calidad absoluta infalible, se ha mejorado en un aspecto particular que lo hace más adecuado para un uso particular; optimizado

Y la elección del lenguaje tiene un efecto aquí, porque la velocidad, el uso de la memoria y la legibilidad pueden verse afectados por él, pero también la compatibilidad con otros sistemas, la disponibilidad de bibliotecas, la disponibilidad de tiempos de ejecución, la madurez de esos tiempos de ejecución en un sistema operativo dado (Por mis pecados, de alguna manera terminé teniendo Linux y Android como mis sistemas operativos favoritos y C # como mi lenguaje favorito, y aunque Mono es genial, pero todavía me encuentro con este bastante).

Decir que "elegir C ++ sobre C # / Java / ... tiene que ver con optimizaciones" solo tiene sentido si cree que C ++ realmente apesta, porque la optimización se trata de "mejor a pesar de ..." no "mejor". Si crees que C ++ es mejor a pesar de sí mismo, entonces lo último que necesitas es preocuparte por esas microopciones posibles tan pequeñas. De hecho, probablemente sea mejor que lo abandones; ¡los hackers felices también son una calidad para optimizar!

Sin embargo, si se inclina a decir "Me encanta C ++, y una de las cosas que me encanta es exprimir ciclos adicionales", entonces ese es un asunto diferente. Sigue siendo un caso que las microopciones solo valen la pena si pueden ser un hábito reflexivo (es decir, la forma en que tiende a codificar de forma natural será más rápido que más lento). De lo contrario, ni siquiera son optimización prematura, son pesimismo prematuro que empeoran las cosas.

Jon Hanna
fuente
0

Los paréntesis están ahí para decirle al compilador en qué orden deben evaluarse las expresiones. A veces son inútiles (excepto que mejoran o empeoran la legibilidad), porque especifican el orden que se usaría de todos modos. A veces cambian el orden. En

int a = 1 + 2 + 3;

prácticamente todos los idiomas existentes tienen una regla de que la suma se evalúa agregando 1 + 2, luego agregando el resultado más 3. Si escribió

int a = 1 + (2 + 3);

entonces el paréntesis forzaría un orden diferente: primero sumar 2 + 3, luego sumar 1 más el resultado. Su ejemplo de paréntesis produce el mismo orden que se habría producido de todos modos. Ahora, en este ejemplo, el orden de las operaciones es ligeramente diferente, pero la forma en que funciona la suma de enteros, el resultado es el mismo. En

int a = 10 - (5 - 4);

los paréntesis son críticos; dejarlos afuera cambiaría el resultado de 9 a 1.

Después de que el compilador ha determinado qué operaciones se realizan en qué orden, los paréntesis se olvidan por completo. Todo lo que el compilador recuerda en este momento es qué operaciones realizar en qué orden. Entonces, en realidad no hay nada que el compilador pueda optimizar aquí, los paréntesis se han ido .

gnasher729
fuente
practically every language in existence; excepto APL: Pruebe (aquí) [tryapl.org] ingresando (1-2)+3(2), 1-(2+3)(-4) y 1-2+3(también -4).
tomsmeding
0

Sin embargo, estoy de acuerdo con gran parte de lo que se ha dicho ... lo general aquí es que los paréntesis están allí para obligar a ordenar la operación ... lo que el compilador está haciendo absolutamente. Sí, produce código de máquina ... pero, ese no es el punto y no es lo que se pregunta.

Los paréntesis han desaparecido: como se ha dicho, no forman parte del código de máquina, que son números y nada más. El código de ensamblaje no es código de máquina, es legible para humanos y contiene las instrucciones por su nombre, no el código de operación. La máquina ejecuta lo que se denomina códigos de operación: representaciones numéricas del lenguaje ensamblador.

Los lenguajes como Java caen en un área intermedia ya que se compilan solo parcialmente en la máquina que los produce. Se compilan en un código específico de máquina en la máquina que los ejecuta, pero eso no hace ninguna diferencia a esta pregunta: los paréntesis aún desaparecen después de la primera compilación.

jinzai
fuente
1
No estoy seguro de que esto responda la pregunta. Los párrafos de apoyo son más confusos que útiles. ¿Cómo es relevante el compilador de Java para el compilador de C ++?
Adam Zuckerman
OP preguntó si los paréntesis habían desaparecido ... Le dije que sí y le expliqué que el código ejecutable son solo números que representan códigos de operación. Java apareció en otra respuesta. Creo que responde bien a la pregunta ... pero esa es solo mi opinión. Gracias por responder.
jinzai
3
Los paréntesis no fuerzan el "orden de operación". Cambian la precedencia. Entonces, en a = f() + (g() + h());, el compilador es libre de llamar f, gy hen ese orden (o en cualquier orden que desee).
Alok
Estoy en desacuerdo con esa afirmación ... absolutamente puede forzar el orden de operación con paréntesis.
Jinzai
0

Los compiladores, independientemente del idioma, traducen todas las matemáticas infijadas a postfix. En otras palabras, cuando el compilador ve algo como:

((a+b)+c)

lo traduce a esto:

 a b + c +

Esto se hace porque si bien la notación infija es más fácil de leer para las personas, la notación postfix está mucho más cerca de los pasos reales que la computadora tiene que tomar para hacer el trabajo (y porque ya hay un algoritmo bien desarrollado para ello). definición, postfix ha eliminado todos los problemas con el orden de las operaciones o los paréntesis, lo que naturalmente hace las cosas mucho más fáciles al escribir el código de la máquina.

Recomiendo el artículo de Wikipedia sobre la notación polaca inversa para obtener más información sobre el tema.

Brian Drozd
fuente
55
Esa es una suposición incorrecta acerca de cómo los compiladores traducen las operaciones. Por ejemplo, está asumiendo una máquina de pila aquí. ¿Y si tuvieras un procesador vectorial? ¿Qué pasaría si tuviera una máquina con una gran cantidad de registros?
Ahmed Masud