Program kompilowany chodzi szybciej niż budowany

2

Witam,

tworzę sobie własną grę pisaną w języku Java z biblioteką JSwing. Podczas pisania programu nie zauważyłem żadnych problemów optymalizacyjnych, program chodzi bardzo szybko, powiedział bym, że płynnie, wszystko jest cacy tak jak ma być. Niestety nastąpiła chwila, kiedy musiałem udostępnić program osobom trzecim, przez co zostałem zmuszony do "zbudowania" programu aby nie oddawać również kodów źródłowych. No i tutaj pojawia się problem. Otóż program ten znacznie spowolnił.

Przykład? Postać porusza się po kratkach 32x32 px. Pomiędzy przejściem z jednej kratki na drugą wykonuje się animacja. Postać przesuwa się 4 razy w danym kierunku z przesunięciem o 8 px oraz zmienia się obrazek postaci imitujący jej ruch. W momencie, kiedy program jest kompilowany, cała akcja przejścia pomiędzy kratkami odbywa się w ciągu kilku milisekund. W momencie, kiedy program jest zbudowany i odpalam go z pliku .jar, przejście tej postaci pomiędzy kratkami odbywa się w ciągu (dajmy na to) pół sekundy. To jest znaczne opóźnienie.

Z czego to opóźnienie może wynikać? Czy podczas budowania programu trzeba na coś jeszcze zwrócić uwagę, np. jakieś dodatkowe logowania itd. ?

Program udostępniony osobom trzecim załączam jako załącznik. Aplikacja kompilowana była w Netbeans IDE 8.0.2, baza danych utworzona jest w SQLite

0

Najlepiej spróbować uruchomić jakiś profiler. Poszukaj pod java profiler. Pewnie jest coś w ramach SDK. Profiler szybko wskaże Ci wąskie gardła. Sam jestem ciekaw, co wyjdzie. Może w pliku jar brakuje jakichś elementów, które są przy uruchamianiu ręcznym.

Inny sposób badania tego problemu to jakieś inne, pośrednie, metody uruchamiania. Np. wypakować jara (to zip) do plików i uruchamiać poleceniem java -cp .. Albo od drugiej strony - skopiować pliki klas z katalogu netbeansa do jakiegoś nowego folderu i tam odpadalć. Przy większych projektach zajęłoby to za dużo czasu, ma sens tylko w czymś prostym, bez wielu dependency.

Skoro jedna operacja zajmuje 0,5 sekundy, to nawet zwykły debuger powinien wskazać problematyczne miejsce. Albo logowanie na stdout.

0

Prawdopodobnie jest to przez różne tryby pracy JVM w tych dwóch przypadkach, a dokładniej różne tryby kompilacji JIT lub ew. rodzaje używanego garbage collectora. Podłącz się przez VisualVM w obywdu przypadkach, pooglądaj i porównaj.

1

Zbudowany JAR to ZIP z włączoną kompresją. Jeśli twój program bardzo często wczytuje zasoby w kółko to znaczy, że pliki z tego ZIPa będą musiały być w kółko rozpakowywane. Spróbuj zrobić jakiś cache dla zasobów, obojętne czy w pamięci czy np na dysku w jakimś katalogu tymczasowym. Ewentualnie spróbuj wyłączyć kompresję dla tego JARa.

0

Jak nie oddałeś kodu, jak oddałeś, c# i java są w pełni dekompilowalne, c/c++ są dekompilowalne też ale nie w 100% tzn. kod robi to samo, ale nie koniecznie jest tak samo zrobiony jak jego autor to zrobił, ale robi to samo. Chodź w sumie to samo można w javie zrobić.
Taki problem że większość czynności można na wiele sposobów wykonać.

Obfuskacja jest bardziej skuteczna bezpośrednio na procesorze.
Z logicznego punktu widzenia jest trudniejsza do analizy.

Budowę traktujesz jak plik skompresowany, ten plik raz jest rozpakowany i potem czas wykonywania jest taki sam.
Jak pamięć na takich urządzeniach jak android jest czymś świętym, to trzeba o to dbać.
Jednak tam pamięci zawsze braknie i nawet lepiej jak coś się dłużej odpala, ale działa tak samo, a zajmuje mniej pamięci.

3

