#ifdef #ifndef en Java

106

Dudo que haya una manera de crear condiciones en tiempo de compilación en Java como #ifdef #ifndef en C ++.

Mi problema es que tengo un algoritmo escrito en Java, y tengo diferentes mejoras de tiempo de ejecución para ese algoritmo. Por eso quiero medir cuánto tiempo ahorro cuando se usa cada mejora.

En este momento tengo un conjunto de variables booleanas que se utilizan para decidir durante el tiempo de ejecución qué mejora se debe utilizar y cuál no. Pero incluso probar esas variables influye en el tiempo total de ejecución.

Así que quiero encontrar una manera de decidir durante el tiempo de compilación qué partes del programa deben compilarse y usarse.

¿Alguien sabe una forma de hacerlo en Java? O tal vez alguien sepa que no existe tal forma (también sería útil).

jutky
fuente

Respuestas:

126
private static final boolean enableFast = false;

// ...
if (enableFast) {
  // This is removed at compile time
}

Los condicionales como el que se muestra arriba se evalúan en tiempo de compilación. Si en cambio usas esto

private static final boolean enableFast = "true".equals(System.getProperty("fast"));

Entonces, cualquier condición que dependa de enableFast será evaluada por el compilador JIT. La sobrecarga para esto es insignificante.

Mark Thornton
fuente
Esta solución es mejor que la mía. Cuando intenté inicializar las variables con un valor externo preestablecido, el tiempo de ejecución volvió a 3 segundos. Pero cuando definí las variables como variables de clase estáticas (y no una variable local de función) el tiempo de ejecución volvió a 1 segundo. Gracias por la ayuda.
jutky
6
IIRC, esto incluso funcionó antes de que Java tuviera un compilador JIT. javacCreo que el código fue eliminado . Esto solo funcionó si la expresión para (digamos) enableFastera una expresión constante de tiempo de compilación.
Stephen C
2
Sí, pero este condicional debe residir dentro de un método, ¿correcto? ¿Qué pasa con el caso en el que tenemos un montón de cadenas finales estáticas privadas que nos gustaría configurar? (por ejemplo, un conjunto de direcciones URL del servidor que se establecen de forma diferente para la estadificación vs. producción)
tomwhipple
3
@tomwhipple: cierto, además, esto no te permite hacer algo como: private void foo(#ifdef DEBUG DebugClass obj #else ReleaseClass obj #endif )
Zonko
3
¿qué pasa con las importaciones (por ejemplo, con respecto a classpath)?
n611x007
44

javac no generará código compilado que sea inalcanzable. Utilice una variable final establecida en un valor constante para su #definey una ifdeclaración normal para #ifdef.

Puede usar javap para demostrar que el código inalcanzable no está incluido en el archivo de clase de salida. Por ejemplo, considere el siguiente código:

public class Test
{
   private static final boolean debug = false;

   public static void main(String[] args)
   {
       if (debug) 
       {
           System.out.println("debug was enabled");
       }
       else
       {
           System.out.println("debug was not enabled");
       }
   }
}

javap -c Test da el siguiente resultado, lo que indica que solo se compiló una de las dos rutas (y la declaración if no):

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #3; //String debug was not enabled
   5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return
Phil Ross
fuente
2
¿Es este javac específico o este comportamiento está realmente garantizado por JLS?
Pacerier
@pacerier, no tengo idea de si esto está garantizado por JLS, pero ha sido cierto para todos los compiladores de Java con los que me he encontrado desde los años 90, con la posible excepción de antes de 1.1.7, y solo porque no lo hice pruébelo entonces.
12

Creo que encontré la solución, es mucho más simple.
Si defino las variables booleanas con el modificador "final", el propio compilador de Java resuelve el problema. Porque sabe de antemano cuál sería el resultado de probar esta condición. Por ejemplo este código:

    boolean flag1 = true;
    boolean flag2 = false;
    int j=0;
    for(int i=0;i<1000000000;i++){
        if(flag1)
            if(flag2)
                j++;
            else
                j++;
        else
            if(flag2)
                j++;
            else
                j++;
    }

se ejecuta aproximadamente 3 segundos en mi computadora.
Y éste

    final boolean flag1 = true;
    final boolean flag2 = false;
    int j=0;
    for(int i=0;i<1000000000;i++){
        if(flag1)
            if(flag2)
                j++;
            else
                j++;
        else
            if(flag2)
                j++;
            else
                j++;
    }

dura aproximadamente 1 segundo. El mismo tiempo que tarda este código

    int j=0;
    for(int i=0;i<1000000000;i++){
        j++;
    }
jutky
fuente
1
Eso es interesante. ¡Parece que JIT ya admite la compilación condicional! ¿Funciona si esas finales son en otra clase o en otro paquete?
joeytwiddle
¡Excelente! Entonces creo que esto debe ser una optimización en tiempo de ejecución, el código en realidad no se está eliminando en tiempo de compilación. Eso está bien siempre que use una máquina virtual madura.
joeytwiddle
@joeytwiddle, Keyword es "siempre que use" una máquina virtual madura.
Pacerier
2

Nunca lo usé, pero esto existe

JCPP es una implementación Java pura, completa, compatible y autónoma del preprocesador C. Está destinado a ser útil para las personas que escriben compiladores de estilo C en Java utilizando herramientas como sablecc, antlr, JLex, CUP, etc. Este proyecto se ha utilizado para preprocesar con éxito gran parte del código fuente de la biblioteca GNU C. A partir de la versión 1.2.5, también puede preprocesar la biblioteca de Apple Objective C.

http://www.anarres.org/projects/jcpp/

Tom
fuente
1
No estoy seguro de que esto se adapte a mis necesidades. Mi código está escrito en Java. ¿Quizás me está proponiendo obtener sus fuentes y usarlas para preprocesar mi código?
jutky
2

Si realmente necesita una compilación condicional y usa Ant , es posible que pueda filtrar su código y hacer una búsqueda y reemplazo en él.

Por ejemplo: http://weblogs.java.net/blog/schaefa/archive/2005/01/how_to_do_condi.html

De la misma manera se puede, por ejemplo, escribir un filtro para reemplazar LOG.debug(...);con /*LOG.debug(...);*/. Esto aún se ejecutaría más rápido que las if (LOG.isDebugEnabled()) { ... }cosas, sin mencionar que sería más conciso al mismo tiempo.

Si usa Maven , hay una función similar que se describe aquí .

rustyx
fuente
2

Manifold proporciona un preprocesador Java completamente integrado (sin pasos de compilación ni fuente generada). Se dirige exclusivamente a la compilación condicional y utiliza directivas de estilo C.

Preprocesador Java de Manifold

Scott
fuente
1

¿Usar el patrón de fábrica para cambiar entre implementaciones de una clase?

El tiempo de creación del objeto no puede ser una preocupación ahora, ¿verdad? Cuando se promedia durante un período de tiempo de ejecución prolongado, el mayor componente del tiempo invertido debería estar en el algoritmo principal ahora, ¿no es así?

Estrictamente hablando, realmente no necesita un preprocesador para hacer lo que busca lograr. Lo más probable es que existan otras formas de cumplir con sus requisitos además de la que he propuesto, por supuesto.

jldupont
fuente
Los cambios son muy pequeños. Como probar algunas condiciones para conocer de antemano el resultado solicitado en lugar de volver a calcularlo. Entonces, la sobrecarga de la llamada a la función podría no ser adecuada para mí.
Jutky
0
final static int appFlags = context.getApplicationInfo().flags;
final static boolean isDebug = (appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
alicanbatur
fuente