Pro virtuální stroj programovacího jazyka Java (JVM) v současnosti existuje již poměrně velké množství různých „alternativních“ programovacích jazyků. Kromě samotné Javy jakožto primárního jazyka pro JVM se setkáme především s trojicí oblíbených jazyků Scala, Kotlin a Groovy, za nimiž následuje Clojure, JRuby, minimálně dvě implementace JavaScriptu a v neposlední řadě i Jython, což je varianta Pythonu 2.x přepsaná pro JVM. A právě Jythonem, který doposud stojí poněkud v pozadí, se dnes budeme zabývat.

Obsah

1. Jython aneb poněkud opomíjený programovací jazyk pro virtuální stroj Javy

2. Programovací jazyky pro JVM aneb nejde jen o Javu

3. Scala

4. Clojure

5. Groovy

6. JRuby, Jython, Rhino a Nashhorn

7. Kotlin

8. Další programovací jazyky portované na JVM

9. Instalace Jythonu a první spuštění interpretru

10. Interní nápověda

11. Základní moduly dodávané společně s Jythonem

12. Interoperabilita mezi Pythonem a Javou

13. Srovnání rychlosti Pythonu 2.x, Pythonu 3.x a Jythonu při výpočtech s FP hodnotami

14. Zdrojový kód jednoduchého benchmarku

15. Skripty pro spuštění benchmarku se zvoleným interpretrem a parametry

16. Výsledky prvního benchmarku

17. Druhý benchmark založený na použití komplexních čísel

18. Výsledky druhého benchmarku

19. Repositář s benchmarky i dalšími příklady

20. Odkazy na Internetu

1. Jython aneb poněkud opomíjený programovací jazyk pro virtuální stroj Javy

V dnešním článku si představíme projekt Jython, což je implementace dnes velmi populárního programovacího jazyka Python určená pro běh ve virtuálním stroji jazyka Java (JVM – Java Virtual Machine). A nejenom to – aplikace psané v Jythonu mohou kooperovat s třídami a rozhraními vytvořenými v Javě, což je velmi výhodné, protože s rostoucí složitostí moderních aplikací je většinou zapotřebí mít k dispozici vhodný skriptovací jazyk sloužící jako „lepidlo“ (glue) mezi jednotlivými bloky, z nichž se aplikace skládá. Jython ovšem samozřejmě není dokonalý. Jednou z jeho nevýhod je fakt, že je stále postaven na dnes již obstarožním Pythonu 2, druhou nevýhodou pak ta skutečnost, že se jedná o dosti pomalý jazyk, což si ostatně ukážeme na dvojici benchmarků. Tato pomalost se negativně projeví zejména při výpočtech a někdy i při manipulaci s rozsáhlými datovými strukturami, ovšem u aplikací, v nichž převládají I/O operace se nemusí jednat o kritický nedostatek.

Obrázek 1: Logo programovacího jazyka Jython.

2. Programovací jazyky pro JVM aneb nejde jen o Javu

Virtuální stroj Javy (JVM), specifikace JVM a dokonce ani jeho instrukční soubor vlastně nikde striktně nepředpokládají, že JVM bude spouštět pouze bajtkód získaný překladem javovských programů. Ve specifikaci JVM je dokonce na několika místech explicitně zmíněn předpoklad, že nad virtuálním strojem Javy budou provozovány i další programovací jazyky umožňující přímý či nepřímý překlad do bajtkódu. Kromě toho je samozřejmě možné přímo v Javě vytvořit interpret prakticky libovolného (interpretovaného) programovacího jazyka, takže vlastně nebude velkým překvapením, když zjistíme, že dnes těchto „JVM jazyků“ již existuje relativně velké množství. V následující tabulce jsou vypsány ty nejznámější a pravděpodobně nejpoužívanější jazyky, a to pro úplnost včetně samotné Javy:

Jazyk pro JVM Stručný popis Odkaz
Java primární jazyk pro JVM, bajtkód odpovídá právě Javě https://www.oracle.com/java/index.html
Clojure moderní dialekt programovacího jazyka Lisp https://clojure.org/
Groovy dynamicky typovaný jazyk pro JVM http://groovy-lang.org/
Rhino jedna z implementací JavaScriptu https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino
Nashorn alternativní implementace JavaScriptu https://blogs.oracle.com/nashorn/
JRuby portace jazyka Ruby na JVM http://jruby.org/
Jython portace jazyka Python na JVM http://www.jython.org/
Kotlin moderní staticky typovaný jazyk http://kotlinlang.org/
Scala další moderní jazyk pro JVM https://www.scala-lang.org/

3. Scala

Pravděpodobně nejznámějším příkladem programovacího jazyka provozovaného nad JVM je Scala, která nabízí prakticky bezproblémovou spolupráci mezi částmi kódu psanými ve Scale a zbytkem aplikace psaným v Javě (popř. jsou některé projekty psané pouze ve Scale, ovšem provozovány jsou například na ryze javovských serverech – Tomcat, Jetty atd.). Díky tomu, že zdrojové kódy psané ve Scale jsou přímo překládány do bajtkódu, získali tvůrci tohoto programovacího jazyka prakticky zadarmo veškeré vymoženosti, které virtuální stroj Javy (či přesněji řečeno celé JRE) poskytuje – od poměrně pečlivé kontroly bajtkódu při jeho načítání do virtuálního stroje přes použití správců paměti a JIT překladačů (Just in Time Compiler) až po možnost využití rozsáhlých standardních knihoven J2SE a samozřejmě taktéž mnoha dalších knihoven a frameworků, které jsou pro JVM dostupné. Ovšem Scala samozřejmě není jediným programovacím jazykem, který díky překladu do bajtkódu umožňuje využít prakticky veškerého potenciálu JVM/JRE.

