flag c++ optimization gcc compilation

c++ - flag - Compilar y optimizar para diferentes arquitecturas de destino.



gcc flags (8)

Resumen: quiero aprovechar las optimizaciones del compilador y los conjuntos de instrucciones del procesador, pero aún tengo una aplicación portátil (que se ejecuta en diferentes procesadores). Normalmente, de hecho, podría compilar 5 veces y dejar que el usuario elija la correcta para ejecutar.

Mi pregunta es: ¿cómo puedo automatizar esto, para que el procesador se detecte en tiempo de ejecución y se ejecute el ejecutable correcto sin que el usuario tenga que elegirlo?

Tengo una aplicación con muchos cálculos matemáticos de bajo nivel. Estos cálculos normalmente se ejecutarán durante mucho tiempo.

Me gustaría aprovechar la mayor optimización posible, preferiblemente también de conjuntos de instrucciones (no siempre compatibles). Por otro lado, me gustaría que mi aplicación fuera portátil y fácil de usar (por lo que no me gustaría compilar 5 versiones diferentes y dejar que el usuario elija).

¿Existe la posibilidad de compilar 5 versiones diferentes de mi código y ejecutar dinámicamente la versión más optimizada posible en el momento de la ejecución? Con 5 versiones diferentes, me refiero a diferentes conjuntos de instrucciones y diferentes optimizaciones para procesadores.

No me importa el tamaño de la aplicación.

En este momento estoy usando gcc en Linux (mi código está en C ++), pero también estoy interesado en esto para el compilador de Intel y para el compilador de MinGW para compilar en Windows.

El ejecutable no tiene que ser capaz de ejecutarse en diferentes sistemas operativos, pero lo ideal sería que hubiera algo posible con la selección automática de 32 bits y 64 bits también.

Edición: indique claramente cómo hacerlo, preferiblemente con pequeños ejemplos de código o enlaces a explicaciones. Desde mi punto de vista, necesito una solución súper genérica, que sea aplicable en cualquier proyecto aleatorio de C ++ que tenga más adelante.

Editar. Le asigné la recompensa a ShuggyCoUk, tenía una gran cantidad de consejos a los que prestar atención. Me hubiera gustado dividirlo entre múltiples respuestas pero eso no es posible. Todavía no lo he implementado, por lo que la pregunta sigue siendo "abierta". Por favor, aún agregue y / o mejore las respuestas, aunque ya no haya más recompensa.

¡Gracias a todos!


¿Puedes usar el script?

Podría detectar la CPU utilizando un script y cargar dinámicamente el ejecutable que está más optimizado para la arquitectura. Puede elegir versiones de 32/64 bits también.

Si está utilizando un Linux, puede consultar la CPU con

cat /proc/cpuinfo

Probablemente podría hacer esto con un script de bash / perl / python o un host de scripts de windows en windows. Probablemente no quiera forzar al usuario a instalar un motor de script. Uno que funciona en el sistema operativo fuera de la caja IMHO sería el mejor.

De hecho, en Windows probablemente querrá escribir una pequeña aplicación de C # para poder consultar la arquitectura más fácilmente. La aplicación C # podría generar cualquier ejecutable que sea más rápido.

Alternativamente, puede colocar sus diferentes versiones de código en un dll o en un objeto compartido, y luego cargarlas dinámicamente según la arquitectura detectada. Mientras tengan la misma firma de llamada, debería funcionar.


Como menciona que está utilizando GCC, asumiré que su código está en C (o C ++).

Neil Butterworth ya sugirió hacer bibliotecas dinámicas separadas, pero eso requiere algunas consideraciones multiplataforma no triviales (la carga manual de bibliotecas dinámicas es diferente en Linux, Windows, OSX, etc., y hacerlo bien probablemente llevará algo de tiempo).

Una solución económica es simplemente escribir todas sus variantes con nombres únicos y usar un puntero de función para seleccionar el correcto en tiempo de ejecución.

Sospecho que la falta de referencia adicional causada por el puntero a la función se amortizará por el trabajo real que está realizando (pero querrá confirmarlo).

