2013-05-14 22 views
8

Mam program ClojureScript, który wykonuje głównie obliczenia matematyczne w kolekcjach. Został opracowany w idiomatycznym, niezależnym od hosta Clojure, więc łatwo go porównać. Ku mojemu zaskoczeniu (i wbrew temu, co sugerowałyby odpowiedzi na Which is faster, Clojure or ClojureScript (and why)?), ten sam kod w ClojureScript działa 5-10 razy wolniej niż jego odpowiednik Clojure.Zwiększenie wydajności programu ClojureScript

Oto co zrobiłem. Otworzyłem wersję lein repl i przeglądarkę pod numerem http://clojurescript.net/. Następnie wypróbowałem te fragmenty w obu REPL.

(time (dotimes [x 1000000] (+ 2 8))) 

(let [coll (list 1 2 3)] (time (dotimes [x 1000000] (first coll)))) 

Potem otworzył konsolę JavaScript w repl przeglądarki i napisał minimalistyczny funkcję benchmarkiem

function benchmark(count, fun) { 
    var t0 = new Date(); 
    for (i = 0; i < count; i++) { 
    fun(); 
    } 
    var t1 = new Date(); 
    return t1.getTime() - t0.getTime(); 
} 

Powrót do przeglądarki rEPL:

(defn multiply [] (* 42 1.2)) 

następnie spróbuj zarówno rodzimej JavaScript mnożenie i jego wariant clojurescript w konsoli javascript,

benchmark(1000000, cljs.user.multiply); 

benchmark(1000000, function(){ 42 * 1.2 }); 

Co znalazłem

  • Native javascript matematyki jest porównywalna z matematyki w Clojure
  • ClojureScript jest 5-10 razy wolniej niż którekolwiek z nich

Teraz moje pytanie, w jaki sposób mogę poprawić wydajność mojego programu ClojureScript?

Istnieje kilka metod, jakie uważane dotychczas

  • spaść do korzystania zmienne tablic i obiektów za kulisami javascript. (Czy to w ogóle możliwe?)
  • Powrót do korzystania z macierzystych operatorów matematycznych javascript. (Czy to w ogóle możliwe?)
  • użycie javascript tablice wyraźnie z (aget js/v 0)
  • użyć mniej ambitną realizację Clojure-for-javascript, jak https://github.com/chlorinejs/chlorine lub https://github.com/gozala/wisp generują one więcej idiomatyczne JavaScript, ale nie obsługują nazw którego używam bardzo.

Odpowiedz

10

JavaScript ma wyraźny zwrot, więc

function() { 42 * 1.2 } 

robi nic; zamiast tego należy przeprowadzić analizę porównawczą:

function() { return 42 * 1.2 } 

. Zdarza się, że jest to dokładnie to, do czego kompiluje się wersja ClojureScript, więc nie będzie żadnej różnicy (w ClojureScriptu podstawowe funkcje arytmetyczne w nie-wyższym celu są traktowane jako regularne wyrażenia JavaScript oparte na operatorze).

Teraz Clojure jest zdecydowanie szybszy od ClojureScript w tym momencie. Jednym z powodów jest to, że Clojure jest jeszcze ostrożniej dostrojony niż ClojureScript, chociaż ClojureScript poprawia się w dość szybkim tempie w tym dziale.Inną częścią jest to, że Clojure ma bardziej dojrzałe JIT, aby skorzystać (nowoczesne silniki JS, w szczególności V8, są całkiem dobre, ale nie do końca jeszcze w klasie HotSpot).

Wielkość różnicy jest jednak nieco trudna do zmierzenia; fakt, że zaangażowane są JIT oznacza, że ​​pętla z ciałem wolnym od jakichkolwiek skutków ubocznych, takich jak ten w pytaniu, prawdopodobnie zostanie zoptymalizowana, być może nawet przy pierwszym uruchomieniu (poprzez użycie wymiany na stosie , używane przez HotSpot i Myślę, że również V8 - musiałbym jednak sprawdzić, żeby się upewnić). Więc lepiej benchmarku coś jak

(def arr (long-array 1)) 

;;; benchmark this 
(dotimes [_ 1000000] 
    (aset (longs arr) 0 (inc (aget (longs arr) 0)))) 

(longs wezwanie, aby uniknąć odbicie w Clojure; może także używać ^longs podpowiedź).

Na koniec, zarówno w Clojure, jak i ClojureScript, dla niektórych rodzajów kodów szczególnie wrażliwych na wydajność najlepiej jest używać tablic natywnych i innych. Na szczęście, nie ma żadnego problemu z tego: po stronie ClojureScript, masz array, js-obj, aget, aset, make-array, można użyć :mutable metadane na polach w deftype móc set! je w organach sposobu etc.

7

ClojureScript matematyka jest JavaScript matematyki. Tak, jeśli wydajność ma kluczowe znaczenie, skorzystaj z tablic JavaScript i dostarczonych operatorów niskopoziomowych, gwarantując w miarę możliwości tworzenie optymalnego kodu (tj. Bez użycia wyższego rzędu). Trwałe struktury danych ClojureScript są zapisywane w następujący sposób: mutacja macierzy, arytmetyka, twiddling bitów.

Mam mały przykład wydajnego ClojureScript - http://github.com/swannodette/cljs-stl/blob/master/src/cljs_stl/spectral/demo.cljs, który może okazać się przydatny jako przewodnik.

+1

Myślałem, że masz gdzieś normę widmową w ClojureScript! +1 do tego. –

Powiązane problemy