2009-08-18 13 views
17

Resumen: Quiero aprovechar las optimizaciones del compilador y los conjuntos de instrucciones del procesador, pero todavía tengo una aplicación portátil (que se ejecuta en diferentes procesadores). Normalmente podría compilar 5 veces y dejar que el usuario elija el correcto para ejecutar.Compilar y optimizar para diferentes arquitecturas de destino

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 generalmente se ejecutarán durante un tiempo prolongado.

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 sea 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 que sea posible en el momento de la ejecución? Me refiero a 5 versiones diferentes con 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 MinGW para la compilación a Windows.

El ejecutable no tiene que poder ejecutarse en diferentes OS'es, pero lo ideal sería que haya algo posible con la selección automática de 32 bits y 64 bits también.

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

Editar Asigné la recompensa a ShuggyCoUk, tenía una gran cantidad de consejos para tener en cuenta. Me hubiera gustado dividirlo entre varias respuestas, pero eso no es posible. No estoy implementando esto todavía, ¡entonces la pregunta todavía está 'abierta'! Por favor, aún agregue y/o mejore las respuestas, aunque ya no se otorgue ninguna recompensa.

¡Gracias a todos!

+0

¿No es esto lo que Apple hace con sus binarios "universales" (PPC - x86)? – Edmundo

+0

Me aseguré de haber hecho +1 en todas las respuestas que creía que eran buenas, y que todas obtuvieron un poco de mí :). Saludos por la aceptación. – ShuggyCoUk

+0

Ah, y si encuentra más información a medida que avanza, edite mi respuesta y conviértala en CW ... – ShuggyCoUk

Respuesta

5

