#ifdef #ifndef w Javie


106

Wątpię, czy istnieje sposób na stworzenie warunków czasu kompilacji w Javie, takich jak #ifdef #ifndef w C ++.

Mój problem polega na tym, że mam algorytm napisany w Javie i mam inny czas pracy, który poprawia ten algorytm. Chcę więc zmierzyć, ile czasu oszczędzam, gdy wykorzystuję każdą poprawę.

W tej chwili mam zestaw zmiennych logicznych, które są używane do decydowania w czasie wykonywania, które ulepszenia powinny być używane, a które nie. Ale nawet testowanie tych zmiennych wpływa na całkowity czas pracy.

Dlatego chcę znaleźć sposób na podjęcie decyzji w czasie kompilacji, które części programu powinny zostać skompilowane i użyte.

Czy ktoś zna sposób na zrobienie tego w Javie. A może ktoś wie, że nie ma takiego sposobu (też by się przydał).

Odpowiedzi:


126
private static final boolean enableFast = false;

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

Warunki warunkowe, takie jak pokazane powyżej, są oceniane w czasie kompilacji. Jeśli zamiast tego użyjesz tego

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

Następnie wszystkie warunki zależne od enableFast zostaną ocenione przez kompilator JIT. Koszt tego jest znikomy.


To rozwiązanie jest lepsze od mojego. Kiedy próbowałem zainicjować zmienne z zadaną wartością zewnętrzną, czas działania wrócił do 3 sekund. Ale kiedy zdefiniowałem zmienne jako zmienne klasy statycznej (a nie zmienną lokalną funkcji), czas działania powrócił do 1 sekundy. Dzięki za pomoc.
jutky

6
IIRC, działało to nawet zanim Java miała kompilator JIT. javacMyślę, że kod został usunięty . To działało tylko wtedy, gdy wyrażenie for (powiedzmy) enableFastbyło wyrażeniem stałej czasu kompilacji.
Stephen C

2
Tak, ale ten warunek musi znajdować się w metodzie, prawda? A co z przypadkiem, w którym mamy kilka prywatnych statycznych ciągów końcowych, które chcielibyśmy ustawić. (np. zestaw adresów URL serwerów, które są ustawione inaczej na potrzeby produkcyjne i
tymczasowe

3
@tomwhipple: true, plus to nie pozwala na zrobienie czegoś takiego jak: private void foo(#ifdef DEBUG DebugClass obj #else ReleaseClass obj #endif )
Zonko,

3
a co z importem (na przykład w odniesieniu do ścieżki klas)?
n611x007

44

javac nie wyświetli skompilowanego kodu, który jest nieosiągalny. Użyj końcowej zmiennej ustawionej na stałą wartość dla swojej #definei zwykłej ifinstrukcji dla #ifdef.

Możesz użyć javap, aby udowodnić, że nieosiągalny kod nie jest zawarty w pliku klasy wyjściowej. Weźmy na przykład pod uwagę następujący kod:

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 daje następujący wynik, wskazując, że tylko jedna z dwóch ścieżek została wkompilowana (a instrukcja if nie była):

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

2
Czy to jest specyficzne dla javac, czy też JLS gwarantuje takie zachowanie?
Pacerier,

@pacerier, nie mam pojęcia, czy jest to gwarantowane przez JLS, ale było to prawdą dla każdego kompilatora java, z którym się spotkałem od lat 90-tych, z możliwym wyjątkiem przed 1.1.7 i tylko dlatego, że tego nie zrobiłem to przetestuj.

12

Myślę, że znalazłem rozwiązanie, jest znacznie prostsze.
Jeśli zdefiniuję zmienne boolowskie za pomocą "końcowego" modyfikatora, sam kompilator Javy rozwiązuje problem. Ponieważ wie z góry, jaki byłby wynik przetestowania tego warunku. Na przykład ten kod:

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

działa około 3 sekund na moim komputerze.
I ten

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

kursuje około 1 sekundy. W tym samym czasie zajmuje ten kod

    int j=0;
    for(int i=0;i<1000000000;i++){
        j++;
    }

1
To jest interesujące. Wygląda na to, że JIT obsługuje już kompilację warunkową! Czy to działa, jeśli te finały są w innej klasie lub innym pakiecie?
joeytwiddle

Wspaniały! Więc uważam, że to musi być optymalizacja w czasie wykonywania, kod nie jest w rzeczywistości usuwany w czasie kompilacji. Nie ma problemu, o ile używasz dojrzałej maszyny wirtualnej.
joeytwiddle

@joeytwiddle, słowo kluczowe to „tak długo, jak używasz” dojrzałej maszyny wirtualnej.
Pacerier,

2

Nigdy go nie używałem, ale to istnieje

JCPP to kompletna, zgodna, samodzielna, czysta Java implementacja preprocesora C. Jest przeznaczony dla osób piszących kompilatory w stylu C w Javie przy użyciu narzędzi takich jak sablecc, antlr, JLex, CUP i tak dalej. Ten projekt został użyty do pomyślnego wstępnego przetworzenia większości kodu źródłowego biblioteki GNU C. Od wersji 1.2.5 może również wstępnie przetwarzać bibliotekę Apple Objective C.

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


1
Nie jestem pewien, czy to odpowiada moim potrzebom. Mój kod jest napisany w Javie. Może proponujesz mi zdobycie ich źródeł i wykorzystanie ich do wstępnego przetworzenia mojego kodu?
jutky

2

Jeśli naprawdę potrzebujesz kompilacji warunkowej i używasz Ant , możesz być w stanie przefiltrować swój kod i wykonać w nim wyszukiwanie i zamianę.

Na przykład: http://weblogs.java.net/blog/schaefa/archive/2005/01/how_to_do_condi.html

W ten sam sposób można na przykład napisać filtr do wymiany LOG.debug(...);z /*LOG.debug(...);*/. To i tak działałoby szybciej niż if (LOG.isDebugEnabled()) { ... }rzeczy, nie wspominając o tym, że jest jednocześnie bardziej zwięzłe.

Jeśli używasz Mavena , tutaj opisano podobną funkcję .


2

Manifold zapewnia w pełni zintegrowany preprocesor Java (bez kroków kompilacji ani wygenerowanego źródła). Jest przeznaczony wyłącznie do kompilacji warunkowej i używa dyrektyw w stylu C.

Preprocesor Java Manifolda


1

Użyć wzorca fabryki do przełączania się między implementacjami klasy?

Czas tworzenia obiektu nie może być teraz problemem, prawda? Po uśrednieniu z długiego okresu czasu, największy składnik czasu spędzonego powinien teraz znajdować się w głównym algorytmie, prawda?

Ściśle mówiąc, tak naprawdę nie potrzebujesz preprocesora, aby zrobić to, co chcesz osiągnąć. Najprawdopodobniej istnieją inne sposoby spełnienia twoich wymagań niż ten, który oczywiście zaproponowałem.


Zmiany są bardzo drobne. Podobnie jak testowanie niektórych warunków, aby z wyprzedzeniem poznać żądany wynik zamiast go ponownie obliczyć. Więc narzut wywołania funkcji może być dla mnie nieodpowiedni.
jutky

0
final static int appFlags = context.getApplicationInfo().flags;
final static boolean isDebug = (appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.