Además, obtener diferentes optimizaciones del compilador probablemente requerirá diferentes archivos .c / .cpp, así como algunos cambios de tu herramienta de compilación. Pero es probable que sea menos trabajo en general que las bibliotecas separadas (que ya necesitaban esto de una forma u otra).


Como no especificó si tiene límites en la cantidad de archivos, le propongo otra solución: compile 5 ejecutables y luego cree un sexto ejecutable que inicie el binario apropiado. Aquí hay algunos pseudocódigo, para Linux

int main(int argc, char* argv[]) { char* target_path[MAXPATH]; char* new_argv[]; char* specific_version = determine_name_of_specific_version(); strcpy(target_path, "/usr/lib/myapp/versions"); strcat(target_path, specific_version); /* append NULL to argv */ new_argv = malloc(sizeof(char*)*(argc+1)); memcpy(new_argv, argv, argc*sizeof(char*)); new_argv[argc] = 0; /* optionally set new_argv[0] to target_path */ execv(target_path, new_argv); }

En el lado positivo, este enfoque permite proporcionar al usuario de forma transparente tanto los binarios de 32 bits como los de 64 bits, a diferencia de cualquier método de biblioteca que se haya propuesto. En el lado negativo, no hay execv en Win32 (pero una buena emulación en cygwin); en Windows, debe crear un nuevo proceso, en lugar de volver a ejecutar el actual.


Eche un vistazo a liboil: http://liboil.freedesktop.org/wiki/ . Puede seleccionar dinámicamente implementaciones de cálculos relacionados con multimedia en tiempo de ejecución. Puedes encontrar que puedes librarte a ti mismo y no solo a sus técnicas.


Si desea que esto funcione de manera limpia en Windows y aproveche al máximo las plataformas adicionales compatibles con 64 bits del espacio de direccionamiento y los 2. registros adicionales (que probablemente le sean más útiles) debe tener como mínimo un proceso separado para los de 64 bits.

Puede lograr esto teniendo un ejecutable separado con el encabezado PE64 relevante. Simplemente utilizando CreateProcess se iniciará esto como bitness relevante (a menos que el ejecutable iniciado se encuentre en alguna ubicación redirigida, no hay necesidad de preocuparse por la redirección de carpetas WoW64)

Dada esta limitación en Windows, es probable que el simple hecho de ''encadenar'' al ejecutable relevante sea la opción más simple para todas las diferentes opciones, además de simplificar las pruebas individuales.

También significa que el ejecutable ''principal'' es libre de estar totalmente separado dependiendo del sistema operativo de destino (ya que la detección de las capacidades de cpu / OS es, por su naturaleza, muy específica del sistema operativo) y luego hace la mayor parte del resto de su código como compartido objetos / dlls. También puede "compartir" los mismos archivos para dos arquitecturas diferentes si actualmente no cree que haya algún punto en el uso de las diferentes capacidades.

Yo sugeriría que el ejecutable principal sea capaz de ser forzado a hacer una elección específica para que pueda ver qué sucede con las versiones "menores" en una máquina más capaz (o qué errores aparecen si intenta algo diferente).

Otras posibilidades dadas este modelo son:

  • Vincule estáticamente a diferentes versiones de los tiempos de ejecución estándar (para aquellos con / sin seguridad de subprocesos) y utilícelos adecuadamente si está ejecutando sin ninguna capacidad de SMP / SMT.
  • Detectar si hay varios núcleos presentes y si son reales o de hipervínculos (también si el sistema operativo sabe cómo funciona la planificación en esos casos)
  • verifique el rendimiento de cosas como el temporizador del sistema / los temporizadores de alto rendimiento y utilice el código optimizado para este comportamiento, por ejemplo, si hace algo en el que se espera que expire cierto tiempo y, por lo tanto, pueda conocer su mejor granularidad posible.
  • Si desea optimizar su elección de código en función del tamaño de caché / otra carga en la caja. Si está utilizando bucles sin enrollar, las opciones de desenrollamiento más agresivas pueden depender de tener una cierta cantidad de caché de nivel 1/2.
  • Compilación condicional para usar dobles / flotantes dependiendo de la arquitectura. Menos importante en el hardware de Intel, pero si está apuntando a ciertos cpu de ARM, algunos tienen soporte real de hardware de punto flotante y otros requieren emulación. El código óptimo cambiaría mucho, incluso en la medida en que solo use la compilación condicional en lugar de usar el compilador de optimización (1).
  • Haciendo uso de hardware de coprocesador como tarjetas gráficas con capacidad CUDA.
  • detectar la virtualización y alterar el comportamiento (tal vez tratando de evitar escrituras del sistema de archivos)