Obrázek 2: Logo programovacího jazyka Scala.

4. Clojure

Z dalších překladačů programovacích jazyků, které pro virtuální stroj Javy vznikly, je podle mého názoru nejzajímavějším jazykem a současně i jazykem s velkým potenciálem pro budoucnost programovací jazyk s názvem Clojure, jehož autorem a dodnes nejaktivnějším vývojářem je Rich Hickey. Samotný název tohoto jazyka vznikl vložením písmene „j“ (Java/JVM) do slova closure (toto slovo se používá ve smyslu „lexikální uzávěr“ – důležitá abstrakce používaná nejenom ve funkcionálních programovacích jazycích). Velká část předností a pro mnohé vývojáře taktéž záporů programovacího jazyka Clojure vychází z toho, že se jedná o programovací jazyk, jehož syntaxe a sémantika do značné míry vychází z LISPu a Scheme, tedy jazyků známých především tím, že se v programech v nich psaných používá nadměrné množství kulatých závorek. Podrobnější informace o Clojure byly uvedeny v samostatném seriálu, který již vyšel na serveru www.root.cz.

Obrázek 3: Logo programovacího jazyka Clojure.

5. Groovy

Dalším programovacím jazykem, a nutno říci že jazykem poměrně populárním, je Groovy. Jedná se o jazyk inspirovaný některými dalšími (většinou dynamicky typovanými) programovacími jazyky, jako je Python, Ruby, ale například i Perl. Groovy podporuje objektově orientované programování a oproti Javě umožňuje psát kratší kód, z něhož jsou odstraněny méně podstatné části, které Java jakožto silně staticky typovaný jazyk vyžaduje. Z tohoto důvodu se Groovy velmi často používá všude tam, kde je vyžadovaný skriptovací jazyk běžící nad JVM. Dobrým příkladem je například konzole Jenkinsu, která ve výchozím nastavení používá právě Groovy. Podpora pro tento jazyk je součástí mnoha populárních integrovaných vývojových prostředí, a to včetně IntelliJ IDEA, Netbeans i Eclipse (přes pluginy – doplňkové moduly).

Obrázek 4: Logo programovacího jazyka Groovy.

6. JRuby, Jython, Rhino a Nashhorn

Zatímco Scala, Clojure i Groovy jsou novými jazyky, které původně vznikly přímo a pouze pro potřeby programátorů používajících virtuální stroj Javy (JVM), zmíníme se v této kapitole alespoň ve stručnosti o jazycích „klasických“, které byly na JVM pouze naportovány. V první řadě se jedná o projekty pojmenované JRuby a Jython, což jsou varianty jazyků Ruby a Python. Původní interpretry Ruby a Pythonu jsou naprogramovány v jazyku C (proto se ostatně tato varianta Pythonu nazývá CPython), JRuby a Jython jsou reimplementace pro JVM (navíc byl Python portován i na platformu .NET ve formě projektu IronPython a existuje i varianta Pythonu nazvaná PyPy naprogramovaná v samotném Pythonu, resp. přesněji řečeno v jeho zjednodušené variantě RPython). Na platformu JVM byl portován i programovací jazyk JavaScript, a to dokonce několikrát. Implementaci JavaScriptu zajišťují projekty Rhino a Nashhorn.

Obrázek 5: Logo programovacího jazyka JRuby.

7. Kotlin

Posledním programovacím jazykem určeným pro běh nad virtuálním strojem Javy, o němž se dnes alespoň ve stručnosti zmíníme, je jazyk pojmenovaný Kotlin. Jedná se o moderní staticky typovaný programovací jazyk vyvinutý ve společnosti JetBrains a jeho cíl je vlastně velmi podobný cíli, který si dal již zmíněný a poněkud starší jazyk Scala – efektivní a rychlá tvorba typově bezpečných aplikací určených pro běh nad JVM. Typovost jde ještě o krok dále, než je tomu v Javě, protože například rozlišuje mezi nulovatelnými a nenulovatelnými datovými typy. Na rozdíl od Scaly je však rychlost překladu zdrojových kódů naprogramovaných v Kotlinu rychlejší a blíží se rychlosti překladu kódů napsaných v samotné Javě. Kotlin podporuje různá paradigmata: objektově orientované, procedurální i funkcionální. Tomuto velmi zajímavému programovacímu jazyku se budeme věnovat v samostatném seriálu.

Obrázek 6: Logo programovacího jazyka Kotlin.

8. Další programovací jazyky portované na JVM

Na virtuální stroj Javy bylo portováno i mnoho dalších programovacích jazyků, ať již se jednalo o překladače či o interpretry. V následující tabulce jsou některé z těchto jazyků vypsány. V prvním sloupci je zobrazen název původního jazyka popř. rodiny jazyků, ve sloupci druhém pak jméno jeho konkrétní implementace pro JVM. Povšimněte si, že některé jazyky byly portovány několikrát (to se ostatně týkalo již JavaScriptu zmíněného ve třetí kapitole):

