Cómo mejorar el rendimiento de ClojureScript
Frecuentes
Visto 3,582 equipos
7
Empecé a usar ClojureScript recientemente. Cuando reescribí un programa de JavaScript en ClojureScript, me preocupé por el rendimiento de ClojureScript.
Código ClojureScript
(def NUM 10000)
(def data
(vec (repeatedly NUM #(hash-map :x (rand) :y (rand)))))
(.time js/console "cljs")
(loop [x 0 y 0 d data]
(if (empty? d)
[x y]
(recur (+ x (:x (first d)))
(+ y (:y (first d)))
(rest d))))
(.timeEnd js/console "cljs")
Código JavaScript compilado (optimizaciones: espacios en blanco)
benchmark_cljs.benchmark.NUM = 1E4;
benchmark_cljs.benchmark.data = cljs.core.vec.call(null, cljs.core.repeatedly.call(null, benchmark_cljs.benchmark.NUM, function() {
return cljs.core.PersistentHashMap.fromArrays.call(null, [new cljs.core.Keyword(null, "x", "x", 1013904362), new cljs.core.Keyword(null , "y", "y", 1013904363)], [cljs.core.rand.call(null), cljs.core.rand.call(null)]);
}));
console.time("cljs");
var x_4753 = 0;
var y_4754 = 0;
var d_4755 = benchmark_cljs.benchmark.data;
while (true) {
if (cljs.core.empty_QMARK_.call(null, d_4755)) {
new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [x_4753, y_4754], null);
} else {
var G__4756 = x_4753 + (new cljs.core.Keyword(null, "x", "x", 1013904362)).cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null, d _4755));
var G__4757 = y_4754 + (new cljs.core.Keyword(null, "y", "y", 1013904363)).cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null, d _4755));
var G__4758 = cljs.core.rest.call(null, d_4755);
x_4753 = G__4756;
y_4754 = G__4757;
d_4755 = G__4758;
continue;
}
break;
}
console.timeEnd("cljs");
Código JavaScript
var NUM = 10000;
var data = [];
for (var i = 0; i < NUM; i++) {
data[i] = {
x: Math.random(),
y: Math.random()
}
}
console.time('js');
var x = 0;
var y = 0;
for (var i = 0; i < data.length; i++) {
x += data[i].x;
y += data[i].y;
}
console.timeEnd('js');
El código ClojureScript y el código JavaScrpt están haciendo las mismas cosas, pero cada tiempo de proceso es diferente.
Tiempo de procesamiento
ClojureScript(optimizations :whitespace): 30 〜 70ms
ClojureScript(optimizations :advanced): 9 〜 13ms
JavaScript: 0.3ms 〜 0.9ms
Dígame cómo mejorar el tiempo de procesamiento de ClojureScript.
Gracias de antemano.
2 Respuestas
13
Está utilizando estructuras de datos persistentes en ClojureScript y matrices y objetos mutables en JavaScript. Es de esperar que las características de rendimiento de los dos fragmentos sean diferentes.
Ahora, si el rendimiento es realmente crítico para lo que estás haciendo y la persistencia no proporciona ningún beneficio, solo puede usar matrices y objetos de ClojureScript:
(def NUM 10000)
(def data (array))
(loop [i 0]
(when (< i NUM)
(aset data i (js-obj "x" (js/Math.random) "y" (js/Math.random)))
(recur (inc i))))
(let [lim (alength data)]
(loop [x 0 y 0 i 0]
(if (< i lim)
(recur (+ x (aget data i "x"))
(+ y (aget data i "y"))
(inc i))
(println x y))))
Por otro lado, si necesita conservar versiones antiguas de las estructuras de datos involucradas, probablemente recuperará el "tiempo perdido" al no tener que hacer copias completas para preservarlas.
Respondido 12 Feb 14, 07:02
Estaba pensando que tal vez un for
la comprensión se compilaría muy bien en JavaScript, algo así como: (def data (vec (for [x (range NUM)] {:x (rand) :y (rand)})))
- elclanrs
Claro, funcionaría bien. El resultado final seguiría siendo un vector persistente de mapas de matrices persistentes, con todas las implicaciones de rendimiento. (Bueno, en realidad, el OP usa mapas hash muy pequeños, por lo que este enfoque probablemente funcionaría un poco mejor). Michał Marczyk
@elclanrs for
las comprensiones generan secuencias perezosas. Si bien es agradable, no verá el tipo de rendimiento demostrado por esta respuesta. - dnolen
¡Gracias, @Michał! Podría mejorar el tiempo de proceso usando "matriz" para mi programa. - rapé
6
Tiene una serie de opciones aquí dependiendo de cuánto rendimiento necesita y de lo que está dispuesto a renunciar. pongo un poco los puntos de referencia arriba en GitHub si estás interesado.
Mediante el uso de registros y acceso a campos nativos, puede reducir a la mitad el tiempo de ejecución de su solución ClojureScript original:
(defrecord XY [x y])
(def data (mapv (fn [_] (XY. (rand) (rand))) (range NUM)))
(defn sumXsAndYsWithLoopAndNativeFieldAccess [data]
(loop [x 0 y 0 data data]
(if (seq data)
(let [o (first data)]
(recur (+ x (.-x o)) (+ y (.-y o)) (rest data)))
[x y])))
(time (sumXsAndYsWithLoopAndNativeFieldAccess data))
También puede usar matrices como locales mutables y obtener una solución solo 8 veces más lenta que la versión nativa de JavaScript:
(defn sumsXsAndYsWithDotimesNativeFieldAccessAndMutableLocals [data]
(let [x (doto (make-array 1)
(aset 0 0))
y (doto (make-array 1)
(aset 0 0))]
(dotimes [i (count data)]
(let [o (data i)]
(aset x 0 (+ (aget x 0) (.-x o)))
(aset y 0 (+ (aget y 0) (.-y o)))))
[(aget x 0) (aget y 0)]))
(time (sumsXsAndYsWithDotimesNativeFieldAccessAndMutableLocals data))
Además, puede usar lo anterior junto con arreglos y lograr una solución aproximadamente 3 veces más lenta que la versión nativa de JavaScript:
(def data (into-array (mapv #(XY. (rand) (rand)) (range NUM))))
(defn sumsXsAndYsWithDotimesOnArrayNativeFieldAccessAndMutableLocals [data]
(let [x (doto (make-array 1)
(aset 0 0))
y (doto (make-array 1)
(aset 0 0))]
(dotimes [i (alength data)]
(let [o (aget data i)]
(aset x 0 (+ (aget x 0) (.-x o)))
(aset y 0 (+ (aget y 0) (.-y o)))))
[(aget x 0) (aget y 0)]))
(time (sumsXsAndYsWithDotimesOnArrayNativeFieldAccessAndMutableLocals data))
Es posible que desee ver la de David Nolen recámara proyecto. Tiene algunas macros agradables para crear y actualizar mutables locales que hacen que lo anterior no parezca ridículo.
De todos modos, espero que eso ayude.
Respondido 18 Feb 14, 06:02
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas javascript clojure clojurescript or haz tu propia pregunta.
¿Puede publicar la salida de JavaScript compilada generada por ClojureScript? - elclanrs
Agregué código JavaScript compilado. - snufkon
Dado que las estructuras de datos nativas de Clojure son inmutables, es incorrecto decir que JS y ClJS están haciendo lo mismo. Estos son dos lenguajes diferentes, incluso si ClJS está compilado en JS. - yonki