#ifdef #ifndef in Java

106

Ich bezweifle, dass es eine Möglichkeit gibt, Bedingungen zur Kompilierungszeit in Java wie #ifdef #ifndef in C ++ zu erstellen.

Mein Problem ist, dass ein Algorithmus in Java geschrieben ist und ich andere Laufzeitverbesserungen zu diesem Algorithmus habe. Ich möchte also messen, wie viel Zeit ich spare, wenn jede Verbesserung verwendet wird.

Im Moment habe ich eine Reihe von booleschen Variablen, mit denen während der Laufzeit entschieden wird, welche Verbesserung verwendet werden soll und welche nicht. Aber selbst das Testen dieser Variablen beeinflusst die Gesamtlaufzeit.

Daher möchte ich herausfinden, wie ich während der Kompilierungszeit entscheiden kann, welche Teile des Programms kompiliert und verwendet werden sollen.

Kennt jemand einen Weg, dies in Java zu tun. Oder vielleicht weiß jemand, dass es keinen solchen Weg gibt (es wäre auch nützlich).

jutky
quelle

Antworten:

126
private static final boolean enableFast = false;

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

Bedingungen wie die oben gezeigten werden zur Kompilierungszeit ausgewertet. Wenn Sie dies stattdessen verwenden

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

Dann werden alle von enableFast abhängigen Bedingungen vom JIT-Compiler ausgewertet. Der Aufwand hierfür ist vernachlässigbar.

Mark Thornton
quelle
Diese Lösung ist besser als meine. Als ich versuchte, die Variablen mit einem voreingestellten äußeren Wert zu initialisieren, ging die Laufzeit auf 3 Sekunden zurück. Als ich die Variablen als statische Klassenvariablen (und nicht als lokale Funktionsvariable) definierte, kehrte die Laufzeit auf 1 Sekunde zurück. Danke für die Hilfe.
Jutky
6
IIRC, dies funktionierte sogar, bevor Java einen JIT-Compiler hatte. Der Code wurde von javacich denke entfernt. Dies funktionierte nur, wenn der Ausdruck für (sagen wir) enableFastein Ausdruck für die Kompilierungszeitkonstante war.
Stephen C
2
Ja, aber diese Bedingung muss innerhalb einer Methode liegen, richtig? Was ist mit dem Fall, in dem wir eine Reihe privater statischer Endzeichenfolgen haben, die wir festlegen möchten? (zB eine Reihe von Server-URLs, die für Produktion und Staging unterschiedlich eingestellt sind)
Tomwhipple
3
@ Tomwhipple: wahr, und dies erlaubt Ihnen nicht, etwas zu tun wie: private void foo(#ifdef DEBUG DebugClass obj #else ReleaseClass obj #endif )
Zonko
3
Was ist mit Importen (zum Beispiel in Bezug auf Klassenpfade)?
n611x007
44

javac gibt keinen kompilierten Code aus, der nicht erreichbar ist. Verwenden Sie eine endgültige Variable, die auf einen konstanten Wert für Ihre #defineund eine normale ifAnweisung für die festgelegt ist #ifdef.

Sie können javap verwenden, um zu beweisen, dass der nicht erreichbare Code nicht in der Ausgabeklassendatei enthalten ist. Betrachten Sie beispielsweise den folgenden Code:

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 gibt die folgende Ausgabe aus, die angibt, dass nur einer der beiden Pfade kompiliert wurde (und die if-Anweisung nicht):

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
quelle
2
Ist dieser Javac spezifisch oder wird dieses Verhalten tatsächlich vom JLS garantiert?
Pacerier
@pacerier, ich habe keine Ahnung, ob dies vom JLS garantiert wird, aber es gilt für jeden Java-Compiler, auf den ich seit den 90er Jahren gestoßen bin, mit der möglichen Ausnahme vor 1.1.7, und nur, weil ich es nicht getan habe teste es dann.
12

Ich denke, dass ich die Lösung gefunden habe. Es ist viel einfacher.
Wenn ich die booleschen Variablen mit dem Modifikator "final" definiere, löst der Java-Compiler selbst das Problem. Weil es im Voraus weiß, was das Ergebnis des Testens dieses Zustands sein würde. Zum Beispiel dieser Code:

    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++;
    }