Jazyk Implementace pro JVM
Ada JGNAT
Arden Syntax Arden2ByteCode
COBOL Micro Focus Visual COBOL
ColdFusion Markup Language (CFML) Adobe ColdFusion
ColdFusion Markup Language (CFML) Railo
ColdFusion Markup Language (CFML) Lucee
ColdFusion Markup Language (CFML) Open BlueDragon
Common Lisp Armed Bear Common Lisp
Cypher Neo4j
Mercury Mercury (Java grade)
Pascal MIDletPascal
Pascal Oxygene
Perl 6 Rakudo Perl 6
PHP Quercus
Prolog JIProlog
Prolog TuProlog
R Renjin
Rexx NetRexx
Ruby JRuby
Ruby Mirah
Scheme Bigloo
Scheme Kawa
Scheme SISC
Scheme JScheme
Tcl Jacl
Visual Basic Jabaco

9. Instalace Jythonu a první spuštění interpretru

Instalace Jythonu je velmi jednoduchá, protože pouze postačuje si stáhnout Java archiv z adresy http://search.maven.org/remotecontent?filepath=org/python/jython-standalone/2.7.0/jython-standalone-2.7.0.jar, například takto:

$ <strong>wget -O jython-standalone-2.7.0.jar http://search.maven.org/remotecontent?filepath=org/python/jython-standalone/2.7.0/jython-standalone-2.7.0.jar</strong>

Následně si již můžeme spustit interaktivní interpret, což se provede tímto příkazem:

$ <strong>java -jar jython-standalone-2.7.0.jar</strong>

Po několika sekundách by se měly vypsat dva řádky s informacemi o verzi interpretru Jythonu a o použitém virtuálním stroji Javy. Na třetím řádku se vypíše nápověda k některým příkazů a na řádku následujícím pak výzva (prompt) interpretru:

