"The Grid. A digital frontier. I tried to picture clusters of information as they move through the computer. What did they look like? Ships? Motorcycles? Were the circuits like freeways? I kept dreaming of a world I thought I’d never see. And then, one day, I got in." — Tron: Legacy

2014-07-27

Java vs Clojure: Streams/Reducers

Ostatnio natrafiłem na materiał wideo porównujący kolekcje w językach Java, Scala i z użyciem biblioteki przygotowanej przez developerów firmy Goldman Sachs. Z ciekawości utowrzyłem podobny kod w Clojure by porównać wyniki. I muszę powiedzieć, że są trochę zaskakujące.
W materiale tym porównywana była wydajność kolekcji dla 1 miliona elementów. Postanowiłem utrudnić trochę i dodać 1 zero. Wyniki są podyktowane cechami mojego PC, na innych komputerach będą się różnić, być może znacznie.

Tutaj źródło: http://www.infoq.com/presentations/java-streams-scala-parallel-collections



Java:

List<Long> a;
List<Long> b;
b = LongStream.range(0, 10_000_000)

              .boxed()
              .collect(Collectors.toList());
      
a = b.parallelStream()
     .filter(e -> e % 10_000 != 0)
     .map(e -> String.valueOf(e))
     .map(e -> Long.valueOf(e))
     .filter(e -> (e + 1) % 10_000 != 0)
     .collect(Collectors.toList());


Czas wykonania ~3.8 sekundy po 30 iteracjach.

Clojure:
 - funkcje wyciągnięte na zewnątrz dla zwiększenia czytelności

(require '[clojure.core.reducers :as r])
(defn not10k [^Long x] (not= 0 (rem x 10000)))
(defn not10k1 [^Long x] (not= 0 (rem (inc x) 10000)))
(defn toString [^Long x] (java.lang.String/valueOf x))
(defn toLong [^String x] (java.lang.Long/valueOf x))

(def a (vec (range 10000000)))

(def kompozycja (comp (r/filter not10k1)
                      (r/map toLong)
                      (r/map toString)
                      (r/filter not10k)))

(into [] (kompozycja a)) ; czas mierzony tutaj

Czas wykonania ~4.7 sekundy. Po 30 iteracjach. Java tylko ok. 24% szybsza. Całkiem nieźle.

Edit:

Aby zwiekszyć wydajność kodu Clojure (prędkość ta sama jak dla Java) trzeba uzyć zwykłej tablicy typu long dla kolekcji wejściowej, więc:

(def a (vec (range 10000000)))

zamienić na:

(def a (long-array (range 10000000)))

Jako ciekawostka, kod w języku Smalltalk (Pharo 3.0):

aStream := (ReadStream on: (1 to: 10000000)) contents readStream.
 

Time millisecondsToRun:  [
   bStream := Array new writeStream.

   not10k := [ :x | (x rem: 10000) ~= 0 ].
   not10k1 := [ :x | (x + 1 rem: 10000) ~= 0  ].

   [ ( x := aStream next) notNil ]
   whileTrue: [
        ((not10k value: x) and:
         (not10k1 value: (x asString) asNumber))
        ifTrue: [ bStream nextPut:  x ]].

   b := bStream contents.]

  
~23 sekundy.

1 komentarz:

  1. Łatwy sposób na zwiększenie wydajności, muszę bardziej przyjrzeć się temu językowi. Coraz lepiej mi to idzie muszę przyznać. Tu też firma https://ermlab.com może was zainteresować jeżeli szukacie specjalistów od tworzenia aplikacji

    OdpowiedzUsuń