Share via


Procedimientos recomendados para la optimización

En este documento se describen algunos procedimientos recomendados para optimizar programas de C++ en Visual Studio.

Opciones del compilador y del vinculador

Optimización guiada por perfiles

Visual Studio admite la Optimización guiada por perfiles (PGO). Esta optimización usa datos de perfil de ejecuciones de entrenamiento de una versión instrumentada de una aplicación para controlar la optimización posterior de la aplicación. La utilización de PGO puede ocupar mucho tiempo; por tanto, no lo deberían utilizar todos los desarrolladores, sino que se recomienda utilizar PGO para la versión de lanzamiento final de un producto. Para más información, vea Optimizaciones guiadas por perfiles.

Además, se ha mejorado la optimización de todo el programa (que también se conoce como Generación de código en tiempo de vínculo) y las optimizaciones /O1 y /O2. En general, una aplicación compilada con una de estas opciones es más rápida que la misma aplicación compilada con un compilador anterior.

Para obtener más información, vea /GL (Optimización de todo el programa) y /O1, /O2 (Minimizar tamaño, maximizar velocidad).

¿Qué nivel de optimización debo usar?

Si es posible, las últimas versiones de lanzamiento se deberían compilar con las optimizaciones guiadas por perfiles. Si no es posible generar con PGO, ya sea debido a una infraestructura insuficiente para ejecutar las compilaciones instrumentadas, o bien por no tener acceso a los escenarios, se recomienda realizar la generación con la optimización de todo el programa.

El modificador /Gy también resulta muy útil. Genera un COMDAT independiente para cada función, dando más flexibilidad al vinculador cuando quita COMDAT sin referencia y plegamiento de COMDAT. El único inconveniente de usar /Gy es que puede producir problemas durante la depuración. Por consiguiente, por lo general se recomienda utilizarlo. Para obtener más información, vea /Gy(Habilitar vinculación en el nivel de función).

Para vinculaciones en entornos de 64 bits, se recomienda utilizar la opción del enlazador /OPT:REF,ICF y, en entornos de 32 bits, se recomienda /OPT:REF. Para obtener más información, consulte el artículo /OPT (Optimizaciones).

También se recomienda encarecidamente generar símbolos de depuración, incluso con versiones de lanzamiento optimizadas. Esto no afecta al código generado y facilita la depuración de la aplicación, si fuera necesario.

Modificadores de punto flotante

La opción del compilador /Op se ha quitado y se han agregado las cuatro opciones del compilador siguientes que se encargan de las optimizaciones de punto flotante:

Opción Descripción
/fp:precise Ésta es la recomendación predeterminada y se debería utilizar en la mayoría de los casos.
/fp:fast Se recomienda si el rendimiento es de la máxima importancia; por ejemplo, en juegos. Dará como resultado un rendimiento mucho más rápido.
/fp:strict Se recomienda si necesitan excepciones en punto flotante y si se desea el comportamiento IEEE. Dará como resultado el rendimiento más lento.
/fp:except[-] Se puede utilizar junto con /fp:strict o /fp:precise, pero no con /fp:fast.

Para obtener más información, vea /fp (Especificar comportamiento de punto flotante).

Modificadores declspec de optimización

En esta sección, se examinarán dos modificadores declspec que se pueden utilizar en programas para mejorar el rendimiento: __declspec(restrict) y __declspec(noalias).

El modificador declspec restrict sólo se puede aplicar a declaraciones de función que devuelven un puntero, como __declspec(restrict) void *malloc(size_t size);

El modificador declspec restrict se utiliza en funciones que devuelven punteros sin alias. Esta palabra clave se utiliza para la implementación de la biblioteca en tiempo de ejecución de C de malloc ya que nunca devolverá un valor de puntero que se esté utilizando en el programa actual (salvo que se esté haciendo algo no válido, como utilizar la memoria después de liberarla).

El modificador declspec restrict da más información al compilador para realizar las optimizaciones del compilador. Una de las tareas que más le cuesta al compilador es determinar qué punteros son alias de otros punteros y esta información ayuda considerablemente al compilador.

Merece la pena indicar que es una sugerencia para el compilador, no algo que el compilador vaya a comprobar. Si el programa utiliza el modificador declspec restrict de manera inapropiada, el programa podrá tener un comportamiento incorrecto.

Para obtener más información, vea restrict.

El modificador declspec noalias también se aplica sólo a funciones, e indica que la función es una función semipura. Una función semipura es aquella que modifica o hace referencia únicamente a variables locales, argumentos y direccionamientos indirectos de primer nivel de argumentos. Este modificador declspec es una sugerencia para el compilador, y si la función hace referencia a variables globales o direccionamientos indirectos de segundo nivel de argumentos a punteros, el compilador podrá generar código que interrumpa la aplicación.

Para obtener más información, vea noalias.

