2012-04-01 4 views
6

En su mayoría desarrollo utilizando C#, pero creo que esta pregunta también podría ser adecuada para otros idiomas.
Además, parece que hay un montón de código aquí, pero la pregunta es muy simple.
¿Cómo se incorpora un método con una ruta de retorno absoluta?

Alinear, como yo lo entiendo, es el compilador (en el caso de C# la Máquina Virtual) reemplazando una llamada a método insertando el cuerpo del método en cada lugar desde el que se invocó el método.

Digamos que tengo el siguiente programa:

static Main() 
{   
    int number = 7; 
    bool a; 
    a = IsEven(number); 
    Console.WriteLine(a); 
} 

... el cuerpo del método IsEven:

bool IsEven(int n) 
{ 
    if (n % 2 == 0) // Two conditional return paths 
     return true; 
    else 
     return false; 
} 

pude entender cómo el código se verá como después inlining el método:

static Main() 
{ 
    int number = 7; 
    bool a; 
    if (number % 2 == 0) 
     a = true; 
    else 
     a = false; 
    Console.WriteLine(a); // Will print true if 'number' is even, otherwise false 
} 

Un programa obviamente simple y correcto.

Pero si modifico el cuerpo de IsEven un poco para incluir una vía de retorno absoluto ...

bool IsEven(int n) 
{ 
    if (n % 2 == 0) 
     return true; 
    return false; // <- Absolute return path! 
} 

personalmente me gusta este estilo un poco más en algunas situaciones. Algunas herramientas de refracción incluso pueden sugerir que cambie la primera versión para que se vea como esta, pero cuando traté de imaginar cómo sería este método cuando está impreso, me quedé perplejo.
Si Inline la segunda versión del método:

static Main() 
{ 
    int number = 7; 
    bool a; 
    if (number % 2 == 0) 
     a = true; 
    a = false; 
    Console.WriteLine(a); // Will always print false! 
} 

La pregunta que debe hacerse:
¿Cómo funciona el compilador/acuerdo de máquina virtual con inlining un método que tiene una trayectoria de retorno absoluto?
Parece extremadamente improbable que algo así evite realmente la creación de métodos, por lo que me pregunto cómo se manejan. Tal vez el proceso de inline no es tan simple como esto? ¿Tal vez una versión es más probable que esté en línea por la VM?

Editar: perfilar ambos métodos (y procesos en línea manual de la primera de ellas) no mostró diferencias en el rendimiento, por lo que sólo puede suponer que ambos métodos consiguen entre líneas y el trabajo de la misma manera o similar (al menos en mi VM).
Además, estos métodos son extremadamente simples y parecen casi intercambiables, pero los métodos complejos con rutas de retorno absolutas pueden ser mucho más difíciles de cambiar en versiones sin rutas de retorno absolutas.

Respuesta

6

Puede ser algo difícil explicar lo que JITter hace cuando se en línea - no cambia el código C# para hacer la línea entrante - trabajará (¿siempre?) En los bytes producidos (la versión compilada) y las "herramientas" que tiene al producir el código ensamblador (los bytes del código máquina reales) son mucho más finas que las que tiene en C# (o IL para el caso).

Dicho esto, puede tener una idea de cómo funciona, en términos de C# teniendo en cuenta la palabra clave break ..

en cuenta la posibilidad de que cada función en línea que no es trivial, está encerrado en un bucle de while (true) (o do while(false) bucle) - y que cada fuente de retorno se traduce a un conjunto de declaraciones localVar = result; break;. Entonces usted consigue algo como esto:

static Main() 
{ 
    int number = 7; 
    bool a; 
    while (true) 
    { 
     if (number % 2 == 0) 
     { 
      a = true; 
      break; 
     } 
     a = false; 
     break; 
    } 

    Console.WriteLine(a); // Will always print the right thing! Yey! 
} 

Del mismo modo, cuando se produce el montaje, se verá un montón de jmp s generándose - esas son el equivalente moral de las sentencias break, pero son mucho más flexible (pensar ellos como gotos anónimos o algo así).

Para que pueda ver, la inestabilidad (y cualquier compilador que compila a nativo) tiene MUCHAS herramientas a mano que puede utilizar para hacer "lo correcto".

+1

Y por cierto: este mecanismo de ruptura while (verdadero) también es útil para otras cosas: no lo uso a menudo, pero cuando hay un conjunto de operaciones que posiblemente se deben realizar en orden, pero que podrían ser detenido en cualquier momento dado, es útil. De manera similar, se usa en los lenguajes nativos para simular el constructo "finalmente" cuando no lo tienen (lo usé bastante en algunos proyectos) –

3

La declaración return indica:

  • El resultado valor
  • Destrucción de las variables locales (esto se aplica a C++, no en C#) En C#, finally bloques y disponer las llamadas en using bloques se ejecutará.
  • Un salto de la función

Todas estas cosas siguen sucediendo después de procesos en línea. El salto será local en lugar de funciones cruzadas después de la alineación, pero seguirá estando allí.

Incrustar NO es una sustitución textual como las macros C/C++.

Otras cosas que son diferentes entre inline y sustitución textual son el tratamiento de las variables con el mismo nombre.

2

El código de máquina que ejecuta la CPU es un lenguaje muy simple. No tiene la noción de declaración de devolución, una subrutina tiene un único punto de entrada y un único punto de salida. Por lo que su método ISEVEN() así:

bool IsEven(int n) 
{ 
    if (n % 2 == 0) 
     return true; 
    return false; 
} 

necesita ser reescrita por la fluctuación de algo que se parece a esto (no válido C#):

void IsEvent(int n) 
{ 
    if (n % 2 == 0) { 
     $retval = true; 
     goto exit; 
    } 
    $retval = false; 
exit: 
} // $retval becomes the function return value 

La variable retval $ podría ser falsa aquí. No lo es, es el registro EAX en un núcleo x86. Ahora verá que este código es simple de en línea, se puede trasplantar directamente en el cuerpo de Main(). La variable $ retval se puede equiparar a la variable a con una simple sustitución lógica.

Cuestiones relacionadas