RychlostGraalVM
Z Denik
(→JavaScript) |
|||
Řádka 125: | Řádka 125: | ||
Rychlé '''Ruby''' je sice hezká věc, ale kdo dnes píše v '''Ruby'''? Ten jazyk už je hezkých pár let za zenitem. Dnes přeci všichni píší v '''JavaScript'''u, že! | Rychlé '''Ruby''' je sice hezká věc, ale kdo dnes píše v '''Ruby'''? Ten jazyk už je hezkých pár let za zenitem. Dnes přeci všichni píší v '''JavaScript'''u, že! | ||
- | Ano, '''JavaScript''' je v současnosti vcelku populární a tudíž jej [[GraalVM]] také podporuje. A to buď jako čistý jazyk bez knihoven a nebo v kombinaci s ''node.js'', který poskytuje rozhraní pro přístup k operačnímu systému. Přepsat moje [https://github.com/jtulach/sieve/blob/ccca0c8a7c30b36234d2f2196581aa861d0ad428/js/sieve.js síto do JavaScript]u nebylo těžké. Struktura programu zůstala zachována: stále je tam generátor přirozených čísel, spojový seznam s prvočísly i smyčka jež měří čas potřebný pro výpočet prvních sto tisíc prvočísel. | + | Ano, '''JavaScript''' je v současnosti vcelku populární a tudíž jej [[GraalVM]] také podporuje. A to buď jako čistý jazyk bez knihoven a nebo v kombinaci s ''node.js'', který poskytuje rozhraní pro přístup k operačnímu systému. Přepsat moje [https://github.com/jtulach/sieve/blob/ccca0c8a7c30b36234d2f2196581aa861d0ad428/js/sieve.js síto do JavaScript]u nebylo těžké. Struktura programu zůstala zachována: stále je tam generátor přirozených čísel, spojový seznam s prvočísly i smyčka jež měří čas potřebný pro výpočet prvních sto tisíc prvočísel. [https://github.com/jtulach/sieve/blob/ccca0c8a7c30b36234d2f2196581aa861d0ad428/js/sieve.js Kód tedy máme] a můžeme měřit. Obecně je za nejrychlejší virtuální stroj pro běh '''JavaScript'''u považována '''V8''' od Googlu, která je také součástí standardní distribuce ''node.js''. Můžeme se tedy zkusit porovnat s ní: |
- | + | ||
- | Obecně je za nejrychlejší virtuální stroj pro běh '''JavaScript'''u považována '''V8''' od Googlu, která je také součástí standardní distribuce ''node.js''. Můžeme se tedy zkusit porovnat s ní: | + | |
<pre> | <pre> | ||
$ nodejs -v | $ nodejs -v |
Verze z 20. 4. 2018, 11:30
Jsou opravdu GraalVM jazyky tak rychlé, jak o sobě tvrdí? A jak vůbec měřit rychlost jazyka?
Objektivně měřit něco takového je přetěžký úkol. Téměř každý jazyk poskytuje dodatečné vestavěné operace, které již nejsou implementovány v daném jazyce, ale volají do vysoce optimalizované knihovny napsané v Céčku či assembleru. Její autoři si pak dávají dost záležet, aby zrovna ten jejich výpočet byl co nejrychlejší. Tudíž porovnávat rychlost jazyků na základě takovýchto specializovaných vychytávek nedává moc smysl. Na druhou stranu každý řádný programovací jazyk musí být turingovský úplný (musí podporovat obecné programovací konstrukce jako if, for a while), a tak navrhuji, abychom turingovskou rychlost jazyka měřili na základě obecného výpočtu, který pokud možno bude co nejméně využívat zabudovaných zkratek. To je samozřejmě těžké. Pokud se takový obecný výpočet stane známým, tak na něj začnou tvůrci jazyka cílit, speciálně optimalizovat a bude opět po nezávislosti. Tomu lze asi nejlépe zabránit tím, že si člověk napíše svůj vlastní algoritmus a provede si měření sám. Tak jako jsem to já udělal s Eratosthénovým sítem na výpočet prvočísel.
Ruby
Když jsem se k OracleLabs před pár lety přidal, moji kolegové hrdě tvrdili, že jejich implementace Ruby je desetkrát rychlejší než jakákoli jiná. "To určitě!" pomyslel jsem si. "Možná je rychlá na nějakém speciálním příkládku, ale jinak to přeci není možné" a řekl jsem si, že to vyzkouším. Chvíli jsem přemýšlel o nějakém vhodném příkladu a Eratosthénovo síto mi přišlo ideální. Je to výpočet, který může běžet libovolně dlouho (dokud nedojdou prvočísla), který provádí aritmetické operace a který (v mé verzi) alokuje objekty do spojového seznamu. Vcelku vhodný kandidát na měření turingovské rychlosti, řekl jsem si a začal psát svůj první program v Ruby:
class Natural def initialize @x = 1 end def next @x += 1 end end class Filter attr_reader :number attr_accessor :next def initialize(number) @number = number @next = nil @last = self end def acceptAndAdd(n) filter = self upto = Math.sqrt(n) while filter if n % filter.number == 0 return false end if filter.number > upto break end filter = filter.next end filter = Filter.new(n) @last.next = filter @last = filter true end end class Primes def initialize(natural) @natural = natural @filter = nil end def next while true n = @natural.next if @filter == nil @filter = Filter.new(n) return n end if @filter.acceptAndAdd(n) return n end end end end def ms(start) ((Time.now - start) * 1000).floor end def fewthousands natural = Natural.new primes = Primes.new(natural) start = Time.now cnt = 0 prntCnt = 97 begin res = primes.next cnt += 1 if cnt % prntCnt == 0 puts "Computed #{cnt} primes in #{ms(start)} ms. Last one is #{res}." prntCnt = prntCnt * 2 end end while cnt < 100000 ms(start) end puts "Ready!" count = -1 if ARGV.length == 1 then count = ARGV[0].to_i end while count != 0 puts "Hundred thousand prime numbers in #{fewthousands} ms" count = count - 1 end
Jeho první komponentou je generátor, zvaný Natural, přirozených čísel od dvojky do nekonečna. Další částí je Filter - to je prvek spojového seznamu do něhož se ukládají již nalezená prvočísla. Také má operaci acceptAndAdd, jež zkouší dělitelnost nového čísla již známými prvočísly a případně nové prvočíslo přidá do seznamu. Třída Primes si pak udržuje hlavu spojového seznamu a umí vydat další prvočíslo. Nakonec je v programu smyčka, která počítá sto tisíc prvočísel a měří čas, jak dlouho to trvalo.
Tato smyčka se opakuje stále dokola. Proč? Protože, a to jsem zatím ještě nezmínil, je hlavním cílem GraalVM zrychlit dlouhotrvající výpočty na serverech. Takové výpočty stále do kola opakují to samé a tudíž nevadí, když prvních pár (tisíců) výpočtů je pomalejších. To co je důležité je rychlost, které program dosáhne po zahřátí na provozní teplotu. Což je právě případ GraalVM: s dalším výpočtem se zrychluje a zrychluje. Proto je třeba provést několik výpočtů na zahřátí a teprve pak změřit dosaženou rychlost.
A jak to měření dopadlo? Ruby, které mám na svém Ubuntu dokáže jednu smyčku provést asi tak za dvě sekundy:
$ ruby -v ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu] $ ruby ruby/sieve.rb Hundred thousand prime numbers in 2055 ms
Ruby, které je součástí enterprise GraalVM to dokáže za méně než 150 ms: </pre> $ /graalvm-0.33/bin/ruby ruby/sieve.rb Hundred thousand prime numbers in 119 ms </pre> Tudíž, světe div se, moji kolegové nekecali. Opravdu je GraalVM Ruby alespoň desetkrát rychlejší než jakákoli jiná implementace.
JavaScript
Rychlé Ruby je sice hezká věc, ale kdo dnes píše v Ruby? Ten jazyk už je hezkých pár let za zenitem. Dnes přeci všichni píší v JavaScriptu, že!
Ano, JavaScript je v současnosti vcelku populární a tudíž jej GraalVM také podporuje. A to buď jako čistý jazyk bez knihoven a nebo v kombinaci s node.js, který poskytuje rozhraní pro přístup k operačnímu systému. Přepsat moje síto do JavaScriptu nebylo těžké. Struktura programu zůstala zachována: stále je tam generátor přirozených čísel, spojový seznam s prvočísly i smyčka jež měří čas potřebný pro výpočet prvních sto tisíc prvočísel. Kód tedy máme a můžeme měřit. Obecně je za nejrychlejší virtuální stroj pro běh JavaScriptu považována V8 od Googlu, která je také součástí standardní distribuce node.js. Můžeme se tedy zkusit porovnat s ní:
$ nodejs -v v6.14.1 $ nodejs js/sieve.js Hundred thousand prime numbers in 108 ms $ /graalvm-0.33/bin/js js/sieve.js Hundred thousand prime numbers in 106 ms
Nechci tvrdit, že by GraalVM byla vždy rychlejší než V8, ale když se ten program správně napíše, tak může být. Určitě však není o moc pomalejší. To by nás šéf hnal - v mládí si na prázdniny odskočil do Googlu napsat CrankShaft kompilátor - a teď nedá pokoj, dokud nejsme alespoň stejně rychlí.
V každém případě je však vhodné si položit otázku. GraalVM JavaScript' je o něco rychlejší než naše Ruby, ale jen o kousíček. Má cenu přepisovat celé programy z Ruby do JavaScriptu? Ne, nemá. Stačí přepsat jen kousíček!