Zrobiłem testa i rzeczywiście wychodzi na to, że czytanie zasobów ze spakowanego JARa jest znacznie wolniejsze.

Jako plik testowy posłużył mi English text z http://www.maximumcompression.com/data/files/index.html

Kod testowy:

package fgd;

import java.io.IOException;
import java.io.InputStream;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        byte[] buffer = new byte[4096];
        for (int i = 0; i < 10; i++) {
            long startIterationTime = System.currentTimeMillis();
            InputStream stream = App.class.getResourceAsStream("/world95.txt");
            while (stream.read(buffer) >= 0) ;
            long totalIterationTime = System.currentTimeMillis() - startIterationTime;
            System.out.println("time taken for iteration: " + totalIterationTime + "ms");
        }
        long totalTime = System.currentTimeMillis() - startTime;
        System.out.println("time taken: " + totalTime + "ms");
    }
}

Odpalony wprost z IntelliJa daje taki wynik:

time taken for iteration: 8ms
time taken for iteration: 2ms
time taken for iteration: 1ms
time taken for iteration: 1ms
time taken for iteration: 1ms
time taken for iteration: 0ms
time taken for iteration: 1ms
time taken for iteration: 5ms
time taken for iteration: 1ms
time taken for iteration: 1ms
time taken: 22ms

Odpalony JAR z linii komend daje taki wynik:

time taken for iteration: 13ms
time taken for iteration: 11ms
time taken for iteration: 11ms
time taken for iteration: 10ms
time taken for iteration: 11ms
time taken for iteration: 10ms
time taken for iteration: 11ms
time taken for iteration: 11ms
time taken for iteration: 10ms
time taken for iteration: 11ms
time taken: 109ms

Spróbowałem jeszcze wyłączyć kompresję JARa za pomocą porad tutaj: https://stackoverflow.com/questions/1032685/how-to-turn-off-jar-compression-in-maven
Wyniki:

time taken for iteration: 3ms
time taken for iteration: 1ms
time taken for iteration: 1ms
time taken for iteration: 0ms
time taken for iteration: 1ms
time taken for iteration: 1ms
time taken for iteration: 0ms
time taken for iteration: 1ms
time taken for iteration: 0ms
time taken for iteration: 1ms
time taken: 9ms

Podsumowując:

  • najszybciej z nieskompresowanego JARa: 9ms
  • nieco wolniej prosto z dysku: 22ms
  • bardzo wolno ze skompresowanego JARa: 109ms

Rozwiązaniem na krótką metę jest więc wyłączenie kompresji JARa. Możesz go skompresować po zbudowaniu, np wrzucając go wprost do pliku ZIP, RAR, 7z czy cokolowiek.
Rozwiązaniem na dłuższą metę byłoby poprawne zarządzanie zasobami, a nie wczytywanie ich w kółko. Prawdopodobnie nie dość że w kółko wczytujesz strumienie to jeszcze w kółko tworzysz jakieś obiekty Bitmap, Image czy tym podobne. Wstaw sobie licznik do kodu by sprawdzić ile razy w rzeczywistości wczytujesz zasoby i uzmysłowić sobie skalę problemu.

0

Jako ciekawostka. Można jeszcze zasoby ładować z plików, a nie z jara. Dokładny opis eksperymentu: http://todayguesswhat.blogspot.com/2011/03/jar-manifestmf-class-path-referencing.html

U mnie działa Class-Path: zasoby/

0

Póki co to kombinuję nad tym Mavenem. W Netbeans IDE kompresja jest wyłączona, a mimo to nie widzę różnicy. Jako, że Maven jest pisany specjalnie pod Javę, to sprawdzam na nim czy będzie lepiej.

0

Możesz też ręcznie rozpakować JARa i złożyć go ponownie np ZIPem z wyłączoną kompresją.

0

Dzięki za pomoc Jarekczek oraz Wibowit :) Niestety efektów wam nie przedstawię, ponieważ dostałem grubszy temat do napisania i nie mam za bardzo czasu teraz tego testować. Nie ma chyba sensu trzymać tego wątku otwartego, chociaż by ze względu na tak głęboką analizę wykonaną przez Wibowita pokazującą na czym może polegać problem :)

Także temat można zakończyć.

1 użytkowników online, w tym zalogowanych: 0, gości: 1