läuft ca. 3 Sekunden auf meinem Computer.
Und das hier

    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++;
    }

läuft ca. 1 Sekunde. Die gleiche Zeit, die dieser Code benötigt

    int j=0;
    for(int i=0;i<1000000000;i++){
        j++;
    }
jutky
quelle
1
Das ist interessant. Es klingt so, als ob JIT bereits die bedingte Kompilierung unterstützt! Funktioniert es, wenn diese Endspiele in einer anderen Klasse oder einem anderen Paket stattfinden?
Joeytwiddle
Toll! Dann glaube ich, dass dies eine Laufzeitoptimierung sein muss, der Code wird zur Kompilierungszeit nicht wirklich entfernt. Das ist in Ordnung, solange Sie eine ausgereifte VM verwenden.
Joeytwiddle
@joeytwiddle, Schlüsselwort ist "solange Sie eine ausgereifte VM verwenden".
Pacerier
2

Ich habe es nie benutzt, aber das gibt es

JCPP ist eine vollständige, kompatible, eigenständige, reine Java-Implementierung des C-Präprozessors. Es soll für Benutzer von C-Style-Compilern in Java mit Tools wie sablecc, antlr, JLex, CUP usw. von Nutzen sein. Dieses Projekt wurde verwendet, um einen Großteil des Quellcodes der GNU C-Bibliothek erfolgreich vorzuverarbeiten. Ab Version 1.2.5 kann auch die Apple Objective C-Bibliothek vorverarbeitet werden.

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

Tom
quelle
1
Ich bin mir nicht sicher, ob dies meinen Bedürfnissen entspricht. Mein Code ist in Java geschrieben. Vielleicht schlagen Sie mir vor, ihre Quellen zu beschaffen und sie zur Vorverarbeitung meines Codes zu verwenden?
Jutky
2

Wenn Sie wirklich eine bedingte Kompilierung benötigen und Ant verwenden , können Sie möglicherweise Ihren Code filtern und darin suchen und ersetzen.

Zum Beispiel: http://weblogs.java.net/blog/schaefa/archive/2005/01/how_to_do_condi.html

Auf die gleiche Weise können Sie beispielsweise einen Filter schreiben, durch den ersetzt LOG.debug(...);werden soll /*LOG.debug(...);*/. Dies würde immer noch schneller als if (LOG.isDebugEnabled()) { ... }alles andere funktionieren, ganz zu schweigen davon, dass es gleichzeitig prägnanter ist.

Wenn Sie Maven , gibt es eine ähnliche Funktion beschrieben hier .

Rustyx
quelle
2

Manifold bietet einen vollständig integrierten Java-Präprozessor (keine Erstellungsschritte oder generierte Quelle). Es zielt ausschließlich auf die bedingte Kompilierung ab und verwendet Anweisungen im C-Stil.

Java-Präprozessor von Manifold

Scott
quelle
1

Verwenden Sie das Factory-Muster, um zwischen Implementierungen einer Klasse zu wechseln.

Die Objekterstellungszeit kann jetzt kein Problem sein, oder? Im Durchschnitt über einen langen Zeitraum sollte der größte Teil der aufgewendeten Zeit im Hauptalgorithmus liegen, nicht wahr?

Genau genommen brauchen Sie keinen Präprozessor, um das zu tun, was Sie erreichen möchten. Es gibt höchstwahrscheinlich andere Möglichkeiten, Ihre Anforderungen zu erfüllen, als die, die ich natürlich vorgeschlagen habe.

jldupont
quelle
Die Änderungen sind sehr gering. Testen Sie beispielsweise einige Bedingungen, um das angeforderte Ergebnis im Voraus zu kennen, anstatt es neu zu berechnen. Der Aufwand für den Aufruf der Funktion könnte daher für mich nicht geeignet sein.
Jutky
0
final static int appFlags = context.getApplicationInfo().flags;
final static boolean isDebug = (appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
alicanbatur
quelle