2015-01-23 10 views
7

Ich habe immer geglaubt, dass GHCJS aus naheliegenden Gründen im Vergleich zu manuell geschriebenem und optimiertem Code sehr langsame JavaScript-Programme erzeugte. Als ich damit experimentierte, bemerkte ich, dass es nicht so schlimm war, wie ich es erwartet hatte. Ich beschloss, eine Reihe kleiner Benchmarks zu machen, um die wahre Leistung zu verstehen, und diese überraschte mich besonders. Das Programm füllt einfach ein Array mit "1" und summiert sie.Wie kann dieses Haskell-Programm, kompiliert zu JavaScript, schneller sein als JavaScript selbst?

Haskell:

import Data.Array.Repa 
len = 1024*1024*64 
arr = fromFunction (Z :. len) (const 1) :: Array D DIM1 Float 
main = sumAllP arr >>= print 

JavaScript:

var len = 1024*1024*64 
var arr = []; 
var sum = 0; 
for (var i=0; i<len; ++i) 
    arr[i] = 1; 
for (var i=0; i<len; ++i) 
    sum += arr[i]; 
console.log(sum); 

Und eine grobe Benchmark:

apple1$ ghcjs -O2 bench_fill.hs -funfolding-use-threshold10000 -funfolding-keeness-factor1000 -o bench_fill.js; time node bench_fill.js/all.js 
Linking bench_fill.js (Main) 
6.7108864e7 

real 0m1.543s 
user 0m1.512s 
sys 0m0.033s 

apple1$ time node benchfill.js 
67108864 

real 0m1.764s 
user 0m1.173s 
sys 0m0.583s 

Wie können die GHCJS schneller laufen als eine schlanke, saubere nativen for-Schleife? Das sollte nicht möglich sein, wenn man bedenkt, wie viele Felder der generierte Code enthalten sollte.

+1

Sind Sie sicher, dass der Haskell-Compiler den Code zum Drucken des Endergebnisses nicht optimiert? In jedem Fall müssen wir wahrscheinlich den generierten Code sehen, um herauszufinden, warum er schneller ist. – JJJ

+9

Geben Sie bitte die Ausgabe .js von ghcjs ein. So beantwortet man die Frage. –

+1

[This] (https://gist.github.com/viclib/3023b1c44daf7f33ede4) ist die Ausgabe js. [This] (https://gist.github.com/viclib/9108f02fb1655cdc0787) auch, außer dass das gesamte Array nach dem Drucken der Summe gedruckt wird, um sicherzustellen, dass es gefüllt ist. – MaiaVictor

Antwort

4

Array D DIM1 Float ist ein delayed array. Es wird nur als die Funktion const 1 plus die Grenzen des Arrays dargestellt. Es gibt kein Array von 64 Millionen Floats irgendwo gespeichert.

Das JavaScript-Programm erstellt tatsächlich ein Array von 64 Millionen Doppel, die 512 MB Speicher verwendet. Die Kosten für das Lesen und Schreiben eines so großen Arrays sind nicht zu vernachlässigen (ebenso wie die Kosten für die Zuteilung; beachten Sie die erhebliche Systemzeit).

+1

Ah, ich sehe - mein Fehler ging davon aus, dass 'sumAllP' das Array vorher berechnet hat. Der Aufruf von 'computeP' macht es langsamer - wenn auch immer noch um eine kleine (4x) Marge. Aber jetzt habe ich noch etwas Unerwartetes gefunden: Die Verdoppelung der Länge lässt das JavaScript-Programm nicht mehr voll laufen, während das Haskell-Programm (mit computeP), kompiliert nach JavaScript, dies nicht tut. Ich bin mir sicher, dass computeP das Array tatsächlich berechnet, weil es es druckt, also ... das ist für mich ein Rätsel. – MaiaVictor

+1

Es kommt nicht darauf an, wann das Array ausgewertet wird. 'arr' wird nicht physikalisch als Array gespeichert, unabhängig davon, wie viel oder wie wenig Sie es auswerten. –

+1

Wenn Sie ein physisches Array möchten, können Sie eines der anderen Speicherformate wie 'U' (anstelle von' D') verwenden. –