2009-06-11 14 views
14

Digamos que tiene una función como esta:¿Funcionará correctamente el cambio de bit por cero?

inline int shift(int what, int bitCount) 
{ 
    return what >> bitCount; 
} 

Se va a llamar desde diferentes sitios cada vez bitCount será no negativo y en el número de bits en int. Estoy particularmente preocupado por la llamada con bitCount igual a cero - ¿Funcionará correctamente entonces?

También existe la posibilidad de que un compilador que vea el código completo de la función al compilar su sitio de llamadas reducirá las llamadas con bitCount igual a cero a una no operación?

Respuesta

17

Es cierta que al menos un compilador de C++ reconocerá la situación (cuando el 0 es conocido en tiempo de compilación) y convertirlo en un no-op:

Fuente

inline int shift(int what, int bitcount) 
{ 
    return what >> bitcount ; 
} 

int f() { 
    return shift(42,0); 
} 

interruptores compilador

icpc -S -O3 -mssse3 -fp-model fast=2 bitsh.C 

Intel C++ 11.0 ensamblaje

# -- Begin _Z1fv 
# mark_begin; 
     .align 16,0x90 
     .globl _Z1fv 
_Z1fv: 
..B1.1:       # Preds ..B1.0 
     movl  $42, %eax          #7.10 
     ret              #7.10 
     .align 16,0x90 
           # LOE 
# mark_end; 
     .type _Z1fv,@function 
     .size _Z1fv,.-_Z1fv 
     .data 
# -- End _Z1fv 
     .data 
     .section .note.GNU-stack, "" 
# End 

Como se puede ver en ..B1.1, Intel recopila "desplazamiento de retorno (42,0)" a "volver 42."

Intel 11 también entresaca el cambio de estas dos variaciones:

int g() { 
    int a = 5; 
    int b = 5; 
    return shift(42,a-b); 
} 

int h(int k) { 
    return shift(42,k*0); 
} 

Para el caso en que el valor de desplazamiento no se puede conocer en tiempo de compilación ...

int egad(int m, int n) { 
    return shift(42,m-n); 
} 

... el cambio no puede se debe evitar ...

# -- Begin _Z4egadii 
# mark_begin; 
     .align 16,0x90 
     .globl _Z4egadii 
_Z4egadii: 
# parameter 1: 4 + %esp 
# parameter 2: 8 + %esp 
..B1.1:       # Preds ..B1.0 
     movl  4(%esp), %ecx         #20.5 
     subl  8(%esp), %ecx         #21.21 
     movl  $42, %eax          #21.10 
     shrl  %cl, %eax          #21.10 
     ret              #21.10 
     .align 16,0x90 
           # LOE 
# mark_end; 

... pero al menos está en línea, por lo que no hay sobrecarga de llamadas.

Montaje extra: volátil es caro. La fuente ...

int g() { 
    int a = 5; 
    volatile int b = 5; 
    return shift(42,a-b); 
} 

... en lugar de un no-op, compila a ...

..B3.1:       # Preds ..B3.0 
     pushl  %esi           #10.9 
     movl  $5, (%esp)         #12.18 
     movl  (%esp), %ecx         #13.21 
     negl  %ecx           #13.21 
     addl  $5, %ecx          #13.21 
     movl  $42, %eax          #13.10 
     shrl  %cl, %eax          #13.10 
     popl  %ecx           #13.10 
     ret              #13.10 
     .align 16,0x90 
           # LOE 
# mark_end; 

... así que si usted está trabajando en una máquina donde los valores se presiona en la pila puede que no sea lo mismo cuando los muestres, bueno, esta optimización perdida es probablemente la menor de tus molestias.

4

Funcionará correctamente en cualquier arquitectura ampliamente utilizada (puedo responder por x86, PPC, ARM). El compilador no podrá reducirlo a un nodo a menos que la función esté en línea.

+0

Eso sería específico del compilador, no creo que el estándar * requiera * que se cree la llamada a la función. La optimización global podría fácilmente descartar la llamada a la función si se pasaba bitCount como una constante 0 y posiblemente incluso como una variable que contiene cero (aunque habría código para examinar la variable al menos, por lo que no sería un no-op) . Depende completamente del compilador. – paxdiablo

+0

Por supuesto, una vez dicho esto, nunca he visto un compilador en la naturaleza que tenga ese nivel de optimización. Sospecho que el retorno de la inversión no sería suficiente para garantizarlo. – paxdiablo

+0

Pax, VC9 hace "fuera de la caja" (configuración de versión predeterminada) para: ---- int Ajuste (int x, int shift) { if (shift> 0) {/ * bloque grande que impide la alineación * /} return x << shift; } ---- Incluso cuando se realiza una llamada desde una unidad de traducción diferente, al pasar shift = 0, la llamada y >> se eliminarán. ¡Muy impresionante! – peterchen

2

Sobre la exactitud de arg < < 0 o arg >> 0, no hay problema, absolutamente bien.

Sobre las optimizaciones posibles: Esto no se reducirá a una> nop < cuando se le llama con una constante de lo = 0 y/o bitcount = 0, a menos que se declara como en línea y elegir optimizaciones (y su compilador de elección entiende lo que es en línea).

Por lo tanto, línea inferior, optimice este código al llamar condicionalmente la función solo si el OR de argumentos no es cero (sobre la forma más rápida de calcular que ambos argumentos son distintos de cero).

+0

@ jpinto3912, creo que la pregunta es preguntar qué sucede cuando el cambio es cero, no el valor (como su respuesta parece suponer). – paxdiablo

+0

Él no está cambiando cero, está cambiando algo cero veces. –

+0

@Pax, @Neil: si cambias a cero, también obtendrás un no-operativo. La pregunta se refiere a un caso, la respuesta señala ambos casos. – Stobor

3

El compilador solo podría realizar esta optimización, hacer eso si supiera en tiempo de compilación que el valor de bitCount era cero. Eso significaría que el parámetro pasado tendría que ser una constante:

const int N = 0; 
int x = shift(123, N); 

C++ sin duda permite una optimización tales a realizar, pero no estoy al tanto de cualquier compiladores que lo hacen. El enfoque alternativo el compilador podría tomar:

int x = n == 0 ? 123 : shift(123, n); 

habría una pessimisation en la mayoría de los casos y no puedo imaginar escritor compilador de implementar tal cosa.

Editar: Se garantiza que un cambio de cero bits no tendrá ningún efecto sobre la cosa que se está desplazando.

+0

Eso está bien, pero ¿es cero el cambio por cero para tener el mismo efecto que no-op (no necesariamente no se emite ningún código)? – sharptooth

+0

C++ no ofrece garantías sobre el código de objeto emitido, y no tiene el concepto de no-op. Sin embargo, un cambio de cero bits no puede tener ningún efecto en ningún objeto de C++. Tal vez podría ampliar su pregunta por qué está preguntando sobre esto? –

+0

Ampliado: lo llamaré muchas veces, a veces, bitCount puede ser cero. Si se garantiza que no tendrá ningún efecto sobre la variable, ¿lo agregará a la respuesta? – sharptooth

20

According to K&R "El resultado no está definido si el operando derecho es negativo, o mayor o igual que el número de bits en el tipo de expresión de la izquierda". (A.7.8) Por lo tanto, >> 0 es el desplazamiento de la derecha de la identidad y perfectamente legal.

1

Para que la función se documente de alguna manera, es posible que desee cambiar bitCount a unsigned para indicar a las personas que llaman que un valor negativo no es válido.

Cuestiones relacionadas