performance - quién - tabla de codigo bytecode
¿Cómo se implementa la coincidencia de patrones en Scala en el nivel de bytecode? (3)
¿Cómo se implementa la coincidencia de patrones en Scala en el nivel de bytecode?
¿Es como una serie de construcciones if (x instanceof Foo)
, o algo más? ¿Cuáles son sus implicaciones de rendimiento?
Por ejemplo, dado el siguiente código (de Scala By Example páginas 46-48), ¿cómo sería el código equivalente de Java para el método eval
?
abstract class Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr
def eval(e: Expr): Int = e match {
case Number(x) => x
case Sum(l, r) => eval(l) + eval(r)
}
PD: Puedo leer código byte de Java, por lo que una representación de bytecode sería lo suficientemente buena para mí, pero probablemente sería mejor para los otros lectores saber cómo se vería como código Java.
PPS ¿Responde el libro Programming in Scala a esta y otras preguntas similares sobre cómo se implementa Scala? He ordenado el libro, pero aún no ha llegado.
Desde la versión 2.8, Scala ha tenido la anotación @switch . El objetivo es garantizar que la coincidencia de patrones se compile en el cambio de tabla o el conmutador de búsqueda en lugar de la serie de sentencias if
condicionales.
El bajo nivel se puede explorar con un desensamblador, pero la respuesta corta es que se trata de un conjunto de si / elses donde el predicado depende del patrón.
case Sum(l,r) // instance of check followed by fetching the two arguments and assigning to two variables l and r but see below about custom extractors
case "hello" // equality check
case _ : Foo // instance of check
case x => // assignment to a fresh variable
case _ => // do nothing, this is the tail else on the if/else
Hay mucho más que puedes hacer con patrones o patrones y combinaciones como "caso Foo (45, x)", pero generalmente esas son solo extensiones lógicas de lo que acabo de describir. Los patrones también pueden tener protecciones, que son restricciones adicionales en los predicados. También hay casos en que el compilador puede optimizar la coincidencia de patrones, por ejemplo, cuando hay una superposición entre casos, puede unir un poco las cosas. Los patrones avanzados y la optimización son un área activa de trabajo en el compilador, por lo que no se sorprenda si el código de bytes mejora sustancialmente sobre estas reglas básicas en las versiones actuales y futuras de Scala.
Además de todo eso, puede escribir sus propios extractores personalizados además o en lugar de los predeterminados que Scala usa para las clases de casos. Si lo hace, entonces el costo de la coincidencia de patrón es el costo de lo que sea que haga el extractor. Se encuentra una buena descripción en http://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf
James (arriba) lo dijo mejor. Sin embargo, si tiene curiosidad, siempre es un buen ejercicio observar el bytecode desensamblado. También puede invocar scalac
con la opción -print
, que imprimirá su programa con todas las características específicas de Scala eliminadas. Es básicamente Java en la ropa de Scala. Aquí está la salida de scalac -print
relevante para el fragmento de código que le diste:
def eval(e: Expr): Int = {
<synthetic> val temp10: Expr = e;
if (temp10.$isInstanceOf[Number]())
temp10.$asInstanceOf[Number]().n()
else
if (temp10.$isInstanceOf[Sum]())
{
<synthetic> val temp13: Sum = temp10.$asInstanceOf[Sum]();
Main.this.eval(temp13.e1()).+(Main.this.eval(temp13.e2()))
}
else
throw new MatchError(temp10)
};