2010-05-30 13 views
27

Estoy involucrado en uno de esos desafíos donde intenta producir el binario más pequeño posible, entonces estoy construyendo mi programa sin la ejecución de C o C++ a tiempo real (RTL). No enlace a la versión DLL o la versión estática. Ni siquiera #include los archivos de encabezado. Tengo esto funcionando bien.Cómo usar las funciones intrínsecas de VC++ sin biblioteca de tiempo de ejecución

Algunas funciones de RTL, como memset(), pueden ser útiles, así que intenté agregar mi propia implementación. Funciona bien en las versiones de depuración (incluso para aquellos lugares donde el compilador genera una llamada implícita al memset()). Pero en las compilaciones de Release, aparece un error que dice que no puedo definir una función intrínseca. Verá, en las compilaciones de Release, las funciones intrínsecas están habilitadas, y memset() es intrínseco.

Me encantaría utilizar el intrínseco para memset() en mis compilaciones de lanzamiento, ya que es probablemente en línea y más pequeño y más rápido que mi implementación. Pero parece que estoy en catch-22. Si no defino memset(), el enlazador se queja de que no está definido. Si lo defino, el compilador se queja de que no puedo definir una función intrínseca.

¿Alguien sabe la combinación correcta de definición, declaración, #pragma, y banderas del compilador y enlazador para obtener una función intrínseca sin tener que recurrir a la sobrecarga de RTL?

Visual Studio 2008, x86, Windows XP +.

Para hacer que el problema un poco más concreto:

extern "C" void * __cdecl memset(void *, int, size_t); 

#ifdef IMPLEMENT_MEMSET 
void * __cdecl memset(void *pTarget, int value, size_t cbTarget) { 
    char *p = reinterpret_cast<char *>(pTarget); 
    while (cbTarget > 0) { 
     *p++ = static_cast<char>(value); 
     --cbTarget; 
    } 
    return pTarget; 
} 
#endif 

struct MyStruct { 
    int foo[10]; 
    int bar; 
}; 

int main() { 
    MyStruct blah; 
    memset(&blah, 0, sizeof(blah)); 
    return blah.bar; 
} 

Y construyo como esto:

cl /c /W4 /WX /GL /Ob2 /Oi /Oy /Gs- /GF /Gy intrinsic.cpp 
link /SUBSYSTEM:CONSOLE /LTCG /DEBUG /NODEFAULTLIB /ENTRY:main intrinsic.obj 

Si compilo con mi aplicación de memset(), me sale un error de compilación:

error C2169: 'memset' : intrinsic function, cannot be defined 

Si compilo esto sin mi implementación de memset(), me sale un error de vinculador:

error LNK2001: unresolved external symbol _memset 
+1

Es '/ GL' ese es el problema, mira mi respuesta a continuación. – egrunin

Respuesta

16

creo que por fin he encontrado una solución:

En primer lugar, en un archivo de cabecera, declarar memset() con un pragma, así:

extern "C" void * __cdecl memset(void *, int, size_t); 
#pragma intrinsic(memset) 

que permite a su código para llamar memset(). En la mayoría de los casos, el compilador alineará la versión intrínseca.

En segundo lugar, en un archivo de implementación separado, proporcione una implementación. El truco para evitar que el compilador se queje de la redefinición de una función intrínseca es usar otro pragma primero. De esta manera:

#pragma function(memset) 
void * __cdecl memset(void *pTarget, int value, size_t cbTarget) { 
    unsigned char *p = static_cast<unsigned char *>(pTarget); 
    while (cbTarget-- > 0) { 
     *p++ = static_cast<unsigned char>(value); 
    } 
    return pTarget; 
} 

Esto proporciona una implementación para aquellos casos en que el optimizador decida no utilizar la versión intrínseca.

El inconveniente principal es que tiene que deshabilitar la optimización de todo el programa (/ GL y/LTCG). No estoy seguro por qué. Si alguien encuentra una forma de hacerlo sin desactivar la optimización global, repítalo.

+0

¿Qué están haciendo todos esos moldes allí? Además, las conversiones de punteros hacia y desde 'void *' son normalmente 'static_cast'-s, no' reinterpret_cast'-s. – AnT

+0

