为什么这个Clojure程序在可变数组上工作这么慢?

时间:2015-12-08 10:21:33

标签: scala clojure

剧透警报,这是第6天来临的第一部分。

我尝试在Clojure和Scala中解决this问题。 Scala程序运行良好,并在我的Macbook Air上几秒钟内完成。然而,Clojure版本需要更长的时间。这有什么问题?

(ns adventofcode.day6
  (:require [clojure.java.io :as io]
            [clojure.string :as string]))

(set! *warn-on-reflection* true)

(def ^:const grid-size 1000)

(defn make-grid []
  (let [grid (make-array Long/TYPE grid-size grid-size)]
    (doseq [i (range grid-size)
            j (range grid-size)]
      (aset grid i j -1))
    grid))

(defn parse-line [l]
  (let [[command left-top right-bottom]
        (-> l
            (string/replace "turn off" "turn-off")
            (string/replace "turn on" "turn-on")
            (string/replace "through " "")
            (string/split #" "))
        parse-coordinates (fn [s]
                            (let [parts (string/split s #",")]
                              (map #(Integer/parseInt %) parts)))
        [left top] (parse-coordinates left-top)
        [right bottom] (parse-coordinates right-bottom)]
    [command left top right bottom]))

(defn process-line [grid command left top right bottom]
  (doseq [i (range left (inc right))
          j (range top (inc bottom))]
    (case command
      "turn-off" (aset grid i j -1)
      "turn-on"  (aset grid i j 1)
      "toggle"   (aset grid i j (* -1 (aget grid i j)))
      (throw (Exception. "unrecognized command")))))

(defn count-lights [grid]
  (reduce + (for [i (range grid-size)
                  j (range grid-size)
                  :let [value (aget grid ^Long i ^Long j)]
                  :when (pos? value)]
              value)))

(defn the-answer []
  (with-open [rdr (-> "input-day6.txt"
                      io/resource
                      io/reader)]
    (let [grid (make-grid)]
      (doseq [l (line-seq rdr)]
        (apply process-line grid
               (parse-line l)))
      (count-lights grid))))
编辑:我已经重写了瞬态向量的解决方案,它具有相当不错的性能(Macbook Air上11秒,Macbook Pro上7秒)

(ns adventofcode.day6faster
  (:require [clojure.java.io :as io]
            [clojure.string :as string]))

(set! *warn-on-reflection* true)

(def ^:const grid-size 1000)

(defn make-grid []
  (vec (repeat (* grid-size grid-size) -1)))

(defn parse-line [l]
  (let [[command left-top right-bottom]
        (-> l
            (string/replace "turn off" "turn-off")
            (string/replace "turn on" "turn-on")
            (string/replace "through " "")
            (string/split #" "))
        parse-coordinates (fn [s]
                            (let [parts (string/split s #",")]
                              (map #(Integer/parseInt %) parts)))
        [left top] (parse-coordinates left-top)
        [right bottom] (parse-coordinates right-bottom)]
    [command left top right bottom]))

(defn set-grid! [t-grid grid-size i j v]
  (let [pos (+ i (* j (dec grid-size)))]
    (assoc! t-grid pos v)))

(defn update-grid! [t-grid grid-size i j f]
  (let [pos (+ i (* j (dec grid-size)))]
    (assoc! t-grid pos
            (f (get t-grid pos)))))

(defn process-line [t-grid command left top right bottom]
  (doseq [i (range left (inc right))
          j (range top (inc bottom))]
    (case command
      "turn-off" (set-grid! t-grid grid-size i j -1)
      "turn-on"  (set-grid! t-grid grid-size i j 1)
      "toggle"   (update-grid! t-grid grid-size i j (fn [i] (* -1 i)))
      (throw (Exception. "unrecognized command")))))

(defn count-lights [grid]
  (count (filter pos? grid)))

(defn the-answer []
  (with-open [rdr (-> "input-day6.txt"
                      io/resource
                      io/reader)]
    (let [t-grid (transient (make-grid))]
      (doseq [l (line-seq rdr)]
        (apply process-line t-grid
               (parse-line l)))
      (count-lights (persistent! t-grid)))))

编辑2:现在有循环,键入提示和单个可变数组。我的Macbook Air上1.5秒!

(ns adventofcode.day6singlearray
  (:require [clojure.java.io :as io]
            [clojure.string :as string]))

(set! *warn-on-reflection* true)
(set! *unchecked-math* :warn-on-boxed) 

(def ^:const grid-size 1000)

(defn make-grid []
  (let [l (* grid-size grid-size)
        a (make-array Long/TYPE l)]
    (loop [i 0]
      (if (< i l)
        (do (aset ^longs a i -1)
            (recur (inc i)))
        a))))

(defn parse-line [l]
  (let [[command left-top right-bottom]
        (-> l
            (string/replace "turn off" "turn-off")
            (string/replace "turn on" "turn-on")
            (string/replace "through " "")
            (string/split #" "))
        parse-coordinates (fn [s]
                            (let [parts (string/split s #",")]
                              (map #(Integer/parseInt %) parts)))
        [left top] (parse-coordinates left-top)
        [right bottom] (parse-coordinates right-bottom)]
    [command left top right bottom]))

(defn set-grid! [grid ^long i ^long j v]
  (let [pos (+ i (* j (dec grid-size)))]
    (aset ^longs grid pos ^long v)))

(defn toggle-grid! [grid ^long i ^long j]
  (let [pos (+ i (* j (dec grid-size)))]
    (aset ^longs grid pos
             ^long (* -1 (aget ^longs grid pos)))))

(defn process-line [grid command left top right bottom]
  (let [operation (case command
                    "turn-off" #(set-grid! grid %1 %2 -1)
                    "turn-on"  #(set-grid! grid %1 %2 1)
                    "toggle"   #(toggle-grid! grid %1 %2)
                    (throw (Exception. "unrecognized command")))]
  (loop [^long i left
         ^long j top]
    (if (<= i ^long right)
      (if (<= j ^long bottom)
        (do (operation i j)
            (recur i (inc j)))
        (recur (inc i) top))))))    

(defn count-lights [grid]
  (count (filter pos? grid)))

(defn the-answer []
  (with-open [rdr (-> "input-day6.txt"
                      io/resource
                      io/reader)]
    (let [grid (make-grid)]
      (doseq [l (line-seq rdr)]
        (apply process-line grid
               (parse-line l)))
      (count-lights grid))))

2 个答案:

答案 0 :(得分:1)

一个Java原始数组对我来说足够好(在便宜的4-yo笔记本电脑上半秒钟)。我没有打扰unbox索引。

(set! *unchecked-math* true)

(defn turn-off [^ints grid [r1 c1 r2 c2]]
  (doseq [i (range r1 (inc r2))
          k (range (+ c1 (* i 1000)) (+ c2 (* i 1000) 1))]
    (aset grid k 0)))

(defn turn-on [^ints grid [r1 c1 r2 c2]]
  (doseq [i (range r1 (inc r2))
          k (range (+ c1 (* i 1000)) (+ c2 (* i 1000) 1))]
    (aset grid k 1)))

(defn toggle [^ints grid [r1 c1 r2 c2]]
  (doseq [i (range r1 (inc r2))
          k (range (+ c1 (* i 1000)) (+ c2 (* i 1000) 1))]
    (aset grid k (- 1 (aget grid k)))))

(defn count-on [^ints grid]
  (areduce grid i cnt 0 (+ cnt (aget grid i))))

(defn day06a []
  (let [grid (int-array (* 1000 1000))] ; 0-initialized via Java
    (with-open [rdr (clojure.java.io/reader "input/input06.txt")]
      (doseq [cmd (line-seq rdr)]
        (let [v (map #(Integer/parseInt %) (re-seq #"[0-9]+" cmd))]
          (cond (.startsWith cmd "turn off") (turn-off grid v)
                (.startsWith cmd "turn on")  (turn-on grid v)
                (.startsWith cmd "toggle")   (toggle grid v)))))
    (count-on grid)))

答案 1 :(得分:0)

注意:

  • 使用

    (set! *warn-on-reflection* true)
    (set! *unchecked-math* :warn-on-boxed)
    

发现使用反射或盒装数学并相应地进行优化。

  • 在适当的位置添加类型提示。

  • 使用循环/重复而不是序列

具有与等效Java或Scala程序相同的性能特征的代码:

(ns adventofcode.day6singlearray
  (:require [clojure.java.io :as io]
            [clojure.string :as string]))

(set! *warn-on-reflection* true)
(set! *unchecked-math* :warn-on-boxed) 

(def ^:const grid-size 1000)

(defn make-grid []
  (let [l (* grid-size grid-size)
        a (make-array Long/TYPE l)]
    (loop [i 0]
      (if (< i l)
        (do (aset ^longs a i -1)
            (recur (inc i)))
        a))))

(defn parse-line [l]
  (let [[command left-top right-bottom]
        (-> l
            (string/replace "turn off" "turn-off")
            (string/replace "turn on" "turn-on")
            (string/replace "through " "")
            (string/split #" "))
        parse-coordinates (fn [s]
                            (let [parts (string/split s #",")]
                              (map #(Integer/parseInt %) parts)))
        [left top] (parse-coordinates left-top)
        [right bottom] (parse-coordinates right-bottom)]
    [command left top right bottom]))

(defn set-grid! [grid ^long i ^long j v]
  (let [pos (+ i (* j (dec grid-size)))]
    (aset ^longs grid pos ^long v)))

(defn toggle-grid! [grid ^long i ^long j]
  (let [pos (+ i (* j (dec grid-size)))]
    (aset ^longs grid pos
             ^long (* -1 (aget ^longs grid pos)))))

(defn process-line [grid command left top right bottom]
  (let [operation (case command
                    "turn-off" #(set-grid! grid %1 %2 -1)
                    "turn-on"  #(set-grid! grid %1 %2 1)
                    "toggle"   #(toggle-grid! grid %1 %2)
                    (throw (Exception. "unrecognized command")))]
  (loop [^long i left
         ^long j top]
    (if (<= i ^long right)
      (if (<= j ^long bottom)
        (do (operation i j)
            (recur i (inc j)))
        (recur (inc i) top))))))    

(defn count-lights [grid]
  (count (filter pos? grid)))

(defn the-answer []
  (with-open [rdr (-> "input-day6.txt"
                      io/resource
                      io/reader)]
    (let [grid (make-grid)]
      (doseq [l (line-seq rdr)]
        (apply process-line grid
               (parse-line l)))
      (count-lights grid))))