On Fri, Oct 9, 2009 at 13:38, andi.xeno...@googlemail.com
<andi.xeno...@googlemail.com> wrote:
>
> Hi,
>
> I'm new to Clojure, and let me first say that I love it! At least I
> love the language, but I have some concerns regarding performance:
>
> My first try was to implement a Gauß elemination algorithm for solving
> a system of linear equations. Here is the code:
>
> http://www.xenoage.com/extern/zongblog/matrix.clj
>
> Solving 1.000.000 SLEs (see last line) took 33.000 ms on my machine,
> while the corresponding algorithm written in Java needed less than 300
> ms.
>
> Since I am a Java programmer and I have no experience in functional
> programming, I probably have to apologize for the bad code. Anyway, I
> already tried to make it faster using the well-known performance tips
> (like type hints), but they did not really help (ok, 25.000 ms instead
> of 33.000, but it is still too much).
>
> What are my options?
> - Can you identify problems in my code? I do not mean things that make
> it 10% faster, but at least double as fast.
> - Could you say, that Clojure is not made for such numerical things,
> and I should use Java code for these performance-critical algorithms?
> (would be perfectly ok for me)
> - Should I try it in Clojure, but using a (mutable) Java array instead
> (also ok for me, since this mutable structure is only used locally and
> within a single thread)

(I'm still a Clojure noob too, so...)

This is the kind of code, I would just write in Java.

However: your Clojure implementation makes use of exact arithmetic,
i.e. rational numbers. Does the Java version do that too, or is it
using floating point? If so, then the two benchmarks are not directly
comparable.

I hacked up your implementation to use http://clojure.org/transients
which were added to Clojure after the 1.0 release. This helped
performance some. See comments in code below.

// Ben

--------------------------------------

(ns matrixt)

; This module applies the Gauss algorithm to a matrix to solve a SLE.
;
; @author Andreas Wenger
; (hacked up to use transients -- bsmith.o...@gmail.com)

;; Used transients instead of normal persistent datastructures within
;; solver since transients are more efficient to update in place at
;; the cost of invalidating previous versions of themselves. (thus not
;; persistent.)
;;
;; This has helped performance, but nothing earth-shattering. Haven't
;; tried type hinting -- or indeed profiling.  Also, this is the first
;; time I've tried to use transients for something and I find the results
;; rather ugly. (tvec-map! especially)
;;
;; On a 1.6 GHz Atom (Dell Mini 9)
;; (do (time (dotimes [_ 100000] (matrixt/solve matrixt/testsle2)))
;;     (time (dotimes [_ 100000] (matrix/solve  matrix/testsle2))))
;; "Elapsed time: 12816.971679 msecs"
;; "Elapsed time: 18283.428481 msecs"

; matrix to solve:
; AAA b
; AAA b
; AAA b
(defstruct sle :A :b)
(def testsle1 (struct sle [[3 2][2 4]] [1 7]))
(def testsle2 (struct sle [[1 2 3][2 5 7][8 2 1]] [1 7 3])) ;
solution: [-16/9 110/9 -65/9]
(def testsle3 (struct sle [[1 2 3][0 1 3][0 0 1]] [2 4 6]))

(defn tvec-map!
  "Map function over elements of tvec (a transient vector). Replace
  elements from tvec with corresponding result computed by f."
  ([f tvec]
     (let [n (count tvec)]
       (loop [i 0, tvec tvec]
         (if (< i n)
           (recur (inc i) (assoc! tvec i (f (nth tvec i))))
           tvec))))
  ([f tvec1 tvec2]
     (let [n (count tvec1)]
       (loop [i 0, tvec tvec1]
         (if (< i n)
           (recur (inc i) (assoc! tvec i (f (nth tvec i) (nth tvec2 i))))
           tvec)))))

(defn sle-transient [s]
  "Transform SLE structmap into a transient map containing transient
  vectors for faster inplace updating."
  (transient {:A (transient (vec (map transient (:A s))))
              :b (transient (:b s))}))

(defn sle-persistent! [s]
     (struct sle
             (persistent! (tvec-map! persistent! (:A s)))
             (persistent! (:b s))))

(defn normalize-line!
  "Converts the line of a given SLE (parameter s) with the given
   index (parameter l) into the form that the column with the given
   index has a 1."
  [s l]
  (let [dividend (get-in s [:A l l])
        div #(/ % dividend)]
    (-> s
        (assoc! :A (assoc! (:A s) l (tvec-map! div (get-in s [:A l]))))
        (assoc! :b (assoc! (:b s) l (div (get-in s [:b l])))))))

(defn subtract-line!
  "Subtracts the line with the given index (ldest) of a given
  SLE (parameter s) by an appropriate multiple of the line with index
  lsrc"
  [s lsrc ldest]
  (let [factor (get-in s [:A ldest lsrc])
        minus #(- %1 (* factor %2))]
    (-> s
        (assoc! :A (assoc! (:A s) ldest (tvec-map! minus
                                                   (get-in s [:A ldest])
                                                   (get-in s [:A lsrc]))))
        (assoc! :b (assoc! (:b s) ldest (minus (get-in s [:b ldest])
                                               (get-in s [:b lsrc])))))))

(defn forward-lines!
  "Converts the lines of a given SLE (parameter s) after the given
   index (parameter l) into a form with the first l columns containing
   0. This is done by subtracting an appropriate multiple of the line
   with index l"
  [s l]
  (let [n (count (:A s))]
    (loop [acc s, i (inc l)]
      (if (>= i n)
        acc
        (recur (subtract-line! acc l i) (inc i))))))

(defn forward-lines
        [s l]
        (sle-persistent! (forward-lines! (sle-transient s) l)))

(defn forward!
  "Converts the given SLE (parameter s) into a triangular form"
  [s]
  (let [n (count (:A s))]
    (loop [acc s, i 0]
      (if (>= i n)
        (normalize-line! acc (dec n))
        (recur (forward-lines! (normalize-line! acc i) i) (inc i))))))

(defn backward-lines!
  "Converts the lines of a given SLE (parameter s) before the given
   index (parameter l) into a form with the last n-l columns
   containing 0 (n is the number of variables in the SLE).  This is
   done by subtracting an appropriate multiple of the line with index l"
  [s l]
  (loop [acc s, i (dec l)]
    (if (< i 0)
      acc
      (recur (subtract-line! acc l i) (dec i)))))

(defn backward!
  "Backward substitution"
  [s]
  (let [n (count (:A s))]
    (loop [acc s, i (dec n)]
      (if (< i 1)
        acc
        (recur (backward-lines! acc i) (dec i))))))

(defn solve
        "Solves the given SLE"
        [s]
        (-> s sle-transient forward! backward! sle-persistent! :b))

;(time (dotimes [_ 1000000] (solve testsle2)))

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to