Pragmas de optimización

Hay también varias directivas pragma útiles para ayudar a optimizar el código. La primera que se describirá es #pragma optimize:

#pragma optimize("{opt-list}", on | off)

Esta directiva pragma le permite establecer un nivel de optimización función a función. Este sistema es perfecto para esas raras ocasiones en las que la aplicación se bloquea cuando una función determinada se compila con optimización. Puede utilizarlo para desactivar las optimizaciones para una función única:

#pragma optimize("", off)
int myFunc() {...}
#pragma optimize("", on)

Para obtener más información, vea optimize.

Los procesos en línea son una de las optimizaciones más importantes que ejecuta el compilador y aquí se van a describir un par de directivas pragma que ayudan a modificar este comportamiento.

#pragma inline_recursion es útil para especificar si se desea o no que la aplicación procese en línea una llamada recursiva. De forma predeterminada está desactivada. En el caso de recursividad superficial de funciones pequeñas, puede activarla. Para obtener más información, vea inline_recursion.

Otra directiva pragma útil para limitar la profundidad del procesamiento en línea es #pragma inline_depth. Sobre todo resulta útil en aquellas situaciones en las que se intenta limitar el tamaño de un programa o función. Para obtener más información, vea inline_depth.

__restrict y __assume

Visual Studio incluye un par de palabras clave que pueden ayudar a mejorar el rendimiento: __restrict y __assume.

En primer lugar, se debería tener en cuenta que __restrict y __declspec(restrict) son dos cosas diferentes. Aunque están relacionadas de algún modo, su semántica es diferente. __restrict es un calificador de tipo, como const o volatile, pero exclusivamente para los tipos de puntero.

Un puntero modificado con __restrict se denomina puntero __restrict. Un puntero __restrict es un puntero al que solo se puede acceder a través del puntero __restrict. Es decir, no se puede usar otro puntero para acceder a los datos a los que apunta el puntero __restrict.

__restrict puede ser una herramienta eficaz para el optimizador de Microsoft C++, pero hay que usarla con cuidado. Si se utiliza incorrectamente, el optimizador podría realizar una optimización que interrumpiría su aplicación.

Con __assume, el desarrollador puede indicar al compilador que haga suposiciones sobre el valor de alguna variable.

Por ejemplo, __assume(a < 5); indica al optimizador que en esa línea de código la variable a es menor que 5. De nuevo, esto es una sugerencia para el compilador. Si a es realmente 6 es en este punto del programa, el comportamiento de este después de que el compilador haya realizado la optimización puede ser distinto de lo que se esperaba. __assume es más útil antes de cambiar instrucciones o expresiones condicionales.

Hay algunas limitaciones en __assume. En primer lugar, al igual que __restrict, es solo una sugerencia, por lo que el compilador puede omitirlo sin mayor problema. Además, __assume funciona actualmente solo con desigualdades de variables frente a constantes. No difunde desigualdades simbólicas, por ejemplo, assume(a < b).

Compatibilidad con funciones intrínsecas

Las funciones intrínsecas son llamadas a función en las que el compilador tiene un conocimiento intrínseco sobre la llamada, y en lugar de llamar a una función de una biblioteca, emite código para esa función. El archivo de encabezado <intrin.h> contiene todas las funciones intrínsecas disponibles para cada una de las plataformas de hardware admitidas.

Las funciones intrínsecas dan al programador la capacidad para profundizar en el código sin tener que utilizar el ensamblado. Las funciones intrínsecas presentan varias ventajas:

  • El código es más portátil. Algunas de las funciones intrínsecas están disponibles en varias arquitecturas de la CPU.

  • El código es más fácil de leer, ya que se sigue escribiendo en C/C++.

  • El código presenta las ventajas de las optimizaciones del compilador. A medida que el compilador mejora, también lo hace la generación de código para las funciones intrínsecas.

Para obtener más información, vea Funciones intrínsecas del compilador.

Excepciones

Hay una merma en el rendimiento asociada al uso de excepciones. Se introducen algunas restricciones al utilizar bloques Try que impiden que el compilador realice ciertas optimizaciones. En las plataformas x86 hay una degradación del rendimiento adicional de los bloques Try debido a la información de estado adicional que se debe generar durante la ejecución del código. En las plataformas de 64 bits, los bloques Try no degradan tanto el rendimiento, pero cuando se produce la excepción, el proceso de encontrar el controlador y desenredar la pila puede ser costoso.

Por consiguiente, se recomienda evitar la introducción de bloques Try/Catch en código que no lo necesite realmente. Si tiene que usar excepciones, utilice si es posible excepciones sincrónicas. Para obtener más información, vea Structured Exception Handling (C/C++).

Por último, sólo produzca excepciones para casos excepcionales. La utilización de excepciones para el flujo de control general probablemente hagan que el rendimiento se resienta.

Consulte también