Para realizar esta comprobación, tiene algunas opciones, la más útil para Intel es la instrucción cpuid .

Alternativamente, vuelva a implementar / actualizar uno existente utilizando la documentación disponible sobre las funciones que necesita.

Un montón de documentos separados para averiguar cómo detectar cosas:

Una gran parte de lo que pagarías en la biblioteca de CPU-Z es alguien que hace todo esto (y los pequeños problemas que involucra).

  1. Tenga cuidado con esto: es difícil superar a los compiladores de optimización decentes en este

Si es posible. Compile todas sus versiones optimizadas de manera diferente como bibliotecas dinámicas diferentes con un punto de entrada común, y proporcione un apéndice ejecutable que cargue y ejecute la biblioteca correcta en tiempo de ejecución, a través del punto de entrada, según el archivo de configuración u otra información.


Usted mencionó el compilador de Intel. Eso es gracioso, porque puede hacer algo como esto por defecto. Sin embargo, hay una trampa. El compilador de Intel no insertó controles para la funcionalidad SSE apropiada. En su lugar, verificaron si tenías un chip Intel en particular. Todavía habría un caso predeterminado lento. Como resultado, las CPU de AMD no obtendrían versiones optimizadas para SSE adecuadas. Hay hacks flotando alrededor que reemplazarán el cheque de Intel con un cheque SSE adecuado.

La diferencia de 32/64 bits requerirá dos ejecutables. Tanto el formato ELF como el formato PE almacenan esta información en el encabezado de los exectuables. No es demasiado difícil iniciar la versión de 32 bits de forma predeterminada, verifique si está en un sistema de 64 bits y luego reinicie la versión de 64 bits. Pero puede ser más fácil crear un enlace simbólico apropiado en el momento de la instalación.


Vamos a dividir el problema en sus dos partes constituyentes. 1) Creación de código optimizado dependiente de la plataforma y 2) compilación en múltiples plataformas.

El primer problema es bastante sencillo. Encapsule el código dependiente de la plataforma en un conjunto de funciones. Crea una implementación diferente de cada función para cada plataforma. Ponga cada implementación en su propio archivo o conjunto de archivos. Es más fácil para el sistema de compilación si coloca el código de cada plataforma en un directorio separado.

Para la segunda parte, te sugiero que mires Gnu Atuotools (Automake, AutoConf y Libtool). Si alguna vez ha descargado y creado un programa GNU desde el código fuente, sabe que debe ejecutar ./configure antes de ejecutar make. El propósito del script de configuración es 1) verificar que su sistema tenga todas las bibliotecas necesarias y las utilidades necesarias para compilar y ejecutar el programa y 2) personalizar los Makefiles para la plataforma de destino. Autotools es el conjunto de utilidades para generar el script de configuración.

Con autoconf, puede crear pequeñas macros para verificar que la máquina admita todas las instrucciones de la CPU que necesita su código dependiente de la plataforma. En la mayoría de los casos, las macros ya existen, solo tiene que copiarlas en su script autoconf. Luego, automake y autoconf pueden configurar los Makefiles para obtener la implementación apropiada.

Todo esto es demasiado para crear un ejemplo aquí. Se necesita un poco de tiempo para aprender. Pero la documentación es todo lo que hay. Incluso hay un libro gratis disponible en línea. Y el proceso es aplicable a tus futuros proyectos. Para el soporte multiplataforma, creo que esta es realmente la forma más robusta y sencilla de hacerlo. Muchas de las sugerencias publicadas en otras respuestas son cosas con las que se ocupa Autotools (detección de CPU, soporte de biblioteca estática y compartida) sin que tenga que pensar demasiado en ello. El único problema con el que podría tener que lidiar es averiguar si Autotools está disponible para MinGW. Sé que son parte de Cygwin si puedes ir por esa ruta.