Si desea que esto funcione limpiamente en Windows y aproveche al máximo las plataformas de 64 bits con capacidad adicional de 1. Espacio de direcciones y 2. registros (que probablemente le resulten 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 correspondiente. Simplemente usando CreateProcess se lanzará esto como el bitness relevante (a menos que el ejecutable lanzado esté en una ubicación redirigida no hay necesidad de preocuparse por WoW64 folder redirection

Dada esta limitación en Windows, es probable que simplemente 'encadenado' al ejecutable relevante será la opción más simple para todas las diferentes opciones, y también hará que las pruebas individuales sean más sencillas.

También significa que el ejecutable 'principal' puede ser totalmente independiente según el sistema operativo de destino (como detectar la CPU/La capacidad del sistema operativo es, por naturaleza, muy específica del sistema operativo) y luego hace la mayor parte del resto del código como objetos compartidos/dlls. También puede 'compartir' los mismos archivos para dos arquitecturas diferentes si Actualmente no cree que haya ningún punto que use las diferentes capacidades.

Sugeriría que el ejecutable principal puede forzarse a realizar una elección específica para que pueda ver lo que sucede con las versiones "menores" en una máquina más capaz (o qué errores aparecen si prueba algo diferente).

Otras posibilidades dadas este modelo son:

  • vincular estáticamente a diferentes versiones de los tiempos de ejecución estándar (para los con/sin hilo de seguridad) y usando de manera apropiada si está ejecutando sin ninguna capacidad de SMP/SMT.
  • Detectar si varios núcleos están presentes y si son de roscado real o hiper (también si el sistema operativo sabe el horario de manera efectiva en aquellos casos)
  • comprobar el rendimiento de cosas como los temporizadores temporizador del sistema/de alto rendimiento y el uso de código optimizado para este comportamiento, digamos si hace algo donde busca una determinada cantidad de tiempo para caducar y así puede conocer su mejor granularidad posible.
  • Si desea optimizar su elección de código según el tamaño de caché/otra carga en la caja. Si está utilizando bucles desenrollados, las opciones de desenrollado más agresivas pueden depender de tener un cierto nivel de cantidad 1/2 de caché.
  • Compilando condicionalmente para usar dobles/flotantes dependiendo de la arquitectura. Menos importante en el hardware de Intel, pero si está apuntando a ciertas CPU de ARM algunas tienen soporte de hardware de punto flotante real y otras requieren emulación. El código óptimo cambiaría mucho, incluso en la medida en que solo utilice la compilación condicional en lugar de usar el compilador de optimización (1).
  • Haciendo uso de hardware de coprocesador como tarjetas gráficas compatibles con CUDA.
  • detectar la virtualización y alterar el comportamiento (tal vez tratando de evitar el sistema de archivos escribe)

En cuanto a haciendo este comprobar que tiene un par de opciones, la más útil de Intel es el de la instrucción cpuid .

Alternativamente volver a implementar/actualizar una existente usando la documentación disponible sobre las características que necesita.

Todo un montón de documentos separados para trabajar la manera de detectar cosas:

Una gran parte de lo que pagaría en la biblioteca de la CPU-Z es que alguien haga todo esto (y los desagradables pequeños problemas involucrados) por usted.


  1. tener cuidado con esto - es difícil de superar los compiladores optimizadores decente en este
6

¿Se puede usar el script?

Puede detectar la CPU utilizando secuencias de comandos y cargar dinámicamente el ejecutable que está más optimizado para la arquitectura. También puede elegir versiones de 32/64 bits.

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

cat /proc/cpuinfo 

Probablemente se podría hacer esto con una gran fiesta/Perl/script en Python o ventanas de secuencias de comandos en las ventanas. Probablemente no desee forzar al usuario a instalar un motor de script. Uno que funciona en el sistema operativo fuera de la caja en mi humilde opinión sería lo mejor.

De hecho, en Windows es probable que desee escribir una pequeña aplicación C# para que pueda consultar más fácilmente la arquitectura. La aplicación C# podría generar el ejecutable más rápido.

Alternativamente, podría colocar sus diferentes versiones de código en un dll u objeto compartido, y luego cargarlas dinámicamente en función de la arquitectura detectada. Siempre que tengan la misma firma de llamada, debería funcionar.

+0

Realmente no necesita una secuencia de comandos para detectar la CPU, puede hacerlo con un sistema nativo dependiente del sistema operativo llamadas. –

+0

Pero si usa script, se vuelve portátil en todos los sistemas operativos y arquitecturas de 64/32 bits. –

+2

Teniendo en cuenta que ya está escribiendo (bastante deliberadamente) código dependiente del sistema operativo, no creo que sea necesario garantizar que la detección del sistema operativo sea portátil. Aunque tener esa parte de la aplicación sea portátil probablemente haría las cosas más fáciles. – Brian

16

Sí, es posible. Compile todas sus versiones optimizadas de forma diferente como diferentes bibliotecas dinámicas con un punto de entrada común, y proporcione un código ejecutable que cargue y ejecute la biblioteca correcta en tiempo de ejecución, a través del punto de entrada, dependiendo del archivo de configuración u otra información.

+0

¡Gracias! ¿Quizás tengas algunos consejos más específicos sobre cómo compilar de esa manera? ¿Y cómo debería ser el talón? –

+0

En Windows, ¿puede iniciar una DLL de 64 bits de un proceso de 32 bits? No pensé que pudieras ... pero me encantaría ver cómo podrías hacerlo :) – Goz

+0

Entonces uno podría proporcionar otra capa: un cargador de 32 bits que, habiéndose detectado ejecutando en un arco de 64 bits, ejecutó Corredor de 64 bits, que a su vez carga la biblioteca de 64 bits. –

3

Como mencionas que estás usando GCC, supongo que tu código está en C (o C++).

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

Una solución económica es simplemente escribir todas sus variantes usando nombres únicos, y usar un puntero de función para seleccionar la correcta en tiempo de ejecución.

Sospecho que la desreferencia adicional causada por el puntero a la función se amortizará con 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 giros de su herramienta de compilación. Pero probablemente sea menos trabajo general que las bibliotecas separadas (que necesitaban esto ya de una forma u otra).

+0

Esta es una sugerencia horrible y tendrías que estar loco para usarla. No suelo hacer tales declaraciones, pero en este caso creo que debo hacerlo. No hagas esto. –

+0

Absolutamente no quiero tener diferentes archivos .cpp. ¡Esa es una pesadilla para mantener! Si tengo alguna optimización para plataformas específicas en mi código, creo que ifdefs me servirá. –

+0

OK, necesito como me siento para defenderme un poco aquí, considerando la fuerza de esos comentarios. En primer lugar, entiendo que desea compilar varias versiones de una rutina intensiva en matemáticas para la misma arquitectura (por ejemplo, x86), pero con diferentes implementaciones/optimizaciones (SSE, -O1/O2/O3, etc.). Creo que "preajuste" y "-mfpmath" de GCC no pueden ser controlados por el preprocesador, por lo que podría tener que recompilar el mismo .cpp para generar diferentes archivos .o. La sugerencia de Neil es que esos terminen en diferentes bibliotecas dinámicas. El mío era tenerlos a todos en el mismo binario (cont.). – jhoule

5

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. Puede encontrar que puede liberarse y no solo sus técnicas.

3

Como no especificó si tiene límites en el número de archivos, propongo otra solución: compilar 5 ejecutables y luego crear un sexto ejecutable que inicie el binario apropiado. Aquí hay alguna 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 manera transparente con ambas binarios de 32 bits y de 64 bits, a diferencia de cualquiera de los métodos de la biblioteca que se han propuesto. En el lado negativo, no hay ejecución en Win32 (pero una buena emulación en cygwin); en Windows, debe crear un nuevo proceso, en lugar de volver a ejecutar el actual.

1

Mencionó el compilador de Intel. Eso es divertido, porque puede hacer algo como esto por defecto. Sin embargo, hay una trampa. El compilador de Intel no insertó comprobaciones para la funcionalidad SSE apropiada. En cambio, comprobaron si tenía un chip Intel en particular. Todavía habría un caso lento por defecto. Como resultado, las CPU AMD no obtendrían versiones adecuadas optimizadas para SSE. Hay hacks flotando que reemplazarán al cheque de Intel con una verificación de SSE adecuada.

La diferencia de 32/64 bits requerirá dos ejecutables. Tanto el formato ELF como PE almacenan esta información en el encabezado de los elementos ejecutables. No es demasiado difícil iniciar la versión de 32 bits de forma predeterminada, verificar si está en un sistema de 64 bits y luego reiniciar 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.

+0

¿Cómo se llama esta funcionalidad Intel? ¿O tiene enlaces a documentación y hacks mencionados? –

1

permite dividir el problema a sus dos partes constituyentes. 1) Crear código optimizado dependiente de la plataforma y 2) construir en múltiples plataformas.

El primer problema es bastante sencillo. Encapsule el código dependiente de la plataforma en un conjunto de funciones. Cree 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, le sugiero que consulte Gnu Atuotools (Automake, AutoConf y Libtool). Si alguna vez ha descargado y creado un programa GNU a partir del código fuente, sabrá 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 y utilidades requeridas para construir 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.

Usando autoconf, puede crear pequeñas macros para verificar que la máquina admite 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 de autoconf. Luego, automake y autoconf pueden configurar Makefiles para obtener la implementación adecuada.

Todo esto es demasiado para crear un ejemplo aquí. Toma un poco de tiempo para aprender. Pero la documentación está por todos lados. Incluso hay un free book disponible en línea. Y el proceso es aplicable a sus proyectos futuros. Para soporte multiplataforma, esta es realmente la manera más robusta y fácil de llevar, creo. Muchas de las sugerencias publicadas en otras respuestas son cosas que trata Autotools (detección de CPU, soporte de biblioteca compartida estática &) sin que tenga que pensar demasiado. La única dificultad con la que debe lidiar es descubrir si las Autotools están disponibles para MinGW. Sé que son parte de Cygwin si puedes ir por esa ruta.

Cuestiones relacionadas