@AndreyT: He cambiado el elenco de 'void *' para usar 'static_cast'. En el momento en que originalmente escribí esto, lo que se usó en esa situación no estaba claro y se debatió acaloradamente. (http://stackoverflow.com/questions/310451/should-i-use-static-cast-or-reinterpret-cast-when-casting-a-void-to-whatever) No estoy seguro de a qué se refiere "todos" esos casos. Hay dos. La primera es necesaria porque no se puede escribir mediante un puntero a void (que es lo que 'memset' toma). El segundo es para que el compilador no advierta sobre la asignación de un int a un char sin signo. –

+1

Puede limitar la desactivación de la optimización de todo el programa únicamente a los intrínsecos, compilando estos intrínsecos en una biblioteca estática separada. –

5
  1. estoy bastante seguro de que hay una bandera compilador que dice VC no ++ para utilizar los intrínsecos

  2. La fuente de la biblioteca de tiempo de ejecución se instala con el compilador . Usted tiene la opción de seleccionar las funciones que desea/necesita, aunque a menudo tendrá que modificarlas extensamente (porque incluyen características y/o dependencias que no quiere/necesita).

  3. También hay otras bibliotecas de tiempo de ejecución de código abierto disponibles, que pueden necesitar menos personalización.

  4. Si realmente habla en serio sobre esto, necesitará saber (y tal vez usar) lenguaje ensamblador.

Editado para añadir:

que consiguiera su nuevo código de prueba para compilar y enlazar.Estos son los ajustes pertinentes:

Enable Intrinsic Functions: No 
Whole Program Optimization: No 

Es esto último que suprime "ayudantes" del compilador como el memset incorporado.

Editado para agregar:

Ahora que se ha desacoplado, puede copiar el código ensamblador de memset.asm en su programa - que tiene una referencia global, pero se puede quitar eso. Es lo suficientemente grande como para que sea no en línea, aunque si elimina todos los trucos que utiliza para ganar velocidad, es posible que sea lo suficientemente pequeño para eso.

que tomaron su ejemplo anterior y se sustituye el memset() con esto:

void * __cdecl memset(void *pTarget, char value, size_t cbTarget) { 
    _asm { 
    push ecx 
    push edi 

    mov al, value 
    mov ecx, cbTarget 
    mov edi, pTarget 
    rep stosb 

    pop edi 
    pop ecx 
    } 
    return pTarget; 
} 

Funciona, pero la versión de la biblioteca es mucho más rápido.

+0

Pero eso va en contra del objetivo final de tratar de hacer el binario más pequeño posible. En muchos casos, incluido 'memset', la función intrínseca en línea es más pequeña que la llamada a la función. –

+0

La versión de lib es más rápida solo porque alinea el puntero de destino a 4 bytes (en máquinas de 32 bits, 8 bytes en 64 bits) y usa rep stosd en lugar de rep stosb, escribiendo separadamente los bytes desalineados al inicio y al final. Hacer eso me haría aún más grande. De nuevo (como dije en los comentarios a mi respuesta), no creo que tu compilador genere realmente lo intrínseco. La implementación de Egrunin es tan pequeña como se puede obtener. En casos muy específicos, tal vez el intrínseco podría ahorrar los push/pops, si ecx y edi están disponibles. ¿Tendría una ganancia neta? Rara vez, supongo. –

+0

El código en la segunda edición de egrunin es esencialmente el mismo que el código generado por el compilador cuando usa el intrínseco. El compilador a menudo puede guardar algunos bytes cuando sabe que no necesita conservar ecx y edi. La versión de biblioteca vale la pena cuando la cantidad de bytes a borrar aumenta. Hay una sobrecarga al tratar con el principio y el final posiblemente desalineados. –

1

Creo que tiene que establecer la optimización para "Minimizar tamaño (/ O1)" o "Desactivado (/ Od)" para obtener la configuración de lanzamiento para compilar; al menos esto es lo que hizo el truco para mí con VS 2005. Los intrínsecos están diseñados para la velocidad, por lo que tiene sentido que estén habilitados para los otros niveles de Optimización (Velocidad y Completa).

+0

Ya tengo/O1, y/Od algo contradice el objetivo de hacer el binario más pequeño posible. La velocidad también es un problema. –

+0

Bueno, no tengo VS2008 delante de mí, así que tal vez cambiaron algo. En VS2005 este fue el único cambio que tuve que hacer para que se desarrollara correctamente. – Luke

0

Simplemente nombre la función algo ligeramente diferente.

+0

Buena idea, pero no funciona. Escribí mi propia versión, llamada 'ClearMemory()' utilizando un espacio de nombres para asegurarme de que no entre en conflicto con ninguna otra cosa. El optimizador reemplazó mi implementación de 'ClearMemory()' con una llamada a 'memset()' (¡con un valor de byte de 0)! Demasiado inteligente por su propio bien. :-) –

+1

Esto tampoco funciona si es el compilador que usa 'memset' en primer lugar (como en un inicializador de clase). –

+0

En el caso específico en el que desea escribir ceros, la función SecureZeroMemory parece funcionar. (Está implementado como una función en línea forzada incrustada en winnt.h.) –

-1

La manera en que la biblioteca de tiempo de ejecución "regular" hace esto es compilando un archivo de ensamblaje con una definición de memset y vinculándolo a la biblioteca de tiempo de ejecución (Puede encontrar el archivo de ensamblaje en C: \ Archivos de programa \ Microsoft Visual Studio 10.0 \ VC \ crt \ src \ intel \ memset.asm o en sus alrededores. Ese tipo de cosas funciona bien incluso con la optimización de todo el programa.

También tenga en cuenta que el compilador solo usará el memset intrínseco en algunos casos especiales (cuando el tamaño es constante y pequeño?). Por lo general, utilizará la función memset proporcionada por usted, por lo que probablemente debería utilizar la función optimizada en memset.asm, a menos que vaya a escribir algo igual de optimizado.

0

Esto definitivamente funciona con VS 2015: Agregue la opción de línea de comando/Oi-. Esto funciona porque "No" en las funciones intrínsecas no es un interruptor, no está especificado./ Oi- y todos tus problemas desaparecen (debería funcionar con la optimización del programa completo, pero no he probado esto correctamente).

+1

Desde MSDN: "/ Oi es solo una solicitud al compilador para que reemplace algunas llamadas de función por intrínsecas; el compilador puede llamar a la función (y no reemplazar la llamada de función por una intrínseca) si se obtiene un mejor rendimiento". Por lo tanto, podría funcionar o no en todos los casos. –

Cuestiones relacionadas