¿Se puede evitar que JIT optimice las llamadas de método?

Estamos creando una herramienta para el análisis de tiempo de ejecución de casos promedio de los programas Java Byte Code. Una parte de esto es medir tiempos de ejecución reales. Por lo tanto, tomaríamos un método arbitrario proporcionado por el usuario que puede tener o no un resultado y puede tener o no efectos secundarios (los ejemplos incluyen Quicksort, factorial, bucles anidados simulados, ...) y ejecutarlo (usando la reflexión), midiendo El tiempo transcurrido. (El hecho de que hagamos o no una evaluación comparativa adecuada es algo aparte del punto aquí).

En el código de evaluación comparativa, obviamente no hacemos nada con el resultado (y algunos métodos ni siquiera tendrán resultados). Por lo tanto, no se sabe qué puede hacer el JIT, y de hecho hemos observado que parece optimizar la llamada de método de referencia en ocasiones. Como los métodos de referencia no se utilizan de forma aislada en la realidad, esto hace que la referencia sea inútil.

¿Cómo podemos evitar que JIT haga eso? No queremos desactivarlo por completo porque la evaluación comparativa lleva años, y queremos evaluar los tiempos de ejecución "reales" de todos modos (por lo que queremos que JIT esté activo dentro del método).

Soy consciente de esta pregunta, pero el escenario dado es demasiado estrecho; no conocemos el tipo de resultado (si hay uno) y, por lo tanto, no podemos usar el resultado de alguna manera, el JIT no lo considera inútil.

Respuesta 1

La solución simple es escribir un punto de referencia más realista que haga algo casi útil para que no se optimice.

Hay una serie de trucos para confundir al JIT, pero es poco probable que te ayuden.

Aquí hay un ejemplo de un punto de referencia donde el método se llama a través de la reflexión, MethodHandle y se compila a nada.

import java.lang.invoke.*;
import java.lang.reflect.*;

public class Main {
    public static void main(String... args) throws Throwable {
        for (int j = 0; j < 5; j++) {
            testViaReflection();
            testViaMethodHandle();
            testWithoutReflection();
        }
    }

    private static void testViaReflection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        Method nothing = Main.class.getDeclaredMethod("nothing");
        int runs = 10000000; // triggers a warmup.
        long start = System.nanoTime();
        Object[] args = new Object[0];
        for (int i = 0; i < runs; i++)
            nothing.invoke(null, args);
        long time = System.nanoTime() - start;
        System.out.printf("A call to %s took an average of %.1f ns using reflection%n", nothing.getName(), 1.0 * time / runs);
    }

    private static void testViaMethodHandle() throws Throwable {
        MethodHandle nothing = MethodHandles.lookup().unreflect(Main.class.getDeclaredMethod("nothing"));
        int runs = 10000000; // triggers a warmup.
        long start = System.nanoTime();
        for (int i = 0; i < runs; i++) {
            nothing.invokeExact();
        }
        long time = System.nanoTime() - start;
        System.out.printf("A call to %s took an average of %.1f ns using MethodHandle%n", "nothing", 1.0 * time / runs);
    }

    private static void testWithoutReflection() {
        int runs = 10000000; // triggers a warmup.
        long start = System.nanoTime();
        for (int i = 0; i < runs; i++)
            nothing();
        long time = System.nanoTime() - start;
        System.out.printf("A call to %s took an average of %.1f ns without reflection%n", "nothing", 1.0 * time / runs);
    }

    public static void nothing() {
        // does nothing.
    }
}

huellas dactilares

A call to nothing took an average of 6.6 ns using reflection
A call to nothing took an average of 10.7 ns using MethodHandle
A call to nothing took an average of 0.4 ns without reflection
A call to nothing took an average of 4.5 ns using reflection
A call to nothing took an average of 9.1 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 4.3 ns using reflection
A call to nothing took an average of 8.8 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 5.4 ns using reflection
A call to nothing took an average of 13.2 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection
A call to nothing took an average of 4.9 ns using reflection
A call to nothing took an average of 8.7 ns using MethodHandle
A call to nothing took an average of 0.0 ns without reflection

Supuse que MethodHandles era más rápido que la reflexión, pero no parece ser así.

Respuesta: 2

No creo que haya ninguna forma de deshabilitar selectivamente las optimizaciones JIT, excepto algunas de las experimentales (como el análisis de escape).

Tú dices esto:

No queremos apagarlo por completo porque la evaluación comparativa lleva años, y queremos evaluar los tiempos de ejecución "reales" de todos modos.

Pero lo que estás tratando de hacer es precisamente eso. En un tiempo de ejecución real, las llamadas a métodos se alinearán y se optimizarán si no hacen nada. Entonces, al inhibir estas optimizaciones, obtendría mediciones para el tiempo de ejecución del método que no coinciden con lo que realmente sucede en un programa real.

Respuesta: 3

Tengo una restricción para mi sistema de producción de que todos los cambios de SQL deben ser ejecutados manualmente por un DBA por razones de seguridad. En consecuencia, quiero usar Liquibase para generar el SQL, y tener el DBA ...

Soy bastante nuevo en Java y me cuesta ver por qué sigo recibiendo un error en la línea de comando. Tengo una superclase, Guest, con variables guestID, fName, lName y dateJoin. También tengo una subclase ...

Realmente me gustaría usar graalVM por sus capacidades AOT para reducir drásticamente el tiempo de arranque de mis aplicaciones. Cuando leí que Quarkus.io y graalVM AOT funcionaban bien con hibernate, realmente tuve el ...

Utilizo maven-gae-plugin para implementar mi aplicación, funciona find hasta que decido implementarlo en otra aplicación-id bajo una cuenta de Google diferente. Me encontré con el problema "Esta aplicación no existe". Encontré allí ...