Author
others
View
9
Download
0
Embed Size (px)
1
POLITECHNIKA ŚLĄSKA
WYDZIAŁ INŻYNIERII MATERIAŁOWEJ I METALURGII
Edukacja Techniczno Informatyczna
Studia dzienne
Praca dyplomowa inżynierska
Tomasz Babiarz
PORÓWNANIE KOMPILATORÓW DLA ŚRODOWISKA JAVA
COMPARISON OF JAVA COMPILERS
Kierujący pracą:
dr inż. Marcin Blachnik
Katowice, marzec 2010r.
2
Spis treści
1. Wstęp .................................................................................................. 3
1.1 Cel pracy .......................................................................................... 4
1.2 Wersje Javy ..................................................................................... 5
1.3 Tworzenie programu w Javie ............................................................ 6
2. Wirtualna maszyny Javy ....................................................................... 7
3. Kompilatory ........................................................................................... 9
3.1 Kompilacja w Javie ......................................................................... 14
3.2 Javac ............................................................................................. 19
3.3 Jikes .............................................................................................. 22
3.4 GCJ ............................................................................................... 22
4. Część praktyczna ................................................................................ 24
4.1 Wyniki testów ................................................................................. 31
4.2 Wykresy ......................................................................................... 32
4.3 Wnioski .......................................................................................... 35
5. Podsumowanie .................................................................................... 36
6. Literatura............................................................................................. 37
7. Spis rysunków ..................................................................................... 38
8. Spis tabel ............................................................................................ 39
9. Spis wykresów .................................................................................... 40
3
1. Wstęp
Początki JAVY sięgają 1991, kiedy to James Gosling wraz z grupką
inżynierów rozpoczął prace nad niewielkim językiem komputerowym, który
znalazłby zastosowanie w niewielkich urządzeniach elektronicznych. Z powodu , iż
urządzenia te nie posiadały za wiele pamięci, język musiał być mały a przy tym
generować prosty kod maszynowy. Ponieważ producenci sprzętu elektronicznego
montowali w swoich produktach rożne procesory, ważne było aby język nie był
zależny od żadnej konkretnej architektury, a tym samym był przenośny. Projekt
ten początkowo został nazwany Green [1].
Projektanci oparli swój język na C++ czerpiąc z niego to, co najlepsze
wprowadzając jednocześnie dużo prostszą składnie. Gotowy język ochrzczono
nazwą Oak (ang. dąb). Ta nazwa nie zakorzeniła się na długo gdyż, jak się
okazało była już zajęta przez inny język programowania, więc została zmieniona
na Java. Swój debiut na scenie internetowej Java miała pod koniec 1995 roku od
razu podbijając serca użytkowników. Za zadanie postawiono jej aby stała się
uniwersalnym łącznikiem miedzy użytkownikami a informacjami, bez zależności,
czy informacja pochodzi z serwerów stron internetowych, baz danych czy innego
miejsca. Jak się okazało Java doskonale spełnia pokładane w niej nadzieje, co
sprawiło że uzyskała akceptacje wszystkich największych firm oprócz
Microsoftu [2]. Java posiada wbudowany system ochrony i bezpieczeństwa, który
zapewnia komfort użytkownikom i programistom, jak i również wbudowane
biblioteki uproszczające łączność między bazami danych, programowanie
sieciowe. Jedną z najważniejszy zmian jaka została wprowadzona w porównaniu
do poprzedników, jest to, że kod źródłowy programu jest kompilowany do kodu
pośredniego. Sprawia to, że skompilowany kod nie jest programem, który można
od razu uruchomić lecz powstaje tzw. Beta-kod interpretowany przez
zainstalowaną na dowolnej stacji roboczej maszynie wirtualnej. Takie rozwiązanie
sprawia, że powstały kod jest niezależny od konfiguracji sprzętowych
i systemowych [2].
4
Rysunek 1. Wieloplatformowość Javy
1.1 Cel pracy
Nie od dziś wiadomo, że Java jest jednym z najpopularniejszych języków
programowania. Popularność Javy sprawiła, że jest dla niej dostępny obok
oficjalnego kompilatora producenta Javy, firmy Sun, szereg innych rozwiązań.
Spośród dostępnych kompilatorów dla języka Java, dwa z nich produkowane są
przez jedne z największych firm udostępniających oprogramowanie: Sun
Microsystems i IBM. Są to Javac i Jikes, które konkurują z tworzonym przez
projekt GNU kompilatorem GCJ. Wszystkie wyżej wymienione programy są do
pobrania bezpłatnie ze stron ich producentów. Celem pracy będzie przedstawienie
i porównanie tych kompilatorów na podstawie pomiaru czasu wykonywania się
5
kodu maszynowego powstałego podczas kompilacji. Kod uruchamiany będzie na
najpopularniejszej obecnie wśród maszyn wirtualnych, wirtualnej maszynie Javy.
Dla porównania wyników, testy zostaną przeprowadzone pod dwoma systemami
operacyjnymi zainstalowanymi na programie wirtualizującym stacje roboczą –
VirtualBox firmy Sun Microsystems. Wyżej wymienionymi systemami będą
Microsoft Windows XP z Service Pack’iem 2 oraz Ubuntu 10.4. Programy, którymi
zostaną przetestowane kompilatory, będą korzystać z najpopularniejszych funkcji
wykorzystywanych przez programistów. W celu wyeliminowania ewentualnych
błędów pomiarowych całość testów zostanie powtórzona dziesięciokrotnie,
a wszystkie wyniki uśrednione.
1.2 Wersje Javy
Do tej pory firma Sun Microsystems wydała już kilka wersji Javy, którymi są :
1.0x – pierwsza uboga i niedopracowana wersja Javy
1.1x – wprowadzenie: nowego modelu zdarzeń, Swing, JavaBeans
1.2x – zmiana nazwy interfejsu programistycznego z JDK (Java Development Kit)
na Java 2 SDK (Software Development Kit), integracja obecnych rozwiązań
platforma Java 2 – posiadająca trzy niezależne edycje:
standardowa J2SE (Java 2 Standard Edition)
biznesowa J2EE (Java 2 Enterprise Edition)
mikro J2ME (Java 2 Micro Edition)
Java 6 – obecna wersja platformy Javy dostępna w trzech wersjach:
Obecnie dostępne wersje to:
JSE 6.0 (Java Standard Edition) - środowisko podstawowe do
standardowych zastosowań komputerów osobistych i serverów
6
JEE 6.0 (Java 2 Enterprise Edition) – środowisko do tworzenia
zaawansowanych i rozbudowanych aplikacji biznesowych, stworzone na
bazie JSE
JME 3.0 (Java 2 Mobility Edition) – środowisko do tworzenie aplikacji do
urządzeń przenośnych o ograniczonych zasobach np. telefony komórkowe,
palmtopy[3][4]
1.3 Tworzenie programu w Javie
Do stworzenia najprostszego programu w Javie nie jest potrzebny żaden
specjalnie dedykowany program. Wystarczy dowolny edytor tekstu np. notatnik.
Dla wszystkich programów postępujemy według schematu:
Stworzenie pliku źródłowego zawierającego klasę o takiej samej nazwie jak
nazwa pliku
Zapisanie kodu w pliku z rozszerzeniu *.java
Kompilacja pliku z kodem źródłowym o rozszerzeniu *.java do pliku o
rozszerzeniu *.class
Uruchomienie w środowisku interpretatora Beta-kodu
Pisanie programów w notatniku nie jest jednak komfortowe, gdyż istnieje duże
prawdopodobieństwo popełnienia błędu, co uniemożliwi kompilacje i późniejsze
uruchomienie programu. Dlatego najlepiej korzystać z jakiegoś środow iska
programistycznego, które sprawdzi czy nie popełniliśmy blędów np. „literówek”,
oraz w znaczący sposób ułatwi programowanie. Najpopularniejszymi
środowiskami wśród programistów są Eclipse i NetBeans. Oba programy są
darmowe i można je ściągnąć ze stron ich producentów:
http://www.eclipse.org/
http://netbeans.org/
7
2. Wirtualna maszyny Javy
Wirtualna maszyna jest to system uruchomieniowy dla programów, którego
charakterystyczną cechą jest niezależność od platformy, na której jest
zainstalowany. Obecnie najpopularniejszą maszyną wirtualna jest JVM firmy Sun
Microsystems. Istnieje także kilka innych wirtualnych maszyn, produkowanych
przez tak znane i uznane firmy jak IBM czy Oracle. Maszyny wirtualne wyżej
wymienionych firm nie znalazły jednak zbyt dużej popularności wśród zwykłych
użytkowników.
Firma SUN Microsystems dostarcza bezpłatnie oprogramowanie Java
w dwóch wersjach Java SE Development Kit 6u18 oraz Java SE Runtime
Environment 6u18. Środowisko RE zawierający podstawowy pakiet programów
pozwalających na ich uruchomienie. Wirtualna maszyna Javy posiada wsparcie
dla wszystkich wersji Windows’a oraz dla różnych dystrybucji Linuxa i Solarisa.
Java Software Development Kit jest zestawem narzędzi przeznaczonym dla
programistów w jego skład wchodzą:
narzędzia do budowania, kompilacji i uruchamiania programu,
narzędzia do dokumentowania i archiwizacji programów,
pakiety klas standardowych
przykładowe aplety i aplikacje.
Zainstalowany pakiet SDK zawiera min. katalogi:
bin zawierający programy usługowe:
javac – kompilator,
java - interpreter,
appletviewer – przeglądarka apletów,
javadoc - generator dokumentacji,
jdb – debuger,
jar – narzędzie do tworzenia archiwów,
8
jre zawierający środowisko uruchomieniowe, maszynę wirtualną - Javy,
bibliotekę klas,
lib zawierający dodatkowe biblioteki klas,
demo zawierający aplety i aplikacje demonstracyjne,
src.zip kody źródłowe klas standardowych,
docs zawierający znormalizowana dokumentacja pakietu w postaci stron
HTML. [2]
9
3. Kompilatory
Kompilator najprościej możemy opisać jako program, który czyta kod
napisany w jednym języku (języku źródłowym) i tłumaczy go na program
równoważny (język wynikowy). Bardzo ważnym elementem procesu translacji jest
zgłaszanie i wyświetlanie użytkownikowi komunikatów o ewentualnych błędach
w programie źródłowym.
Rysunek 2. Ogólny schemat kompilatora
Analizując ilość dostępnych kompilatorów początkowo możemy zostać
przytłoczeni. Obecnie istnieją tysiące języków źródłowych, licząc od tradycyjnych
języków programowania Java, Pascal, rodzina języków C, aż po
wyspecjalizowane języki, stosowane do bardzo różnych zastosowań komputera.
W takim samym stopniu zróżnicowane są języki wynikowe, którymi mogą być: inny
język programowania albo kod maszynowy dowolnego urządzenia
elektronicznego (od procesor mikrofalówki po procesor komputera). Zazwyczaj
kompilatory są sklasyfikowane w oparciu o cel ich przeznaczenia jak
i w zależności od ich konstrukcji. Kompilatory dzielą się na:
jednoprzebiegowe
wieloprzebiegowe
uruchomieniowe (ang. debugging)
typu załaduj i uruchom (ang. load-and-go)
10
Pomimo zróżnicowanych zadań kompilatorów, podstawowe ich funkcje
pozostają takie same.
Kompilatory działają w fazach, które następując kolejno po sobie
przekształcają program z jednej postaci na inną. Poniżej znajduje się schemat
pozwalający zobrazować to zagadnienie
Rysunek 3. Schemat blokowy kompilatora
11
Pierwsze trzy części składają się na część analizującą kompilatora. W jej
skład wchodzi :
analizator leksykalny
analizator składnikowy
analizator semantyczny
Pozostałe części kompilatora to:
generator kodu pośredniego
optymalizator kodu
generator kodu
Zarządzanie tablicą symboli i obłoga błędów współdziałają ze wszystkimi
elementami i zazwyczaj również nazywane fazami.
W celu ułatwienia rozrumienia zasady działania kompilatorów Javy, trzeba
przynajmniej znać sposób działania niektórych z wyżej wymienionych elementów
składowych uogólnionego kompilatora.
Jak już wiemy praca kompilatora przebiega w fazach, a sam kompilator
zbudowany jest z kilku części. Pierwsze fazy kompilatora wchodzą w skład analizy
źródłowej programu. W wcześniej przedstawionym modelu kompilatora program
źródłowy w pierwszej kolejności trafia do analizatora leksykalnego. Jego głównym
zadaniem jest czytanie znaków z wejścia oraz produkcja symboli leksykalnych do
analizy składniowej.
12
Rysunek 4. Wymiana danych miedzy analizatorem leksykalnym i składniowym
Powyższy schemat obrazuje oddziaływanie między analizatorem
leksykalnym a analizatorem składniowym. Współpraca między nimi polega na tym,
że po otrzymaniu polecenia „daj następny symbol” od analizatora składniowego,
analizator leksykalny czyta wszystkie znaki z wejścia, aż zidentyfikuje kolejny
symbol leksykalny. Analizator leksykalny będąc częścią kompilatora czytającą
tekst źródłowy również może wykonywać pewne zadania związane z interfejsem
użytkownika. Jednym z nich może być omijanie z pliku wejściowego komentarzy
i białych znaków czyli spacji, tabulacji i nowych wierszy. Kolejnym zadaniem jest
dopasowywanie wyświetlanych komunikatów do określonego miejsca w kodzie
źródłowym programu. Polega to na śledzeniu przez analizator leksykalny
wczytanych wierszy z wejścia aby kompilator podczas wystąpienia ewentualnego
błędu mógł razem z komunikatem o danym błędzie wyświetlić numer wiersza,
w którym on występuje. Zdarza się, że analizatory leksykalne podzielone są na
dwie fazy, pierwszą z nich jest skanowanie, a drugą analiza leksykalna. Skaner
odpowiedzialny jest za wykonywanie prostych zadań, a analizator leksykalny
zajmuje się tymi bardziej skompilowanymi.
Kolejną istotnym elementem kompilatora, jest analizator składniowy, który
otrzymuje ciąg znaków leksykalnych od analizatora leksykalnego, a następnie
13
sprawdza czy dany ciąg może zostać wygenerowany przez gramatykę dla języka
źródłowego. Analizator składniowy ma również jeszcze jedno bardzo istotne
zadanie, mianowicie ma zgłaszać występowanie błędów składniowych. Jest to
bezpośrednio związane z tym, że nie realne jest aby kompilator przetwarzał tylko
poprawne programy, gdyż programiści to też ludzie i zdarzają się im błędy. Gdyby
jednak dało się ich całkowicie unikać, wtedy projekt i stworzenie kompilatora
bardzo by się uprościły. Niestety tak nie jest i często zdarza się, że programiści
piszą niepoprawne programy, więc dobry kompilator powinien pomóc
programiście w zlokalizowaniu błędu i jego usunięciu. To spowodowało, że
obsługa błędów w analizatorze składniowym ma postawione proste cele:
zgłaszać obecność błędu, w jasny sposób
w razie wykrycia błędu, szybo wracać do analizy składniowej w celu
wykrycia kolejnych błędów
nie spowalniać w sposób znaczący przetwarzania poprawnych programów.
Jedną z ważniejszych funkcji kompilatora jest zapamiętywanie
identyfikatorów, które są używane w programie źródłowym oraz zbieranie
informacji o ich różnych atrybutach. Atrybuty mogą dostarczać informacji na temat
ilości zajętej pamięci przez identyfikator, o jego typie, zasięgu (jego dostępność
i widoczność w programie). Także w przypadku nazw atrybuty podają liczbę i typy
argumentów, metody przekazywania każdego argumentu oraz typ wyniku o ile ta
metoda zwraca wynik. Tablica symboli jest zbudowana ze zbioru rekordów
z atrybutami dla wszystkich identyfikatorów. Struktura to za zadanie ma
umożliwiać szybkie znalezienie rekordu dla każdego identyfikatora oraz szybkie
zapisywanie i odczytywanie danych z rekordu. Identyfikator napotkany przez
analizę leksykalną dodawany jest do tablicy symboli. Reszta faz wstawia do
tablicy informacje o identyfikatorach, aby w kolejnych etapach kompilacji
wykorzystać je do różnych celów takich jak; sprawdzenie podczas analizy
semantycznej i generacji kodu czy identyfikatory używane są poprawnie oraz
wygenerowania poprawnych operacji na nich działających. Dokładne informacje
na temat przydzielonej pamięci identyfikatorom są niezbędne do działania
generatora kodu [5].
14
Ostatnim etapem pracy kompilatora Javy jest generowanie kodu
pośredniego (inne kompilatory np. języka C posiadają jeszcze dodatkowo
generatory kodu wynikowego). Efektem jego pracy jest stworzenie tzw. Beta –
kodu, które zostanie opisane w dalszej części pracy
3.1 Kompilacja w Javie
Znając ogólne zasady działania kompilatorów możemy przyjrzeć się jak
wygląda kompilacja programu napisanego w konkretnym języku, którym w naszym
przypadku jest Java. Jak wiemy proces kompilacji jest niezbędny do uruchomienia
programu napisanego w Javie. Polega on na zamianie kodu źródłowego programu
na kod bajtowy, a następnie bezpośrednio przed jego wykonaniem na kod
maszynowy, co dobrze obrazuje poniższy schemat
Rysunek 5. Ogólny schemat kompilacji programu napisanego w Javie
15
Podczas kompilacji plików z kodem Javy (pliki *.java) tworzone są instrukcje
kodu bajtowego (pliki *.class) które zostają wykonane przez interpreter kodu
bajtowego. Jednak dobrze by było przyjrzeć się bliżej temu procesowi gdyż, co
prawda cała kompilacja od strony praktycznej sprowadza się do wpisania
polecenia w linii komend i jest przeprowadzana automatycznie przez kompilator,
jednak obserwacja efektów jego działania należy już do użytkownika. Programista
tworzący programy w Javie powinien wiedzieć jaka metodą powstaje kod
maszynowy dla danej platformy, czyli innymi słowy jaka jest droga pomiędzy
kodem źródłowym a kodem wykonywalnym. Musimy sobie jednak uświadomić, że
użytkownik nie zawsze ma dostęp do kodu wykonywalnego na daną platformę,
ponieważ taki kod może być generowany dopiero w trakcie uruchamiania
programu. Aby wyjaśnić cały mechanizm generowania kodu maszynowego
programu stworzonego w Javie dla danej platformy trzeba przedstawić kilka
podstawowych zagadnień z nim związanych.
Kod maszynowy jest ciągiem liczb interpretowanych przez komputer,
a konkretnie jego procesor w celu wywołania pożądanego efektu. Używanie serii
liczb w celu wykonania określonego zadania nie jest jednak zbyt efektywne, a dla
samego użytkownika niekomfortowe. Dlatego też stworzony został prosty język,
który posiada proste instrukcje wywoływane z odpowiednimi parametrami lub
wartościami. Tak przygotowany kod tłumaczony jest przez komputer na kod
maszynowy. Opisywany wyżej język nosi nazwę assembler, a w przypadku Javy
kod źródłowy kompilowany jest do pseudo-assamblera nazwanego b-kodem.
Instrukcje napisane w B-kodzie, tłumaczone są na odpowiadające im ciągi liczb
(kod maszynowy) w trakcie uruchomienia programu (klasy). W praktyce oznacza
to, że jedna linia kodu napisanego w assamblerze zwykle generuje jedną linie
kodu maszynowego. Dla porównania assemblera i pseudo-assamblera, poniżej
przedstawiony jest fragment kodu w assemblerze, który wygląda następująco:
add eax, edx
mov ecx, eax
16
Natomiast te same instrukcje, w pseudo-assamblerze wyglądają zupełnie
inaczej [6]:
0 iload_1
1 iload_2
2 iadd
3 istore_3
W celu ułatwienia życia programistom stworzone zostały języki tzw.
wysokiego rzędu jak np. C, C++, Pascal w których pojedyncza linia komend może
być zamieniona na kilka linii kodu maszynowego dla danej platformy. Cały proces
konwertowania kodu źródłowego języka wysokiego rzędu do kodu wykonywalnego
nazwano kompilacją statyczną i w przypadku technologii Java sprowadza się do
kompilacji kodu źródłowego do B-kodu. Proces kompilacji składa się z siedmiu
podstawowych procesów wymienionych w poprzednim rozdziale. tłumaczenie
kodu źródłowego
generacja kodu maszynowego/B-kodu
optymalizacja kodu maszynowego/B-kodu
Pierwszym etapem jest tłumaczenie kodu źródłowego, które polega na
wydobyciu z tekstu poszczególnych elementów języka np. „if”, „)”, „class”;
a w kolejnym etapie połączenie w wyrażenia języka. W momencie gdy zostaną
napotkane elementy niezrozumiałe dla danego języka, które nie są zgodne z jego
wzorcami, to kompilator przerywa pracę i zwraca użytkownikowi błąd, co
zazwyczaj niesie za sobą konieczność skorygowania elementów powodujących
konflikty. Jeżeli tłumaczenie całego kodu przebiegnie pomyślnie to następuje
konwersja do B-kodu, który w kolejnym kroku zostaje zoptymalizowany. Celem
procesu optymalizacji jest zmniejszenie rozmiarów kodu oraz poprawa szybkości
jego działania. Wyrażenia języka są bardzo często kompilowane i tworzą biblioteki
czyli gotowe zbiory kodów, które możemy wykorzystać podczas konstrukcji
własnego programu. Podczas kompilacji programu korzystamy więc z gotowych
wcześniej skompilowanych kodów. Zbiór bibliotek stanowi niezbędną część
zasobów danego języka programowania, jednak mogą być też wytwarzane przez
użytkowników środowiska tworzenia programów. Krótko podsumowując
17
kompilacja statyczna jest procesem konwertowania kodu źródłowego na kod
pośredni.
Kod pośredni (B-kod) podczas uruchomienia aplikacji jest konwertowany na
kod maszynowy. Do tego celu służą różne techniki. Jedną z nich jest
interpretowanie, które polega na cyklicznym pobieraniu instrukcji języka,
tłumaczeniu ich na kod maszynowy, generacji i wykonywaniu. Przykładowymi
interpreterami są powłoki (ang. shell) systemów operacyjnych przykładem, których
są DOS, bash, csh. Największą wada interpretowanie jest jednak to, że nie można
wykonać optymalizacji kodu maszynowego, gdyż nie jest on dostępny.
Jeżeli chodzi o Javę interpretowanie kodu wygląda jednak trochę inaczej.
Związane jest to z jedną z głównych cech, która była jednym z powodów jej
stworzenia. Mowa oczywiście o przenośności kodu. Założenie, że kod może być
uruchamiany na każdej platformie sprzętowej spowodowało konieczność
stworzenia takich interpretatorów, które umożliwiałyby efektywną konwersję tego
samego kodu źródłowego na kod wykonywujący się tak samo na rożnych
platformach sprzętowych. Proces interpretacji zajmuje stosunkowo bardzo dużo
czasu, dlatego konieczne były pewne modyfikacje przyśpieszające go, aby
uruchamiany program był odpowiednio efektywny. Zabiegi te znacznie poprawi ły
wydajność programów napisanych w Javie. Proces ten przebiega dwuetapowo.
Pierwszym etapem opisanym wyżej jest kompilacja kodu źródłowego na kod
pośredniego, który nosi również nazwę kodu maszyny wirtualnej. Kod pośredni
jest efektem translacji kodu źródłowego zgodnie z architekturą języka Java. W
wyniku translacji powstaje zestaw bajtów, który przed uruchomieniem musi być
przekonwertowany na kod maszynowy zgodny z architekturą platformy
sprzętowej, na której jest uruchamiany. W języku używanym przez programistów
zestaw bajtów jest również nazywany Beta-kodem lub B-kodem. Kod wynikowy
konkretnej platformy powstaje w wyniku interpretowania Beta-kodu przez
interpreter maszyny wirtualnej.
Potrzeba jeszcze większego przyśpieszenia procesu kompilacji
spowodowała, że zamiast interpretatora B-kodu opracowano różne kompilatory
dynamiczne. Ich działanie oparte jest na kompilacji w locie B-kodu do kodu
wykonywalnego dla danej maszyny. Otrzymany w ten sposób kod wykonywalny
18
umieszczany jest w pamięci komputera, tym samym oznacza to, że nie jest
zapisywany w postaci pliku na dysku. Z tego wynika, że kod po skończeniu
działania programu jest po prostu niedostępny. [2] Ta metoda kompilacji została
nazwana kompilacją JIT – Just-In-Time (kompilacja w locie). Poprawa wydajności
w stosunku do interpreterów kodu polega na buforowaniu bloków kodu, a nie
interpretowaniu każdej linii po kolei za każdym razem gdy program jest otwierany.
Kod źródłowy skompilowany do Beta-kodu nie jest przypisany do konkretnego
komputera co sprawia, że może on być przenoszony miedzy rożnymi
architekturami. Taki kod może być interpretowany lub uruchamiany na maszynie
wirtualnej. Mimo, iż kompilacja JIT ma za zadanie przyśpieszenie wykonywania
programów zwykle powoduje nieznacznie opóźnienie w początkowym etapie
realizacji kodu. Wpływa na to czas potrzebny do załadowania i skompilowania
Beta-kodu. Kod wykonuje się tym lepiej im więcej razy zostanie wykonany.[7]
Receptą na potrzebę rozgrzewania się wyeliminowano przez stworzenie
kompilatora AOT (ang. Ahead-of-time), który przed uruchomieniem kodu jeszcze
raz go przekompilowuje wprowadzając przy tym zmiany, które poprawiają
wydajność kodu.
W Javie oprócz błędów występują również wyjątki. Wyjątki są to określone
sytuacje konfliktowe spowodowane przez niewłaściwe funkcjonowanie klas
i metod. Przykładem wyjątków mogą być: dzielenie przez zero, brak pliku
o podanej ścieżce, brak hosta o podanym adresie, czy też brak klasy. Wystąpienie
wyjątku nie powoduje zazwyczaj przerwania pracy programu. Każdy wyjątek
związany jest bezpośrednio z określoną klasą i jej metodami. Przykładowe wyjątki
i ich klasy to:
w pakiecie java.lang.*:
ClassNotFoundExeption – brak klasy,
ArrayIndexOutOFBoundsExeption – przekroczenie zakresu tablicy,
ArithmeticExeption – wyjątek operacji arytmetycznych np. dzielenie
przez zero,
w pakiecie java.io.*;
19
EOFExeption –koniec pliku,
FileNotFoundExeption – brak pliku,
InterruptedIOExeption – przerwanie operacji we/wy.
Przykładowe błedy:
OutOfMemoryError – bład braku pamięci,
NonSuchMethodError – bład braku danej metody w klasie,
NonSuchFieldError – bład braku danego pola w klasie [2].
3.2 Javac
Javac jest podstawowym narzędziem wchodzącym w skład JVM (Java
Virtual Machine) służącym do kompilacji programów. Jego najnowsza wersja ma
oznaczenie 1.6.0_17.
Pierwszym krokiem do rozpoczęcia kompilacji programów jest instalacja
pakietu Java SE Development Kit 6u18 – dostępny do pobrania
z http://java.sun.com/javase/downloads/widget/jdk6.jsp. Aby rozpocząć prace
z kompilatorem Javac należy rozpocząć od dodania zmiennej środowiskowej
PATH do katalogu bin zawierającego kompilator Javac i interpreter Java.
20
Rysunek 6. Dodawanie zmiennej środowiskowej
Po tej czynności można bez problemu korzystać z kompilatora. Jego
uruchomienie odbywa się z linii komend. Wprowadzona zmienna środowiskowa
pozwala nam na nie podawanie całej ścieżki dostępu do kompilatora.
Wystarczające jest przejście do katalogu z plikiem programu i wprowadzenie
jedynie polecenia javac nazwa_pliku.java. Jeżeli program został prawidłowo
skompilowany konsola powinna nie wyświetlić żadnego błędu a jedynie nową linie
komend. Gotowy program uruchamiamy poleceniem java nazwa_pliku. Ważne
aby nie dodawać rozszeżenia class, ponieważ wtedy program nie uruchomi się.
21
Rysunek 7. Poprawnie skompilowany program
Javac zawiera duża ilość opcji i ustawień kompilacji są to min.:
-g generuje wszystkie informacje debugowania
-g:none generuje informacje bez błędów
-g:{lines,vars,source} generuje tylko niektóre informacje
-nowarn nie wyświetla ostrzeżeń
-verbose wyświetla informacje na temat pracy kompilatora
-classpath <path> określa ścieżkę dodatkowych klas użytkownika
-cp <path> j.w.
-sourcepath <path> lokalizacja źródła plików wejściowych
-extdirs <dirs> zmienia lokalizację zainstalowanych rozszerzeń
-d <directory> określ, gdzie umieścić wygenerowane pliki class
-s <directory> określ, gdzie umieścić wygenerowane pliki
źródłowe
-target <release> generowanie plików class dla konkretnych wersji
VM
-version informacje o wersji
-help wyświetla standardowe opcje
-X wyświetla niestandardowe opcje [8]
22
3.3 Jikes
Jikes jest produktem IBM’a jednak projekt ten nie jest od kilku lat rozwijany.
Jego ewaluacja stanęła na wersji 1.22. Jikes jest tylko kompilatorem, nie wchodzi
w skład żadnej maszyny wirtualnej przez to jego rozmiary są niewielkie. Program
można z łatwością pobrać z http://sourceforge.net/projects/jikes/files/ wybierając
wersje odpowiadającą naszemu systemowi operacyjnemu. Kompilator jest
dostępny w wersjach dla takich platform jak: MS Windows, platformy Linux’ owe
oraz MAC OS.
Aby rozpocząć prace z kompilatorem Jikes nie potrzebna jest nam jego
instalacja, jednak kompilacja kodu źródłowego wymaga zainstalowanej JVM. Jest
to konieczne aby wskazać kompilatorowi położenie podstawowych klas. Dlatego
kompilator do uruchomienia procesu kompilacji potrzebuje wskazania lokalizacji
pliku rt.jar znajdującego się katalogu lib pakietu JRE. Gotowe polecenie wpisane
w konsole wygląda następująco: jikes –bootclasspath sciazka_do_rt.jar
plik_kompilowany.java. Podobnie jak w przypadku kompilatora Javac jeżeli plik
zostal poprawnie skompilowany konsola nie wyświetla żadnych komunikatów
i zapisuje plik z rozszerzeniem *.class, który bez problemu można uruchomić
poleceniem java plik_kompilowany [9].
3.4 GCJ
Ostatni z przedstawionych kompilatorów jest najczęściej stosowany przez
użytkowników platform UNIX’owych. GCJ czyli kompilator projektu GNU dla
języka Java jest (GNU Compiler for Java) jest częścią GCC (GNU Compiler
Collection) w skład którego wchodzi kompilator jak, środowisko uruchomieniowe
podstawowe biblioteki i narzędzia dla języka Java. Jedną z głównych cech tego
kompilatora jest to, że ułatwia integrację z innymi językami obsługiwanymi przez
pakiet GCC.
23
Instalacja GCJ polega na wpisaniu w konsolę systemową Ubuntu polecenia
sudo apt-get install gcj. Gdy oprogramowanie zostanie pobrane i zainstalowane
w naszym systemie, możemy rozpocząć kompilacje programów. Skompilowanie
programu polega na przejściu do katalogu zawierającego przygotowany wcześniej
program i wpisaniu polecenia gcj –C nazwa_pliku.java. Podobnie jak
w poprzednich przypadkach, jeżeli program został prawidłowo skompilowany to
konsola nie wyświetla żadnych błędów. Trzeba jednak pamiętać że GCJ jest
bardziej wrażliwy na błędy występujące w kodzie. Przykładem może być sytuacja
w której wykonywane są pewne obliczenia ale nie jest zwracana żadna wartość.
Wtedy co prawda kompilator skompiluje program jednak, wyświetlany błąd
zasygnalizuje nam, o zaistniałym problemie [10].
24
4. Część praktyczna
Zgodnie z zdefiniowanym celem pracy, część praktyczna będzie polegać
na porównaniu szybkości wykonywania skompilowanego wyżej opisanymi
kompilatorami kodu. Proces ten zostanie zrealizowany przez trzy kompilatory:
Javac, Jikes i GCJ. Wszystkie skompilowane pliki zostaną uruchomione na
maszynie wirtualnej Javy (JVM).
W skład zestawu testującego wchodzi laptop HP Pavilon dv5 o parametrach:
Procesor: Intel Core 2 Duo P7350 2GHz
Pamięć: 3GB
Karta graficzna NVIDIA GeForce 9600M GT (512 MB)
Dysk twardy: 300GB
System operacyjny: Windows Vista Ultimate
Wszystkie testy zostały przeprowadzone na wirtualnie zainstalowanych
systemach, którymi były:
Windows XP z SP@
Ubuntu 10.4
Do uruchomienia wirtualnych systemów został użyty darmowy program
VirtualBox w wersji 3.1.2 [11]. Dla lepszego porównania wyników systemy
zainstalowane są na maszynach wirtualnych o takich samych ustawieniach, które
przedstawiają się następująco:
Pamięć podstawowa: 1GB
Partycja systemowa: 8GB
Pamięć video: 16MB
Do testów zostało użytych 8 programów. Programy w istocie są bardzo
proste i sprawdzają wydajność podstawowych klas. Każdy z kodów jest
zbudowany według tego samego schematu, czyli pętli wykonywanej określoną
ilość razy oraz metody, która mierzy czas wykonywania programu. Każdy program
25
został uruchomiony 10 razy, a wszystkie wyniki uśrednione. Poszczególne
programy mierzyły:
Program 1 – wydajność typowych operacji liczbowych takich jak
dodawanie, odejmowanie, mnożenie i dzielenie, kod programu poniżej:
[12][13]
public static double RunTest(int Wielkoscpetli) {
long x = 10;
double y = 0;
double z = 0;
for (int i = 0; i < Wielkoscpetli; i++) {
x = x + 3;
y = (double) x / 7;
z = y * 5;
x -= 3;
}
return z;
}
Program 2 – wydajność operacji na stringach używając metody do
porównania ciągu znaków, kod programu poniżej: [12][13]
public static boolean RunTest(int Wielkoscpetli) {
String val0_2 = "hm";
String val1_2 = "hm";
String val2_2 = "Hm";
String val3_2 = "kg";
String val4_2 = "hm1";
String val0_4 = "Hara";
String val1_4 = "Hara";
String val2_4 = "HaRa";
String val3_4 = "xFdW";
String val4_4 = "Harai";
String val0_8 = "Haralamb";
String val1_8 = "Haralamb";
String val2_8 = "haRaLAmb";
String val3_8 = "AmbzJ3pQ";
String val4_8 = "Haralambxq";
String val0_16 = "Haralambos Marma";
String val1_16 = "Haralambos Marma";
String val2_16 = "hAraLamBos maRmA";
26
String val3_16 = "gtswq*skdn-2iosc";
String val4_16 = "Haralambos Marmanis";
boolean test = false;
for (int i = 0; i < Wielkoscpetli; i++) {
if (val0_2.equals(val1_2)
| val0_2.equals(val2_2)
| val0_2.equals(val3_2)
| val0_2.equals(val4_2)) {
test = !test;
}
if (val0_4.equals(val1_4)
| val0_4.equals(val2_4)
| val0_4.equals(val3_4)
| val0_4.equals(val4_4)) {
test = !test;
}
if (val0_8.equals(val1_8)
| val0_8.equals(val2_8)
| val0_8.equals(val3_8)
| val0_8.equals(val4_8)) {
test = !test;
}
if (val0_16.equals(val1_16)
| val0_16.equals(val2_16)
| val0_16.equals(val3_16)
| val0_16.equals(val4_16)) {
test = !test;
}
}
return test;
}
Program 3 - konfiguracja programu jest taka sama jak programu 2, ale
program używa metody equalsIgnoreCase, która przy porównaniu ignoruje
wielkość znaków. Poniżej fragment kodu, którym rożni się od poprzedniego
programu: [12][13]
if (val0_2.equalsIgnoreCase(val1_2)
| val0_2.equalsIgnoreCase(val2_2)
| val0_2.equalsIgnoreCase(val3_2)
| val0_2.equalsIgnoreCase(val4_2))
27
Program 4 - konfiguracja programu jest taka sama jak programu 2, ale
program używa metody compareTo. Poniżej fragment kodu, którym rożni
się od poprzedniego programu: [12][13]
if (val0_2.compareTo(val1_2) == 0 | val0_2.compareTo(val2_2) == 0
| val0_2.compareTo(val3_2) == 0
| val0_2.compareTo(val4_2) == 0)
Program 5 – wydajność niektórych, najczęściej używanych funkcji
matematycznych klasy Java.lang.Math (podnoszenie do potęgi,
pierwiastkowanie, cosinus kąta, tworzenie zbioru liczb losowych), kod
programu poniżej: [12][13]
public static double RunTest(int Wielkoscpetli) {
double pi = java.lang.Math.PI;
double e = java.lang.Math.E;
double x = 0;
double y = 0;
long n1 = 0;
long n2 = 0;
double sign = 0;
double random = 0;
long randomL = 0;
for (int i = 0; i < Wielkoscpetli; i++) {
random = Math.random();
sign = Math.cos(100 * random);
random = (sign * random) * pi * e;
randomL = Math.round((double) random);
x = Math.abs(random);
n1 = Math.abs(randomL);
y = Math.exp(x);
y = Math.log(x);
n2 = Math.max(n1, n2);
y = Math.max(x, y);
x = Math.sqrt(y);
y = Math.pow(y, x);
}
return y;
}
28
Program 6 – wydajność optymalizacji if, kod programu poniżej: [12][14][15]
public static boolean RunTest(int size) {
boolean a = true, b = false, c = true, d = false;
int i, j;
for (i = 0; i <= size; i++) {
if (a | b) {
a = !c;
b = !d;
if (a | b) {
a = !c;
b = !d;
} else {
a = c;
b = d;
}
} else {
a = c;
b = d;
if (a | b) {
a = !c;
b = !d;
} else {
a = c;
b = d;
}
}
}
return b;
}
Program 7 – wydajność optymalizacji „martwego kodu”, kod programu
poniżej: [12][14][15]
public static boolean RunTest(int size) {
boolean a = true, b = false;
int i, j;
double x = 0.12, y = 0.13, z = 0.14;
for (i = 0; i <= size; i++) {
x += 0.0001;
z += 0.00012;
y += Math.log(Math.atan(Math.sqrt(x / z)));
}
b = Math.random() > 0.5;
for (i = 0; i <= size; i++) {
if (b) {
a = b;
} else {
a = !b;
}
}
return a;
}
29
Program 8 – wydajność upraszczania wyrażeń, kod programu poniżej:
[12][14][15]
public static int RunTest(int size) {
int[] a = new int[4];
int i, j;
for (i = 0; i <= size; i++) {
a[0] += i + 0 + i + 0;
a[1] += i * 0;
a[2] += i - i + 2 - 5 + 3;
a[3] += 1 + i + 1;
}
return a.hashCode();
}
30
Konsola po uruchomieniu programu wyświetla czas wykonywania się programu
Rysunek 8. Konsola, wyświetlenie czasów poszczególnych przebiegów
Jest to realizowane za pomocą wpisania w main kodu znajdującego się poniżej:
long startOfTest
long endOfTest;
int test;
startOfTest = System.currentTimeMillis();
test = Test1.RunTest(Wielkoscpetli);
endOfTest = System.currentTimeMillis();
// Raport
output.append("Test[1] (ms): " + (endOfTest - startOfTest));
output.append(" Wolna Pamiec : " + Runtime.getRuntime().freeMemory());
output.append(" Wynik obliczen˝: " + test + "\n");
//Nowy test
startOfTest = System.currentTimeMillis();
test = Test1.RunTest(Wielkoscpetli);
endOfTest = System.currentTimeMillis()
Program przy każdym uruchomieniu wykonywał 3 przebiegi, w celu
zaobserwowania ewentualnej poprawy jego działania (optymalizacji)
31
4.1 Wyniki testów
Tabela 1. Wyniki testów dla kompilatora Javac (czas w tabeli podany w ms)
Przebieg 1 Przebieg 2 Przebieg 3
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Program 1 3479 3728 3422 3644 3449 3586
Program 2 2546 2790 2551 2705 2413 2574
Program 3 12852 13874 12848 13706 12850 13420
Program 4 5768 6214 5744 6060 5670 5957
Program 5 1101 1248 1075 1142 1157 1124
Program 6 8641 9561 8681 9357 2008 2138
Program 7 3157 2378 3176 2271 2122 2239
Program 8 8842 10724 8903 9398 5311 5561
Tabela 2. Wyniki testów dla kompilatora Jikes (czas w tabeli podany w ms)
Przebieg 1 Przebieg 2 Przebieg 3
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Program 1 3478 3750 3475 3685 3413 3584
Program 2 2588 2851 2591 2740 2457 2593
Program 3 12624 13527 12604 13340 12602 13368
Program 4 5712 6175 5712 6019 5624 5919
Program 5 1108 1277 1082 1142 1159 1122
Program 6 5982 6484 5991 6344 2001 2171
Program 7 2172 2474 2166 2372 2113 2247
Program 8 8908 11227 10123 9821 5279 6467
Tabela 3. Wyniki testów dla kompilatora GCJ (czas w tabeli podany w ms)
Przebieg 1 Przebieg 2 Przebieg 3
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Program 1 3486 3827 3485 3795 3416 3679
Program 2 2250 2501 2230 2400 2108 2235
Program 3 12605 13638 12628 13370 12598 13553
Program 4 5712 6201 5712 6060 5624 5941
Program 5 1111 1277 1089 1156 1156 1131
Program 6 8663 9238 8678 9131 2023 2139
Program 7 2180 2495 2190 2399 2128 2243
Program 8 8917 10916 8935 9393 5873 5604
32
4.2 Wykresy
Wykres 1. Porównanie wydajności kompilatora Javac w systemach Windows XP i Ubuntu (na osi pionowej czas w ms)
Wykres 2. Porównanie wydajności kompilatora Jikes w systemach Windows XP i Ubuntu (na osi pionowej czas w ms)
0
10000
20000
30000
40000
50000
60000
Przebieg 1 Przebieg 1 Przebieg 2 Przebieg 2 Przebieg 3 Przebieg 3
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Program 8
Program 7
Program 6
Program 5
Program 4
Program 3
Program 2
Program 1
0
10000
20000
30000
40000
50000
60000
Przebieg 1 Przebieg 1 Przebieg 2 Przebieg 2 Przebieg 3 Przebieg 3
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Program 8
Program 7
Program 6
Program 5
Program 4
Program 3
Program 2
Program 1
33
Wykres 3. Porównanie wydajności kompilatora GCJ w systemach Windows XP i Ubuntu (na osi pionowej czas w ms)
Wykres 4. Porównanie wydajności kompilatorów dla przebiegu 1 w systemach Windows XP i Ubuntu (na osi pionowej czas w ms)
0
10000
20000
30000
40000
50000
60000
Przebieg 1 Przebieg 1 Przebieg 2 Przebieg 2 Przebieg 3 Przebieg 3
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Program 8
Program 7
Program 6
Program 5
Program 4
Program 3
Program 2
Program 1
0
10000
20000
30000
40000
50000
60000
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Javac Javac Jikes Jikes GCJ GCJ
Program 8
Program 7
Program 6
Program 5
Program 4
Program 3
Program 2
Program 1
34
Wykres 5. Porównanie wydajności kompilatorów dla przebiegu 2 w systemach Windows XP i Ubuntu (na osi pionowej czas w ms)
Wykres 6. Porównanie wydajności kompilatorów dla przebiegu 3 w systemach Windows XP i Ubuntu (na osi pionowej czas w ms)
0
10000
20000
30000
40000
50000
60000
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Javac Javac Jikes Jikes GCJ GCJ
Program 8
Program 7
Program 6
Program 5
Program 4
Program 3
Program 2
Program 1
0
5000
10000
15000
20000
25000
30000
35000
40000
WinXP Ubuntu WinXP Ubuntu WinXP Ubuntu
Javac Javac Jikes Jikes GCJ GCJ
program 8
program 7
program 6
program 5
program 4
program 3
program 2
program 1
35
4.3 Wnioski
Testy jednoznacznie pokazały, że każdy kod potrzebuje kilku przebiegów
do pełnego zoptymalizowania. W przypadku każdego testowanego kompilatora ta
reguła się sprawdza. Można to zaobserwować na wykresie 1, 2 i 3. Z wykresów
wynika również, że dystrybucja wirtualnej maszyny Javy działa o wiele szybciej
w systemie Windows XP niż w systemie opartym na Linuxie – Ubuntu.
Początkowo może to być zastanawiające (Linux jest z reguły wydajniejszym
systemem), ale po zastanowieniu można dojść do wniosku, że łatwo jest taka
sytuacje uargumentować. Pierwszym czynnikiem, który miał na to wpływ jest to,
że przeprowadzane testy odbywały się na systemach operacyjnych
zainstalowanych na maszynach wirtualnych. Systemem hostującym był system
Windows, a co za tym idzie komunikacja pomiędzy systemem gościem –
Windows, a systemem macierzystym, mogła przebiegać dużo efektywniej.
W przypadku gdy systemem goszczącym w systemie Windows był system
Ubuntu, komunikacja między nimi wymagała pewnych konwersji, więc wpłynęło to
znacząco na czas wykonywania się testowanych programów. Do interesujących
wniosków można również dojść analizując wykresy 3, 4, 6 bowiem okazuje się,
że najlepszym kompilatorem pod względem wydajności w systemie Windows jest
Jikes. Jego wynikowy kod działa szybciej od jego konkurentów przy każdym
przebiegu, bez względu na to czy kod jest już w pełni zoptymalizowany czy nie.
Drugie miejsce pod tym względem przypada kompilatorowi GCJ, na szarym końcu
jest Javac. Sytuacja zmienia się, jeżeli przeanalizujemy wydajność kompilatorów
w systemie Ubuntu. W pierwszych dwóch przebiegach najlepszym kompilatorem
okazuje się być Jikes. Zaskakujące jest, że kod skompilowany przez kompilator
Jikes w trzecim przebiegu jest znacznie wolniejszy od kodu najszybszego w tym
systemie kompilatora - Javac. Kolejnym nasuwającym się wnioskiem jest to, że
kod jest w pełni zoptymalizowany, gdy zostanie uruchomiony minimum 3 razy.
Doskonale można to zaobserwować po czasach pracy programu 6, które
w przypadku wszystkich kompilatorów przy trzecim przebiegu są kilkakrotnie
krótsze niż podczas pierwszego i drugiego przebiegu. Związane jest to z tym, że
kompilatory nie wykonują niepotrzebnie kilkakrotnie tego samego kodu, jeżeli
wcześniej został on już wykonany.
36
5. Podsumowanie
W pracy zostały przedstawione podstawowe założenia dynamicznie
rozwijającego się języka programowania, jakim jest niewątpliwie Java. Celem
pracy było zapoznanie się z zasadą działania i rodzajami dostępnych na rynku
oprogramowania komputerowego kompilatorów, a w części praktycznej
porównania ich pod względem wydajności, cel został zrealizowany. W porównaniu
wzięły udział 3 najbardziej popularne kompilatory: Javac, Jikes i GCJ. Okazało
się, że w systemie Windows bezkonkurencyjny okazał się Jikes, wyprodukowany
przez firmę IBM. Wygrał on pod każdym względem, jego kod wykonywał się
najszybciej podczas pierwszego, drugiego i trzeciego przebiegu. Drugie miejsce
zajął GCJ, a najgorszy okazał się kompilator Javac. Jednak w systemie Ubuntu
najlepszym kompilatorem okazał Javac. Szczególnie wyniki testów dla systemu
Windows, są bardzo ciekawe, gdyż okazuje się, nie zawsze narzędzia
wyprodukowane przez wiodące firmy na rynku są najlepsze. Istnieje wiele
programów, które nie są bliżej znane większej rzeszy użytkowników, dlatego też
przed rozwiązaniem danego problemu należy prześledzić rynek i wybrać
najbardziej odpowiadający nam program. Niestety jeżeli chodzi o kompilatory to
najlepszy obecnie dostępny kompilator Jikes nie jest już aktualizowany, co za tym
idzie nie posiada obecnie żadnego wsparcia technicznego, a szkoda bo
konkurencja z kompilatorem Javac na pewno wyszłaby na dobre obydwóm
narzędziom. Dlatego mam nadzieję, że programiści IBM’a reaktywują Jikes
i doczekamy się kolejnych jeszcze szybszych i wydajniejszych wersji.
37
6. Literatura
1. Java 2 podstawy, Prentice Hall, Wydawnictwo HELION, 2003
2. Język Java podstawy programowania, Jacek Rumuński, 1999
3. Oficjalna strona Javy http://www.java.com/en/javahistory/timeline.jsp
4. Historia wersji Javy, Wikipedia
http://en.wikipedia.org/wiki/Java_version_history
5. Kompilatory. Reguły metody i narzędzia, Alfred V. Aho, Ravi Sethi, Jeffley
D Ullman, Wydawnictwo Naukowo-Techniczne, Warszawa 2002
6. Opis B-kodu Javy, Wikipedia http://en.wikipedia.org/wiki/Java_bytecode
7. Opis Kompilacji JIT, Wikipedia http://en.wikipedia.org/wiki/Just-in-
time_compilation
8. Oficjalna strona Javy tutorial,
http://java.sun.com/j2se/1.4.2/docs/tooldocs/solaris/javac.html
9. Oficjalna strona Jikes, http://jikes.sourceforge.net/index.shtml
10. Oficjalna strona GNU Project http://gcc.gnu.org/onlinedocs/gcj/Input-
Options.html#Input-Options
11. Oficjalna strona Virtual Boxa http://www.virtualbox.org/wiki/Downloads.
12. Thinking In Java Wydanie 3, Eckel Bruce, Wydawnictwo HELION, 2003
13. Artykuł J. Kowalski, Java performance of java compilers, PWN,
http://java.sys-con.com/node/37555/
14. Strona z przykładowymi programami sprawdzającymi wydajność
kompilatorów, http://www.nullstone.com/htmls/results/java.htm
15. Strona doktora Blachnika
http://kzi.polsl.pl/~mblachnik/doku.php?id=notatki:informatyka:java
38
7. Spis rysunków
Rysunek 1. Wieloplatformowość Javy .................................................................. 4
Rysunek 2. Ogólny schemat kompilatora ............................................................. 9
Rysunek 3. Schemat blokowy kompilatora ......................................................... 10
Rysunek 4. Wymiana danych miedzy analizatorem leksykalnym i składniowym.. 12
Rysunek 5. Ogólny schemat kompilacji programu napisanego w Javie ............... 14
Rysunek 6. Dodawanie zmiennej środowiskowej ............................................... 20
Rysunek 7. Poprawnie skompilowany program .................................................. 21
Rysunek 8. Konsola, wyświetlenie czasów poszczególnych przebiegów ............ 30
39
8. Spis tabel
Tabela 1. Wyniki testów dla kompilatora Javac (czas w tabeli podany w ms) ...... 31
Tabela 2. Wyniki testów dla kompilatora Jikes (czas w tabeli podany w ms) ....... 31
Tabela 3. Wyniki testów dla kompilatora GCJ (czas w tabeli podany w ms) ........ 31
40
9. Spis wykresów
Wykres 1. Porównanie wydajności kompilatora Javac w systemach Windows XP i
Ubuntu (na osi pionowej czas w ms) .................................................................. 32
Wykres 2. Porównanie wydajności kompilatora Jikes w systemach Windows XP i
Ubuntu (na osi pionowej czas w ms) .................................................................. 32
Wykres 3. Porównanie wydajności kompilatora GCJ w systemach Windows XP i
Ubuntu (na osi pionowej czas w ms) .................................................................. 33
Wykres 4. Porównanie wydajności kompilatorów dla przebiegu 1 w systemach
Windows XP i Ubuntu (na osi pionowej czas w ms) ........................................... 33
Wykres 5. Porównanie wydajności kompilatorów dla przebiegu 2 w systemach
Windows XP i Ubuntu (na osi pionowej czas w ms) ........................................... 34
Wykres 6. Porównanie wydajności kompilatorów dla przebiegu 3 w systemach
Windows XP i Ubuntu (na osi pionowej czas w ms) ........................................... 34