Jython 2.7.0 (default:9987c746f838, Apr 29 2015, 02:25:11) 
[OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_79
Type "help", "copyright", "credits" or "license" for more information.
>>>

Povšimněte si, že způsob výpisu je prakticky shodný s nativním CPythonem, který po spuštění taktéž vypíše tři informační řádky následované výzvou:

Python 3.4.3 (default, Nov 28 2017, 16:41:13) 
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Vraťme se však ke spuštěnému interpretru Jythonu. Ten se chová prakticky stejně jako klasický interpret Pythonu (CPythonu), a to včetně historie příkazového řádku, základních klávesových zkratek atd. Zkusme si například přímo v interpretru napsat jednoduchou programovou smyčku a otestovat tak, jak se bude měnit výzva (prompt) ve chvíli, kdy se změní kontext (další řádky nejsou přímo interpretovány, ale čeká se na ukončení bloku či bloků prázdným řádkem):

>>> for i in range(10):
...     print i
... 
0
1
2
3
4
5
6
7
8
9
>>>

Podobným způsobem se kontext přepíná i ve chvíli, kdy deklarujeme funkci (popř. třídu):

>>> def max(x, y):
...     if x > 0:
...         return x
...     else:
...         return y
... 
>>> max(1,20)
1

Interpret Jythonu můžeme (na Linuxu) kdykoli opustit stiskem klávesové zkratky Ctrl+D.

Obrázek 7: Podpora Jythonu v Eclipse s nainstalovaným modulem PyDev.

10. Interní nápověda

Do interaktivní konzole jazyka Jython je integrována i nápověda, která se vyvolá velmi jednoduše zavoláním funkce help():

>>> help()

Po spuštění této funkce se zobrazí úvodní obrazovka s dalšími volbami, například „keywords“, „modules“ atd. Povšimněte si, že se změnila i výzva (prompt) na help>, což značí změnu kontextu interpretru:

Welcome to Python 2.7!  This is the online help utility.

If this is your first time using Python, you should definitely check out
the tutorial on the Internet at http://docs.python.org/2.7/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, or topics, type "modules",
"keywords", or "topics".  Each module also comes with a one-line summary
of what it does; to list the modules whose summaries contain a given word
such as "spam", type "modules spam".
 
help>

V režimu nápovědy si například můžeme vypsat všechna rozpoznávaná klíčová slova:

help> <strong>keywords</strong>

Here is a list of the Python keywords.  Enter any keyword to get more help.

and                 elif                if                  print
as                  else                import              raise
assert              except              in                  return
break               exec                is                  try
class               finally             lambda              while
continue            for                 not                 with
def                 from                or                  yield
del                 global              pass

Nápověda se ukončí prázdným příkazem, tj. stiskem klávesy Enter.

11. Základní moduly dodávané společně s Jythonem

Příkazem modules si můžeme vypsat všechny moduly, které jsou dodávány společně s Jythonem. Ostatně i samotná existence těchto modulů je jedním z důvodů, proč je Java archiv Jythonu tak velký – snahou tvůrců totiž bylo zahrnout javovské varianty většiny modulů, které se v CPythonu velmi často používají pro tvorbu (nejenom) serverových aplikací. Ostatně sami se můžete na seznam dostupných modulů velmi snadno podívat:

BaseHTTPServer      cmath           javapath    sched
CGIHTTPServer       cmd             javashell   select
ConfigParser        code            jffi        sets
Cookie              codecs          json        sgmllib
DocXMLRPCServer     codeop          jythonlib   sha
HTMLParser          collections     keyword     shelve
MimeWriter          colorsys        linecache   shlex
Queue               command         locale      shutil
SimpleHTTPServer    commands        logging     signal
SimpleXMLRPCServer  compileall      macpath     site
SocketServer        compiler        macurl2path smtpd
StringIO            contextlib      mailbox     smtplib
UserDict            cookielib       mailcap     sndhdr
UserList            copy            markupbase  socket
UserString          copy_reg        marshal     sre
_LWPCookieJar       crypt           math        sre_compile
_MozillaCookieJar   csv             md5         sre_constants
__builtin__         ctypes          mhlib       sre_parse
__future__          datetime        mime        ssl
_abcoll             dbexts          mimetools   stat
_ast                decimal         mimetypes   string
_bytecodetools      difflib         mimify      struct
_codecs             dircache        modjy       subprocess
_collections        dis             multifile   symbol
_csv                distutils       mutex       synchronize
_fsum               doctest         netrc       sys
_functools          dom             new         sysconfig
_google_ipaddr_r234 dumbdbm         nntplib     tabnanny
_hashlib            dummy_thread    ntpath      tarfile
_imp                dummy_threading nturl2path  telnetlib
_io                 email           numbers     tempfile
_json               encodings       opcode      test
_jyio               ensurepip       operator    tests
_jythonlib          errno           optparse    textwrap
_marshal            etree           os          this
_py_compile         exceptions      parsers     thread
_pyio               filecmp         pawt        threading
_random             fileinput       pdb         time
_rawffi             fnmatch         pickle      timeit
_socket             formatter       pickletools token
_sre                fpformat        pipes       tokenize
_sslcerts           fractions       pkgutil     trace
_strptime           ftplib          platform    traceback
_systemrestart      functools       plistlib    tty
_threading          future_builtins popen2      types
_threading_local    gc              poplib      ucnhash
_weakref            genericpath     posix       unicodedata
_weakrefset         getopt          posixfile   unittest
abc                 getpass         posixpath   urllib
aifc                gettext         pprint      urllib2
anydbm              glob            profile     urlparse
argparse            grp             pstats      user
array               gzip            pty         uu
ast                 hashlib         pwd         uuid
asynchat            heapq           py_compile  warnings
asyncore            hmac            pycimport   weakref
atexit              htmlentitydefs  pyclbr      webbrowser
base64              htmllib         pydoc       whichdb
bdb                 httplib         pyexpat     wsgiref
binascii            ihooks          quopri      xdrlib
binhex              imaplib         random      xml
bisect              imghdr          re          xmllib
bz2                 imp             readline    xmlrpclib
cPickle             importlib       repr        zipfile
cStringIO           inspect         rfc822      zipimport
calendar            io              rlcompleter zlib
cgi                 isql            robotparser
cgitb               itertools       runpy
chunk               jarray          sax

12. Interoperabilita mezi Pythonem a Javou

Hlavním důvodem vedoucím k použití Jythonu je samozřejmě jeho velmi dobrá kooperace s Javou, tj. jak s virtuálním strojem Javy, tak i se standardními knihovnami Javy (JSE) a ostatně i s libovolnými dalšími knihovnami. To je velmi důležité, protože ekosystém postavený okolo JVM (a například Mavenu) je dnes velmi rozsáhlý a Jython může být v tomto ekosystému použit ve funkci „lepidla“ (glue), které jednotlivé části dokáže spojit dohromady podle potřeb aplikace.

Interoperabilita mezi Pythonem a Javou je dotažena do poměrně velkých detailů, takže například v následujícím příkladu (spouštěném interaktivně v konzoli) můžeme naimportovat javovskou třídu java.util.Date, vytvořit její instanci a následně ji používat stejně, jako jakýkoli jiný objekt v Pythonu:

>>> import java.util.Date
>>> d = java.util.Date()
>>> d
Sat Mar 31 18:24:14 CEST 2018

Alternativní způsob importu bude samozřejmě taktéž funkční:

>>> from java.util import Date
>>> d = Date()
>>> d
Sat Mar 31 18:24:56 CEST 2018

Přístup k atributům objektu a volání jeho metod:

>>> d.time
1522514182141L
 
>>> d.getYear()
118

Automatický převod mezi typem „String“ v Javě a „unicode“ v Jythonu:

>>> from java.lang import String
>>> s = String("42")
 
>>> type(s)
<type 'java.lang.String'>
>>> s.toUpperCase()
u'42'
>>> type(s.toUpperCase())
<type 'unicode'>

Automatická konverze mezi instancí třídy BigInteger v Javě a long v Jythonu:

>>> import java.math.BigInteger
TypeError: java.math.BigInteger(): 1st arg can't be coerced to String, byte[]
>>> b = java.math.BigInteger("2")
 
>>> type(b)
<type 'java.math.BigInteger'>
>>> type(b.pow(10000))
<type 'long'>
>>> type(b.multiply(b2))
<type 'long'>

Základ práce s kolekcemi:

>>> from java.util import List, ArrayList
>>> l = ArrayList()
 
>>> l.append("two")
>>> l.append("three")
 
>>> l.get(1)
'three'

Při volání metod objektů, jejichž třídy byly naprogramovány v Javě, samozřejmě mohou vzniknout výjimky, které „probublají“ do kódu napsaného v Jythonu, kde je můžeme dále zpracovat (jakoby se jednalo o výjimky vyhozené z Pythonovského kódu). Příkladem může být výjimka při přístupu k neexistujícímu prvku seznamu:

>>> l.get(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
        at java.util.ArrayList.rangeCheck(ArrayList.java:635)
        at java.util.ArrayList.get(ArrayList.java:411)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)

Pro zachycení javovských výjimek musíme nejdříve naimportovat příslušnou třídu:

>>> from java.lang import IndexOutOfBoundsException

A poté již je možné výjimku snadno zachytit a zpracovat:

>>> try:
...     l.get(3)
... except IndexOutOfBoundsException as e:
...     print e
... 
java.lang.IndexOutOfBoundsException: Index: 3, Size: 2

Podívejme se ještě, jak si můžeme snadno v interaktivní smyčce vypsat všechny metody nějakého objektu. Postačuje napsat jméno objektu s tečkou a stlačit klávesu [Tab] (ostatně podobně jako v interaktivních vývojových prostředích):

>>> b.
b.ONE               b.TEN               b.ZERO              b.__class__(             b.__copy__(
b.__deepcopy__(     b.__delattr__(      b.__doc__           b.__ensure_finalizer__(  b.__eq__(
b.__format__(       b.__ge__(           b.__getattribute__( b.__gt__(                b.__hash__(
b.__init__(         b.__le__(           b.__lt__(           b.__ne__(                b.__new__(
b.__reduce__(       b.__reduce_ex__(    b.__repr__(         b.__setattr__(           b.__str__(
b.__subclasshook__( b.__unicode__(      b.abs(              b.add(                   b.and(
b.andNot(           b.bitCount(         b.bitLength(        b.byteValue(             b.class(
b.clearBit(         b.compareTo(        b.divide(           b.divideAndRemainder(    b.doubleValue(
b.equals(           b.flipBit(          b.floatValue(       b.gcd(                   b.getClass(
b.getLowestSetBit(  b.hashCode(         b.intValue(         b.isProbablePrime(       b.longValue(
b.lowestSetBit      b.max(              b.min(              b.mod(                   b.modInverse(
b.modPow(           b.multiply(         b.negate(           b.nextProbablePrime(     b.not(
b.notify(           b.notifyAll(        b.or(               b.pow(                   b.probablePrime(
b.remainder(        b.setBit(           b.shiftLeft(        b.shiftRight(            b.shortValue(
b.signum(           b.subtract(         b.testBit(          b.toByteArray(           b.toString(
b.valueOf(          b.wait(             b.xor(

13. Srovnání rychlosti Pythonu 2.x, Pythonu 3.x a Jythonu při výpočtech s FP hodnotami

Jedním z důvodů, proč není Jython tak populární, jako některé další programovací jazyky, které vznikly pro virtuální stroj Javy, je jeho relativně malý výpočetní výkon. Už klasický CPython ostatně velkou rychlostí neoplývá a podle některých benchmarků je Jython dokonce ještě pomalejší než CPython. Tento problém se většinou týká programů, které jsou zaměřeny na výpočty; u typických serverových aplikací s databází, messagingem atd. se jedná o méně závažné omezení (zde více záleží na tom, jak se nám podaří odstranit potřebu synchronizace vláken). Zkusme si nyní vytvořit dva jednoduché benchmarky určené pro porovnání rychlosti Jythonu, CPythonu 2.x a CPythonu 3.x se zaměřením na výpočty (další benchmarky se zpracováním datových struktur budou popsány příště). Všechny benchmarky byly spuštěny na Fedoře 27 a použity byly následující verze Pythonu:

  1. Jython 2.7.0
  2. Python 2.7.14
  3. Python 3.6.3

14. Zdrojový kód jednoduchého benchmarku

Vzhledem k tomu, že se v dnešním benchmarku budeme do značné míry snažit vyhnout měření rychlosti knihovních funkcí, bude celý benchmark skutečně provádět prakticky jen výpočty s výpisem výsledku výpočtů na standardní výstup. Ten bude přesměrován do souboru, protože výsledkem výpočtů budou bitmapy ve formátu Portable Pixel Map (viz [1]). Samozřejmě, že i výpis hodnot na standardní výstup znamená nutnost volání knihovních funkcí, ovšem oproti počtu numerických operací se bude jednat o minimální čas, což je možné zjistit například profilerem (to je ovšem téma, kterým se budeme zabývat v navazujícím článku). Celý benchmark spočívá ve výpočtu barev pixelů Mandelbrotovy množiny, přičemž rozlišení výsledného rastrového obrázku i maximální počet iterací bude možné zvolit z příkazového řádku. Následuje výpis zdrojového kódu benchmarku (kód je napsán tak, aby byl kompatibilní s Pythonem 2.x, Pythonem 3.x i Jythonem):

import palette_mandmap
from sys import argv, exit


def calc_mandelbrot(width, height, maxiter, palette):
    print("P3")
    print("{w} {h}".format(w=width, h=height))
    print("255")

    cy = -1.5
    for y in range(0, height):
        cx = -2.0
        for x in range(0, width):
            zx = 0.0
            zy = 0.0
            i = 0
            while i < maxiter:
                zx2 = zx * zx
                zy2 = zy * zy
                if zx2 + zy2 > 4.0:
                    break
                zy = 2.0 * zx * zy + cy
                zx = zx2 - zy2 + cx
                i += 1

            r = palette[i][0]
            g = palette[i][1]
            b = palette[i][2]
            print("{r} {g} {b}".format(r=r, g=g, b=b))
            cx += 3.0/width
        cy += 3.0/height


if __name__ == "__main__":
    if len(argv) < 4:
        print("usage: python mandelbrot width height maxiter")
        exit(1)

    width = int(argv[1])
    height = int(argv[2])
    maxiter = int(argv[3])
    calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)

V benchmarku se používá i další modul nazvaný palette_mandmap.py, který obsahuje barvovou paletu (palette, color map). Paleta byla získána ze známého (a dnes již vlastně historického) programu Fractint a obsahuje 256 trojic hodnot R, G, B. Samotná paleta nemá prakticky žádný vliv na naměřené hodnoty, ale výsledné obrázky jsou díky ní hezčí:

# taken from Fractint
palette = (
        (255, 255, 255), (224, 224, 224), (216, 216, 216), (208, 208, 208),
        (200, 200, 200), (192, 192, 192), (184, 184, 184), (176, 176, 176),
        (168, 168, 168), (160, 160, 160), (152, 152, 152), (144, 144, 144),
        (136, 136, 136), (128, 128, 128), (120, 120, 120), (112, 112, 112),
        (104, 104, 104),  (96,  96,  96),  (88,  88,  88),  (80,  80,  80),
        (72,   72,  72),  (64,  64,  64),  (56,  56,  56),  (48,  48,  56),
        (40,   40,  56),  (32,  32,  56),  (24,  24,  56),  (16,  16,  56),
        (8,     8,  56), (000, 000,  60), (000, 000,  64), (000, 000,  72),
        (000, 000,  80), (000, 000,  88), (000, 000,  96), (000, 000, 104),
        (000, 000, 108), (000, 000, 116), (000, 000, 124), (000, 000, 132),
        (000, 000, 140), (000, 000, 148), (000, 000, 156), (000, 000, 160),
        (000, 000, 168), (000, 000, 176), (000, 000, 184), (000, 000, 192),
        (000, 000, 200), (000, 000, 204), (000, 000, 212), (000, 000, 220),
        (000, 000, 228), (000, 000, 236), (000, 000, 244), (000, 000, 252),
        (000,   4, 252),   (4,  12, 252),   (8,  20, 252),  (12,  28, 252),
        (16,   36, 252),  (20,  44, 252),  (20,  52, 252),  (24,  60, 252),
        (28,   68, 252),  (32,  76, 252),  (36,  84, 252),  (40,  92, 252),
        (40,  100, 252),  (44, 108, 252),  (48, 116, 252),  (52, 120, 252),
        (56,  128, 252),  (60, 136, 252),  (60, 144, 252),  (64, 152, 252),
        (68,  160, 252),  (72, 168, 252),  (76, 176, 252),  (80, 184, 252),
        (80,  192, 252),  (84, 200, 252),  (88, 208, 252),  (92, 216, 252),
        (96,  224, 252), (100, 232, 252), (100, 228, 248),  (96, 224, 244),
        (92,  216, 240),  (88, 212, 236),  (88, 204, 232),  (84, 200, 228),
        (80,  192, 220),  (76, 188, 216),  (76, 180, 212),  (72, 176, 208),
        (68,  168, 204),  (64, 164, 200),  (64, 156, 196),  (60, 152, 188),
        (56,  144, 184),  (52, 140, 180),  (52, 132, 176),  (48, 128, 172),
        (44,  120, 168),  (40, 116, 160),  (40, 108, 156),  (36, 104, 152),
        (32,   96, 148),  (28,  92, 144),  (28,  84, 140),  (24,  80, 136),
        (20,   72, 128),  (16,  68, 124),  (16,  60, 120),  (12,  56, 116),
        (8,    48, 112),   (4,  44, 108), (000,  36, 100),   (4,  36, 104),
        (12,   40, 108),  (16,  44, 116),  (24,  48, 120),  (28,  52, 128),
        (36,   56, 132),  (40,  60, 140),  (48,  64, 144),  (52,  64, 148),
        (60,   68, 156),  (64,  72, 160),  (72,  76, 168),  (76,  80, 172),
        (84,   84, 180),  (88,  88, 184),  (96,  92, 192), (104, 100, 192),
        (112, 112, 196), (124, 120, 200), (132, 132, 204), (144, 140, 208),
        (152, 152, 212), (164, 160, 216), (172, 172, 220), (180, 180, 224),
        (192, 192, 228), (200, 200, 232), (212, 212, 236), (220, 220, 240),
        (232, 232, 244), (240, 240, 248), (252, 252, 252), (252, 240, 244),
        (252, 224, 232), (252, 208, 224), (252, 192, 212), (252, 176, 204),
        (252, 160, 192), (252, 144, 184), (252, 128, 172), (252, 112, 164),
        (252,  96, 152), (252,  80, 144), (252,  64, 132), (252,  48, 124),
        (252,  32, 112), (252,  16, 104), (252, 000,  92), (236, 000,  88),
        (228, 000,  88), (216,   4,  84), (204,   4,  80), (192,   8,  76),
        (180,   8,  76), (168,  12,  72), (156,  16,  68), (144,  16,  64),
        (132,  20,  60), (124,  20,  60), (112,  24,  56), (100,  24,  52),
        (88,   28,  48),  (76,  32,  44),  (64,  32,  44),  (52,  36,  40),
        (40,   36,  36),  (28,  40,  32),  (16,  44,  28),  (20,  52,  32),
        (24,   60,  36),  (28,  68,  44),  (32,  76,  48),  (36,  88,  56),
        (40,   96,  60),  (44, 104,  64),  (48, 112,  72),  (52, 120,  76),
        (56,  132,  84),  (48, 136,  84),  (40, 144,  80),  (52, 148,  88),
        (68,  156, 100),  (80, 164, 112),  (96, 168, 124), (108, 176, 136),
        (124, 184, 144), (136, 192, 156), (152, 196, 168), (164, 204, 180),
        (180, 212, 192), (192, 220, 200), (208, 224, 212), (220, 232, 224),
        (236, 240, 236), (252, 248, 248), (252, 252, 252), (252, 252, 240),
        (252, 252, 228), (252, 252, 216), (248, 248, 204), (248, 248, 192),
        (248, 248, 180), (248, 248, 164), (244, 244, 152), (244, 244, 140),
        (244, 244, 128), (244, 244, 116), (240, 240, 104), (240, 240,  92),
        (240, 240,  76), (240, 240,  64), (236, 236,  52), (236, 236,  40),
        (236, 236,  28), (236, 236,  16), (232, 232,   0), (232, 232,  12),
        (232, 232,  28), (232, 232,  40), (236, 236,  56), (236, 236,  68),
        (236, 236,  84), (236, 236,  96), (240, 240, 112), (240, 240, 124),
        (240, 240, 140), (244, 244, 152), (244, 244, 168), (244, 244, 180),
        (244, 244, 196), (248, 248, 208), (248, 248, 224), (248, 248, 236),
        (252, 252, 252), (248, 248, 248), (240, 240, 240), (232, 232, 232))

15. Skripty pro spuštění benchmarku se zvoleným interpretrem a parametry

Pro spuštění benchmarku použijeme trojici prakticky shodných skriptů, které budou postupně zvětšovat rozlišení výsledného obrázku. Pro malý počet iterací se tedy bude spíše měřit rychlost nastartování interpretru Pythonu popř. virtuálního stroje Javy, ovšem u vyšších rozlišení (přibližně od 128×128 pixelů) již začne převládat samotná doba výpočtu a vliv startu interpretru/JVM tak bude jen marginální.

Skript pro Python 2.x

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096"

OUTFILE="python2.times"
PREFIX="python2"

rm $OUTFILE

for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" python2 -B mandelbrot.py $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done

Skript pro Python 3.x

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096"

OUTFILE="python3.times"
PREFIX="python3"

rm $OUTFILE

for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" python3 -B mandelbrot.py $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done

Skript pro Jython

Tento skript vyžaduje, aby se v aktuálním adresáři nacházel Java archiv s Jythonem popř. jen symbolický link na tento archiv:

sizes="16 24 32 48 64 96 128 192 256 384 512 768 1024 1536 2048 3072 4096"

OUTFILE="jython.times"
PREFIX="jython"

rm $OUTFILE

for size in $sizes
do
    echo $size
    echo -n "$size " >> $OUTFILE
    /usr/bin/time --output $OUTFILE --append --format "%e %M" java -jar jython-standalone-2.7.0.jar mandelbrot.py $size $size 255 > "${PREFIX}_${size}_${size}.ppm"
done

16. Výsledky prvního benchmarku

Výsledky získané po spuštění prvního benchmarku jsou vypsány v následující tabulce:

Podívejme se nyní na slíbené výsledky:

# x-res y-res Jython (s) Python 2 (s) Python 3 (s)
1 16 16 1.79 0.01 0.02
2 24 24 1.79 0.01 0.02
3 32 32 1.84 0.02 0.02
4 48 48 2.11 0.03 0.03
5 64 64 2.01 0.04 0.05
6 96 96 2.16 0.08 0.09
7 128 128 2.24 0.15 0.15
8 192 192 2.43 0.32 0.33
9 256 256 2.81 0.57 0.58
10 384 384 3.87 1.25 1.29
11 512 512 5.05 2.27 2.28
12 768 768 8.61 5.07 5.21
13 1024 1024 13.22 9.00 9.10
14 1536 1536 28.15 20.73 21.24
15 2048 2048 50.03 36.11 38.24
16 3072 3072 100.78 81.93 84.02
17 4096 4096 179.21 144.45 148.44

Obrázek 9: Výsledky prvního benchmarku vynesené do grafu.

17. Druhý benchmark založený na použití komplexních čísel

Vzhledem k tomu, že Python podporuje i práci s komplexními čísly, si můžeme benchmark ještě více upravit, a to takovým způsobem, aby se v něm všechny výpočty prováděly právě nad typem complex. Zajímavé bude změření a porovnání rychlosti výpočtů, protože samotný virtuální stroj Javy primitivní typ „komplexní číslo“ nezná a tím pádem ani nepodporuje. Výsledkem bude následující zdrojový kód:

import palette_mandmap
from sys import argv, exit


def calc_mandelbrot(width, height, maxiter, palette):
    print("P3")
    print("{w} {h}".format(w=width, h=height))
    print("255")

    c = 0.0 - 1.5J
    for y in range(0, height):
        c = complex(-2.0, c.imag)
        for x in range(0, width):
            z = 0.0 + 0.0J
            i = 0
            while i < maxiter:
                if abs(z) > 4.0:
                    break
                z = z**2 + c
                i += 1

            r = palette[i][0]
            g = palette[i][1]
            b = palette[i][2]
            print("{r} {g} {b}".format(r=r, g=g, b=b))
            c += 3.0/width
        c += 3.0J/height


if __name__ == "__main__":
    if len(argv) < 4:
        print("usage: python mandelbrot width height maxiter")
        exit(1)

    width = int(argv[1])
    height = int(argv[2])
    maxiter = int(argv[3])
    calc_mandelbrot(width, height, maxiter, palette_mandmap.palette)

18. Výsledky druhého benchmarku

Z výsledků druhého benchmarku je patrné, že je Jython při práci s komplexními čísly výrazně pomalejší, než nativní CPython 2.x i CPython 3.x:

# x-res y-res Jython (s) Python 2 (s) Python 3 (s)
1 16 16 1.77 0.01 0.02
2 24 24 1.99 0.01 0.02
3 32 32 1.80 0.02 0.03
4 48 48 1.90 0.03 0.04
5 64 64 1.99 0.04 0.06
6 96 96 2.20 0.08 0.11
7 128 128 2.70 0.14 0.18
8 192 192 3.15 0.32 0.43
9 256 256 4.13 0.56 0.77
10 384 384 6.61 1.25 1.60
11 512 512 10.10 2.22 2.71
12 768 768 20.59 5.12 6.37
13 1024 1024 34.45 9.09 10.78
14 1536 1536 77.73 20.33 24.25
15 2048 2048 134.13 35.95 43.61
16 3072 3072 294.04 81.64 99.77
17 4096 4096 523.57 148.13 176.97

Obrázek 10: Výsledky druhého benchmarku vynesené do grafu.

19. Repositář s benchmarky i dalšími příklady

Oba dva jednoduché demonstrační příklady a taktéž oba benchmarky byly uloženy do Git repositáře, který naleznete na adrese https://github.com/tisnik/jython-examples. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít v aktuálním adresáři symlink na Java archiv jython-standalone-2.7.0.jar):

Úvodní příklady

Zdrojový kód/skript Adresa
hello_world https://github.com/tisnik/jython-examples/blob/master/benchmark2/mandelbrot_complex.py
java_interop https://github.com/tisnik/jython-examples/blob/master/java_interop

První benchmark

Zdrojový kód/skript Adresa
mandelbrot.py https://github.com/tisnik/jython-examples/blob/master/benchmark1/mandelbrot.py
palette_mandmap.py https://github.com/tisnik/jython-examples/blob/master/benchmark1/palette_mandmap.py
test_jython.sh https://github.com/tisnik/jython-examples/blob/master/benchmark1/test_jython.sh
test_python2.sh https://github.com/tisnik/jython-examples/blob/master/benchmark1/test_python2.sh
test_python3.sh https://github.com/tisnik/jython-examples/blob/master/benchmark1/test_python3.sh
clean.sh https://github.com/tisnik/jython-examples/blob/master/benchmark1/clean.sh

Druhý benchmark

Zdrojový kód/skript Adresa
mandelbrot_complex.py https://github.com/tisnik/jython-examples/blob/master/benchmark2/mandelbrot_complex.py
palette_mandmap.py https://github.com/tisnik/jython-examples/blob/master/benchmark2/palette_mandmap.py
test_jython.sh https://github.com/tisnik/jython-examples/blob/master/benchmark2/test_jython.sh
test_python2.sh https://github.com/tisnik/jython-examples/blob/master/benchmark2/test_python2.sh
test_python3.sh https://github.com/tisnik/jython-examples/blob/master/benchmark2/test_python3.sh
clean.sh https://github.com/tisnik/jython-examples/blob/master/benchmark2/clean.sh

20. Odkazy na Internetu

  1. Stránka projektu Jython
    http://www.jython.org/
  2. Jython (Wikipedia)
    https://en.wikipedia.org/wiki/Jython
  3. Scripting for the Java Platform (Wikipedia)
    https://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform
  4. JSR 223: Scripting for the JavaTM Platform
    https://jcp.org/en/jsr/detail?id=223
  5. List of JVM languages
    https://en.wikipedia.org/wiki/List_of_JVM_languages
  6. Stránka programovacího jazyka Java
    https://www.oracle.com/java/index.html
  7. Stránka programovacího jazyka Clojure
    http://clojure.org
  8. Stránka programovacího jazyka Groovy
    http://groovy-lang.org/
  9. Stránka programovacího jazyka JRuby
    http://jruby.org/
  10. Stránka programovacího jazyka Kotlin
    http://kotlinlang.org/
  11. Stránka programovacího jazyka Scala
    https://www.scala-lang.org/
  12. Projekt Rhino
    https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino
  13. Clojure (Wikipedia)
    https://en.wikipedia.org/wiki/Clojure
  14. Groovy (Wikipedia)
    https://en.wikipedia.org/wiki/Groovy_%28programming_language%29
  15. JRuby (Wikipedia)
    https://en.wikipedia.org/wiki/JRuby
  16. Kotlin (Wikipedia)
    https://en.wikipedia.org/wiki/Kotlin_%28programming_language%29
  17. Scala (Wikipedia)
    https://en.wikipedia.org/wiki/Scala_%28programming_language%29
  18. Python Interpreters Benchmarks
    https://pybenchmarks.org/u64q/jython.php
  19. Apache Kafka Producer Benchmarks - Java vs. Jython vs. Python
    http://mrafayaleem.com/2016/03/31/apache-kafka-producer-benchmarks/
  20. What is Jython and is it useful at all? (Stack Overflow)
    https://stackoverflow.com/questions/1859865/what-is-jython-and-is-it-useful-at-all