Obsah
1. Programovací jazyk Clojure a paralelní programy
2. Funkce pcalls – alternativa k funkci pmap
3. Využití návratové hodnoty funkce pcalls
4. Malá odbočka – zkrácený zápis anonymních funkcí
5. Funkce pcalls a lazy sekvence
6. Způsob použití makra pvalues
7. Spuštění výpočtů v samostatných vláknech pomocí future
8. Způsoby řízení „future“ výpočtů
1. Programovací jazyk Clojure a paralelní programy
V předchozí části tohoto seriálu jsme zabývali převážně problematikou zpracování sekvencí a především pak lazy sekvencí, které tvoří velmi důležitou část programovacího jazyka Clojure (skalní lispaři se na lazy sekvence mohou poněkud zjednodušeně dívat jako na zobecnění seznamů). Mj. jsme si řekli, že přímo v základní knihovně tohoto programovacího jazyka se nachází několik funkcí a maker umožňujících velmi jednoduše zajistit, aby se nějaká sekvence či množina funkcí zpracovala paralelně (programátor si ovšem musí dát pozor na to, že se budou volat funkce bez vedlejších efektů, popř. takové funkce, jejichž vedlejší efekty nebudou vzájemně kolidovat). Základem je v tomto případě funkce pmap, která svým chováním odpovídá funkci map, ovšem celá sekvence na vstupu této funkce je zpracovávána paralelně. To může (ale taktéž nemusí) přinést zvýšení výkonu celé aplikace, ovšem je ponecháno pouze na úsudku programátora, kterou z těchto funkcí ve své aplikaci použije – v případě jednodušších výpočtů totiž režie nutná pro vytvoření nových vláken převáží nad výhodami souběžného běhu těchto vláken.
Měření doby běhu nějaké funkce (resp. doby vyhodnocení formy) lze zajistit funkcí time, jejíž způsob použití je ukázán na následujícím příkladu (převzatém z předchozí části tohoto seriálu):
(defn fibonacci [n] (if (< n 2) n (+ (fibonacci (- n 2)) (fibonacci (- n 1))))) (time (dorun (map fibonacci (range 100 150)))) (time (dorun (pmap fibonacci (range 100 150))))
V závislosti na počtu jader mikroprocesoru, nastavení parametrů virtuálního stroje Javy (JRE) a dalších parametrech počítače, na němž je předchozí příklad spuštěn, se budou vypisovat rozdílné časy doby běhu, které by ale měly být na moderních strojích s minimálně dvoujádrovým CPU menší při použití funkce pmap namísto funkce map.
Příklad výsledku předchozího jednoduchého benchmarku na stroji s nízkým počtem procesorových jader:
user=> (time (dorun (map fibonacci (range 20 30)))) "Elapsed time: 3105.400932 msecs" nil user=> (time (dorun (pmap fibonacci (range 20 30)))) "Elapsed time: 2558.662981 msecs" nil
2. Funkce pcalls – alternativa k funkci pmap
Kromě funkce pmap, která je pro použití v reálných aplikacích velmi jednoduchá i praktická, lze v programovacím jazyku Clojure použít i makro nazvané pvalues sloužící pro paralelní vyhodnocení několika forem (opět záleží jen na programátorovi, aby zajistil, že nedojde ke kolizím v případě vedlejších efektů – kdo programuje čistě funkcionálně, nemá problém :-). Taktéž lze použít funkci pcalls, jež umožňuje zavolat a současně (paralelně) spustit několik funkcí předaných ve formě parametrů této funkce. U funkce pcalls spočívá jediný – ovšem v mnoha případech dosti závažný – problém v tom, že volané funkce jsou bezparametrické. V následujícím dosti umělém demonstračním příkladu se paralelně volá trojice bezparametrických funkcí, z nichž každá má vedlejší efekt – výpis textu na standardní výstup (jedná se o jeden z méně zákeřných vedlejších efektů). Nejprve jsou všechny tři funkce nadeklarovány a posléze je dvakrát po sobě zavolána funkce pcalls, aby bylo patrné, že pořadí výpisu na standardní výstup je z hlediska programátora „náhodné“, protože ten nemůže explicitně ovlivnit pořadí spuštění, popř. způsob přepínání vláken:
; jednoduché bezparametrické funkce, ; které pouze na standardní výstup ; vypíšou nějaký text (defn fce1 [] (println "Funkce 1")) (defn fce2 [] (println "Funkce 2")) (defn fce3 [] (println "Funkce 3"))
; nyní všechny tři funkce spustíme tak, ; že každá funkce poběží ve svém vláknu (pcalls fce1 fce2 fce3) Funkce 1 Funkce 2 (Funkce 3 nil nil nil)
V předchozím výpisu došlo ke smíchání výpisu všech tří funkcí s hodnotou, jejíž význam si popíšeme ve třetí kapitole.
; opakované spuštění, aby bylo zřejmé, ; že pořadí výpisu je "náhodné" (pcalls fce1 fce2 fce3) Funkce 1 (Funkce 3 Funkce 2 nil nil nil)
3. Využití návratové hodnoty funkce pcalls
Zajímavá je taktéž trojice hodnot nil nil nil vypsaná předchozím demonstračním příkladem na posledním řádku (ve skutečnosti k těmto hodnotám patří i otevírací a uzavírací kulaté závorky, které se však ve výpisu mohou jednoduše přehlédnout: tyto závorky nám říkají, že výsledkem je buď seznam nebo nějaká obecná sekvence). Samozřejmě se v případě (nil nil nil), jak by měl text správně vypadat, jedná o výsledek samotné funkce pcalls, protože smyčka REPL automaticky vypisuje návratové hodnoty všech vyhodnocených forem. Ve skutečnosti totiž funkce pcalls vrací lazy sekvenci obsahující výsledky všech paralelně spuštěných funkcí, čehož lze samozřejmě náležitě využít. Podívejme se na poněkud složitější demonstrační příklad, v němž se bude paralelně počítat několik členů Fibonacciho řady/posloupnosti (http://cs.wikipedia.org/wiki/Fibonacciho_posloupnost).
Prezentovaný výpočet je samozřejmě z hlediska optimalizací algoritmu nesmyslný, protože by bylo mnohem jednodušší prostě ukládat jednotlivé mezivýsledky do seznamu nebo vektoru. V našem příkladu ovšem výpočet jednoho členu Fibonacciho řady použijeme proto, že se jedná o jednoduchou a současně i snadno pochopitelnou funkci, jejíž vyhodnocení může trvat delší dobu (na mém obstarožním počítači je již výpočet třicátého členu tak pomalý, že je prodleva ve vyhodnocení jasně viditelná):
; nejprve je uvedena deklarace funkce pro výpočet jednoho ; členu Fibonacciho řady: (defn fibonacci [n] (if (< n 2) n (+ (fibonacci (- n 2)) (fibonacci (- n 1)))))
; následně nadeklarujeme pomocné jednoúčelové funkce, ; které nahradí volání funkce fibonacci s parametrem ; (nejedná se o nejšťastnější způsob tvorby programů :-) (defn fib20 [] (fibonacci 20)) (defn fib25 [] (fibonacci 25)) (defn fib30 [] (fibonacci 30))
; test, zda jsou funkce zapsány korektně user=> (fib20) 6765 user=> (fib25) 75025 user=> (fib30) 832040
; dobře, můžeme zkusit spustit výpočty paralelně user=> (pcalls fib20 fib25 fib30) (6765 75025 832040)
4. Malá odbočka – zkrácený zápis anonymních funkcí
Použití pomocných funkcí fib20, fib25 a fib30 nadeklarovaných v demonstračním příkladu uvedeném na konci předchozí kapitoly bylo vynuceno již zmíněným omezením funkce pcalls na volání funkcí, které nemají žádné parametry. Jedná se o již na první pohled velmi nepěkný způsob programování, kdy se na globální úrovni (přesněji řečeno v implicitním jmenném prostoru) vytváří jednoúčelové funkce. Je zajímavé, že v objektově orientovaných jazycích je tomu mnohdy naopak – zde se totiž doporučuje používat jednoduché jednoúčelové metody, ty jsou ovšem omezeny na jednu třídu, popř. na jednu větev hierarchie tříd. Zmíněné omezení funkce pcalls lze však ve skutečnosti obejít i jinak – pomocí anonymních funkcí, což je ve funkcionálním jazyku obecně považováno za mnohem elegantnější přístup. Jedno z možných řešení může vypadat tak, že se přímo ve volání funkce pcalls vytváří potřebné anonymní funkce (ty nesmí mít v tomto případě žádné parametry, protože jejich hodnoty by nebylo možné nijak předat):
(pcalls (fn [] (fibonacci 20)) (fn [] (fibonacci 25)) (fn [] (fibonacci 30)) ) (6765 75025 832040)
Tento způsob zápisu však není o moc čitelnější, než tomu bylo v příkladu předchozím, protože se pro vytvoření anonymní funkce použila speciální forma fn, která je dosti upovídaná. Kratší řešení ovšem existuje – vzhledem k tomu, že se anonymní funkce používají při tvorbě aplikací v programovacím jazyku Clojure velmi často, je možné pro jejich zápis alternativně použít i zkrácenou formu začínající znakem #, za nímž je v závorce přímo napsáno tělo anonymní funkce (tato forma se překládá do volání fn ještě v preprocesoru smyčky REPL, tj. samotná smyčka REPL již „vidí“ pouze zápis speciální formy fn):
#(tělo anonymní funkce)
To, že chybí jméno funkce je pochopitelné – kdyby funkce měla jméno, už by nebyla funkcí anonymní :-) ovšem absence jmen parametrů možná může být poněkud matoucí, protože anonymní funkce bez parametrů by vlastně odpovídala vrácení konstanty (popř. nefunkcionálním „šílenostem“ typu random). Ve skutečnosti však i anonymní funkce vytvořená pomocí znaku # parametry mít může. Ty jsou pojmenovány v závislosti na své pozici takto: %1, %2 atd. Předpokládá se totiž, že anonymní funkce budou velmi krátké a s malým množstvím parametrů – pokud by tomu tak nebylo, nic programátorům nebrání vrátit se ke speciální formě fn.
Ukažme si nějaké příklady použití vytvoření anonymních funkcí pomocí znaku #.
Aplikace anonymní funkce vytvořené s využitím speciální formy fn:
((fn [x y] (* x y)) 6 7)
Zkrácený (ekvivalentní) způsob zápisu:
(#(* %1 %2) 6 7)
Anonymní funkce se velmi často používají jako parametry funkcí map, pmap, filter apod.:
(map #(/ 2 %1) [1 2 3 4 5 6]) (2 1 2/3 1/2 2/5 1/3)
Popř.:
(map #(/ 2 %1) (range 1 7)) (2 1 2/3 1/2 2/5 1/3)
Vraťme se nyní k původnímu (trošku umělému) příkladu. Namísto:
(pcalls (fn [] (fibonacci 20)) (fn [] (fibonacci 25)) (fn [] (fibonacci 30)) ) (6765 75025 832040)
můžeme jednoduše napsat:
(pcalls #(fibonacci 20) #(fibonacci 25) #(fibonacci 30) ) (6765 75025 832040)
Zde máme situaci ještě jednodušší, protože vlastně žádný parametr anonymní funkce nepotřebujeme zpracovat a ve skutečnosti ani nemůžeme, protože – jak jsme si již řekli – funkce pcalls nedovoluje volání funkcí s parametry.
5. Funkce pcalls a lazy sekvence
O tom, že je výsledkem volání funkce pcalls skutečně lazy sekvence, se můžeme přesvědčit jednoduše. Uložíme výsledek do nějaké proměnné a potom si vypíšeme jak hodnotu této proměnné, tak i výsledky některých predikátů:
user=> (def results (pcalls fib20 fib25 fib30)) #'user/results user=> (list? results) false user=> (vector? results) false user=> (seq? results) true user=> results (6765 75025 832040)
Je přitom samozřejmé, že na výslednou lazy sekvenci lze aplikovat všechny funkce, které se sekvencemi mohou pracovat:
user=> (map #(/ 1 %1) results) (1/6765 1/75025 1/832040)
user=> (map #(/ 1 (* %1 %1)) results) (1/45765225 1/5628750625 1/692290561600)
Programovací jazyk Clojure je při práci s lazy sekvencemi skutečně „líný“, protože se výzva smyčky REPL může objevit ještě předtím, než je výpočet dokončen – to nám samozřejmě vůbec nevadí, protože ani funkce fibonacci, ani její „obalující“ funkce fib20, fib25 a fib30 nemají vedlejší efekty. Co se však stane v případě, kdy vedlejší efekt naschvál přidáme, například takovým způsobem, že každá z pomocných funkcí vytiskne text na začátku a na konci výpočtu? Můžeme si to snadno vyzkoušet:
(defn fib20 [] (println "pocitam fibonacci 20") (println "dokonceno fibonacci 20 =" (fibonacci 20))) (defn fib25 [] (println "pocitam fibonacci 25") (println "dokonceno fibonacci 25 =" (fibonacci 25))) (defn fib30 [] (println "pocitam fibonacci30") (println "dokonceno fibonacci 30 =" (fibonacci 30)))
Sice jsme kvůli výpisu zprávy ztratili výsledek výpočtu, to nám však pro ilustraci funkce makra pcalls nemusí vadit. Ještě si vytvoříme pomocnou funkci compute_all, která spustí výpočet a ihned poté vypíše na standardní výstup zprávu „konec vypoctu???“:
(defn compute_all [] (println "zacatek vypoctu") (def result (pcalls fib20 fib25 fib30 fib30)) (println "konec vypoctu???") result)
Zajímavá věc se stane po spuštění této funkce:
user=> (compute_all) zacatek vypoctu pocitam fibonacci 20 pocitam fibonacci 25 konec vypoctu??? (pocitam fibonacci30 pocitam fibonacci30 dokonceno fibonacci 20 = 6765 dokonceno fibonacci 25 = 75025 nil dokonceno fibonacci 30 = 832040 dokonceno fibonacci 30 = 832040 nil nil nil)
V předchozích řádcích jsou navzájem promíchány tři typy zpráv. Jsou to zprávy tištěné pomocnými funkcemi fib20, fib25 a fib30, které jsou spuštěny paralelně, což je ostatně z výpisu patrné. Další typ zpráv „zacatek vypoctu“ a „konec vypoctu???“ tiskne funkce compute_all a povšimněte si, že tato funkce vypíše „konec vypoctu???“ ještě předtím, než je výpočet skutečně dokončen. To nám však nevadí, protože do proměnné result je uložena lazy sekvence, která se vyhodnotí ve chvíli, kdy je to potřeba, popř. se nevyhodnotí nikdy. Vyhodnocení jsme si vynutili posledním výrazem ve funkci compute_all, kterým říkáme, že se má vrátit lazy sekvence. K vyhodnocení dojde z toho důvodu, že je funkce compute_all volána ze smyčky REPL a ta samozřejmě vypíše každou vyhodnocenou formu – v tomto případě jsou to tři hodnoty nil (bylo by jistě možné funkce fib* upravit tak, aby tyto funkce vracely správné hodnoty, to by však již vyžadovalo použití formy let, s níž jsme se prozatím v tomto seriálu neseznámili).
6. Způsob použití makra pvalues
Z trojice funkcí a maker pmap, pcalls a pvalues nám již zbývá popsat pouze makro pvalues. To je ve skutečnosti na použití velmi jednoduché, protože umožňuje paralelně vyhodnotit libovolné množství forem (výrazů), přičemž výsledkem volání makra pvalues je lazy sekvence obsahující hodnoty všech vyhodnocených forem. Může se jednat o zcela libovolné formy, tj. o literály, volání funkcí s různými parametry atd:
user=> (pvalues true nil "retezec" (/ 1 2) (* 6 7) (fibonacci 35)) (true nil "retezec" 1/2 42 9227465)
Pokud zůstaneme u našeho dosti umělého příkladu s výpočtem prvků Fibonacciho řady, můžeme namísto volání pcalls (kde bylo nutné použít buď anonymní funkce nebo bezparametrické „obalové“ funkce) použít makro pvalues následujícím způsobem:
; zavolání funkce Fibonacci ; s následným vyhodnocením lazy sekvence: user=> (pvalues (fibonacci 20) (fibonacci 25) (fibonacci 30)) (6765 75025 832040)
; výsledek je možné skutečně použít jako jakoukoli jinou lazy sekvenci user=> (filter odd? (pvalues (fibonacci 20) (fibonacci 25) (fibonacci 30))) (6765 75025)
; samozřejmě i velmi užitečnou funkcí reduce user=> (reduce + (pvalues (fibonacci 20) (fibonacci 25) (fibonacci 30))) 913830
Vzhledem k tomu, že i makro pcalls vrací lazy sekvenci, je zajištěno i líné vyhodnocení výsledků až ve chvíli, kdy jsou tyto výsledky skutečně zapotřebí:
user=> (def results (pvalues (fibonacci 20) (fibonacci 25) (fibonacci 35))) #'user/results user=> results (6765 75025 9227465)
Poznámka: makro pvalues je ve skutečnosti vytvořeno takovým způsobem, že interně používá funkci pcalls. O makrech jsme si prozatím neříkali žádné podrobnosti, ovšem ze zdrojového kódu makra pvalues je zřejmé, že se funkce pcalls skutečně využívá (co znamenají znaky ` @ atd. si řekneme pravděpodobně až v osmé části tohoto podseriálu):
(defmacro pvalues [& exprs] `(pcalls ~@(map #(list `fn [] %) exprs)))
Poznámka2: zajímavé taktéž je, že samotná funkce pcalls interně volá funkci pmap, což vlastně znamená, že právě pmap je jedinou „paralelní“ funkcí, která musí být podporována v jádru jazyka Clojure a zbylá dvojice pcalls+pvalues je již řešitelná v samotném Clojure:
(defn pcalls [& fns] (pmap #(%) fns))
(znak % v anonymní funkci zde odpovídá %1, význam znaku & si vysvětlíme příště; zde jen bez dalších podrobností uvedu, že se všechny skutečné argumenty při volání funkce pcalls vloží do fns, takže je lze zpracovat jako běžnou sekvenci).
7. Spuštění výpočtů v samostatných vláknech pomocí future
Použití trojice funkcí/maker pmap+pcalls+pvalues není v žádném případě jedinou možností, jak v programovacím jazyce Clojure zapisovat algoritmy zpracovávané paralelně. Velmi elegantní a přitom jednoduchý způsob spuštění paralelního výpočtu představuje použití takzvaných futures. Mimochodem, jedná se o technologii, která existuje i v knihovnách programovacího jazyka Java (dokonce již od verze 5.0), i když v Javě se pravděpodobně jedná o méně známé API – osobně si však myslím, že využití futures v Javě je mnohdy výhodnější, než explicitní tvorba vláken, synchronizovaných metod/bloků a dalších nízkoúrovňových prostředků.
Co se však vlastně pod pojmem futures skrývá? Kromě názvu jednoho typu finančního derivátu :-) představuje future(s) výpočet, který běží asynchronně k hlavnímu vláknu aplikace. Uživatel pouze výpočet spustí a teprve ve chvíli, kdy potřebuje výsledek tohoto výpočtu, začne systém řešit, jakým způsobem má asynchronní výpočet ukončit, tj. jak má provést synchronizaci obou vláken. V ideálním případě je výpočet již dokončen, takže se přímo použije jeho výsledek, v případě opačném se až při čtení výsledku počká na dokončení výpočtu. Je samozřejmé, že jakýkoli přístup ke sdíleným prostředkům musí být omezen, což však v programovacím jazyce, jehož hodnoty jsou neměnitelné, není až tak velký problém (Clojure navíc používá poněkud jinou sémantiku změny stavu, než je tomu v běžných jazycích s proměnnými – více viz následující část tohoto seriálu).
8. Způsoby řízení „future“ výpočtů
Podívejme se nyní, jak by bylo možné paralelně spustit dva výpočty 35. členu Fibonacci řady (pokud máte rychlý počítač, můžete si index vypočteného členu patřičně zvýšit):
; opět klasický paralelní zápis funkce fibonacci (defn fibonacci [n] (if (> n 2) n (+ (fibonacci (- n 2)) (fibonacci (- n 1)))))
; namísto přímého volání funkce fibonacci však spustíme ; dva asynchronní výpočty s využitím (future funkce) user=> (def future_fibonacci1 (future (fibonacci 35))) #'user/future_fibonacci1 user=> (def future_fibonacci2 (future (fibonacci 35))) #'user/future_fibonacci2
Oba výpočty (fibonacci 35) byly spuštěny v samostatných vláknech, což mj. znamená, že smyčka REPL nemusí čekat na výsledek výpočtu a nabídne výzvu (prompt) prakticky ihned po zadání obou předchozích příkazů. Jak se však dostaneme k výsledku? Postačuje kdekoli v programu či přímo v REPL zavolat deref, jemuž se předá název future objektu:
user=> (deref future_fibonacci1) 9227465 user=> (deref future_fibonacci2) 9227465
Co se vlastně stalo? Vzhledem k tomu, že smyčka REPL běží v jiném vlákně, než samotný výpočet a nyní potřebuje získat návratovou hodnotu tohoto asynchronního výpočtu, dojde k čekání na dokončení výpočtového vlákna spjatého se future objektem. Jakmile je výpočet dokončen, je získán jeho výsledek a výpočtové vlákno je zrušeno, resp. přesněji řečeno je vráceno do poolu, aby mohlo být kdykoli později znovu využito (to je důležité v případě, že se asynchronní výpočty == futures používají v aplikaci častěji).
Vzhledem k tomu, že deref se v multivláknových programech používá poměrně často, je možné použít zkrácený způsob zápisu, kdy se namísto (deref foo) použije pouze @foo, tj. neuvádí se ani kulaté závorky. Ekvivalentem předchozího příkladu by tedy byl následující výpočet:
user=> (def future_fibonacci1 (future (fibonacci 35))) #'user/future_fibonacci1 user=> (def future_fibonacci2 (future (fibonacci 35))) #'user/future_fibonacci2 user=> @future_fibonacci1 9227465 user=> @future_fibonacci2 9227465
Poznámka: hodnota 35 byla zvolena tak, aby výpočet trval přibližně 10 sekund na mém postarším (no…velmi postarším :-) počítači. Počítače vážených čtenářů budou s velkou pravděpodobně mnohem rychlejší, takže si prosím zvolte příslušně větší konstantu, popř. je možné namísto výpočtu použít pouhé čekání – ovšem v tomto případě neuvidíte žádné zatížení v programu top či htop.
Čekání, resp. pozastavení aktuálního vlákna lze provést jednoduše zavoláním příslušné javovské funkce:
(Thread/sleep doba_cekani_v_milisekundach)
9. Odkazy na Internetu
- Clojure home page
http://clojure.org/downloads - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc
http://clojuredocs.org/ - Clojure (Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
http://java.sun.com/developer/technicalArticles/DynTypeLang/ - JSR 223: Scripting for the JavaTM Platform
http://jcp.org/en/jsr/detail?id=223 - JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
http://jcp.org/en/jsr/detail?id=292 - Java 7: A complete invokedynamic example
http://niklasschlimm.blogspot.com/2012/02/java-7-complete-invokedynamic-example.html - InvokeDynamic: Actually Useful?
http://blog.headius.com/2007/01/invokedynamic-actually-useful.html - A First Taste of InvokeDynamic
http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html - Java 6 try/finally compilation without jsr/ret
http://cliffhacks.blogspot.com/2008/02/java-6-tryfinally-compilation-without.html - An empirical study of Java bytecode programs
http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/ - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
http://www.root.cz/clanky/vyuziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/ - Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
http://www.root.cz/clanky/jamvm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - BCEL Home page
http://commons.apache.org/bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - FindBugs
http://findbugs.sourceforge.net/ - GNU Classpath
www.gnu.org/s/classpath/ - Java VMs Compared
http://bugblogger.com/java-vms-compared-160/ - JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
http://www.jcp.org/en/jsr/detail?id=223 - Scripting for the Java Platform
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/ - Scripting for the Java Platform (Wikipedia)
http://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform - Java Community Process
http://en.wikipedia.org/wiki/Java_Specification_Request - Java HotSpot VM Options
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html - Great Computer Language Shootout
http://c2.com/cgi/wiki?GreatComputerLanguageShootout - Java performance
http://en.wikipedia.org/wiki/Java_performance - Trying the prototype
http://mail.openjdk.java.net/pipermail/lambda-dev/2010-August/002179.html - Better closures (for Java)
http://blogs.sun.com/jrose/entry/better_closures - Lambdas in Java: An In-Depth Analysis
http://www.infoq.com/articles/lambdas-java-analysis - Class ReflectiveOperationException
http://download.java.net/jdk7/docs/api/java/lang/ReflectiveOperationException.html - Scala Programming Language
http://www.scala-lang.org/ - Run Scala in Apache Tomcat in 10 minutes
http://www.softwaresecretweapons.com/jspwiki/run-scala-in-apache-tomcat-in-10-minutes - Fast Web Development With Scala
http://chasethedevil.blogspot.cz/2007/09/fast-web-development-with-scala.html - Top five scripting languages on the JVM
http://www.infoworld.com/d/developer-world/top-five-scripting-languages-the-jvm-855 - Proposal: Indexing access syntax for Lists and Maps
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/001108.html - Proposal: Elvis and Other Null-Safe Operators
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/000047.html - Java 7 : Oracle pushes a first version of closures
http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/ - Groovy: An agile dynamic language for the Java Platform
http://groovy.codehaus.org/Operators - Better Strategies for Null Handling in Java
http://www.slideshare.net/Stephan.Schmidt/better-strategies-for-null-handling-in-java - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Java Virtual Machine
http://en.wikipedia.org/wiki/Java_virtual_machine - ==, .equals(), compareTo(), and compare()
http://leepoint.net/notes-java/data/expressions/22compareobjects.html - New JDK7 features
http://openjdk.java.net/projects/jdk7/features/ - Project Coin: Bringing it to a Close(able)
http://blogs.sun.com/darcy/entry/project_coin_bring_close - CloseableFinder source code
http://blogs.sun.com/darcy/resource/ProjectCoin/CloseableFinder.java - Joe Darcy blog about JDK
http://blogs.sun.com/darcy - Java 7 – more dynamics
http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/ - New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
http://java.sun.com/developer/technicalArticles/DynTypeLang/index.html