clojure-android / lein-droid

A Leiningen plugin for building Clojure/Android projects
Eclipse Public License 1.0
645 stars 56 forks source link

Clojure on Android seems ... well slow #68

Closed zoejules closed 10 years ago

zoejules commented 10 years ago

Firstly I apologize creating this issue I couldn't find a better place for this. I suspect it is an issue but It's easily possible that I messed up something (being a beginner in clojure).

So basically the following code takes about 0.2 msecs on a nexus 4:

(apply vector (apply map vector [[1 2 3][1 2 3][1 2 3]])) ;we want to transpose a matrix

In a normal (desktop) leiningen project the same codes runs literally 100 times faster (0.002 msec).

I can hardly believe that a 1 Ghz ARM processor is 100 times slower than a 3.3 Ghz Core-i5 desktop processor. I know it's apples and oranges but still it's very surprising.

Maybe there is a better way of transposing a matrix but the thing is we measured slow times with other functions too.

We are currently developing a game and we have to do every frame within about 33 msecs to have 30 FPS.

So I guess the execution time of the above code should be one magnitude faster in order to be usable in real-time applications.

EDIT:

We've tried a similar algorithm with java:

public static double[][] transposeMatrix(double [][] m){ double[][] temp = new double[m[0].length][m.length]; for (int i = 0; i < m.length; i++) for (int j = 0; j < m[0].length; j++) temp[j][i] = m[i][j]; return temp; }

with this input: new double[][]{{1,2,3},{4,5,6},{7,8,9}}

this took about 0.02 so it was 10 times faster but the reason for this might be the difference between the too implementations.

So now we're officially confused.

alexander-yakushev commented 10 years ago

Hello,

You are really kind of comparing apples and oranges, and primarily because your Clojure implementation uses immutable sequences, while the one in Java - mutable arrays. Immutable collections will always be slower, and I'm pretty sure if you are developing a game, you want to use arrays.

Clojure has a basic support for arrays, although the code with them becomes totally disgusting. But it can be improved with a few tailored macros. Here's a terrible example, but I've managed to cut execution time to ~0.05 msec on my Nexus 7:

(let [^"[[Ljava.lang.Object;" mat (to-array-2d (repeat 3 (range 1 4)))]
  (let [n (int (count mat))
        m (int (count (aget mat 0)))
        t (make-array Long/TYPE m n)]
    (loop [i 0 j 0]
      (if (>= i n)
        t
        (if (>= j m)
          (recur (inc i) 0)
          (do
            (aset ^"[J" (aget ^"[[J" t j) i
                  (long (aget ^"[Ljava.lang.Object;" (aget mat i) j)))
            (recur i (inc j))))))
    t))

But if I were you, and still wanted to use Clojure for developing an Android game, I would write the most performance-critical code on the Java side. As you can see, working with arrays is actually much easier in Java.

zoejules commented 10 years ago

Thank you for your detailed answer and taking the time with the code example, I see that it's complicated though. We really considered abandoning clojure in favor of java at some (many) point. We don't want to lose the REPL and we've also developed a small application which monitors files for change and sends it directly to the device (if you are interested I can send it to you with source code). So now the hybrid solution what you've proposed seems best. (It also worth mentioning that we tried integrating Light Table with lein-droid but it has it's own nrepl middleware and we decided it didn't worth the effort at that point)

BTW We also tried transients but they didn't help much. The following (admittedly stupid) code takes 0.1 msec

(defn transpose2 [mat]
  (let [transient-mat (transient mat)]
    (persistent! (assoc! transient-mat 1 (transient-mat 3) 
                                       3 (transient-mat 1)
                                       2 (transient-mat 6)
                                       6 (transient-mat 2)
                                       5 (transient-mat 7)
                                       7 (transient-mat 5)))
  )
)

But I see now that we have to go with arrays anyway. Sorry for the rambling I'm super tired.

alexander-yakushev commented 10 years ago

No problem. I wish I could help more.

Speaking of transients, they don't really shine on small collections, because transients themselves require pretty complex bookkeeping. If the collections are small, it eats away the benefit. An excellent book The Joy of Clojure contains a separate chapter about performance optimizations, I recommend reading it before trying to boost performance in Clojure.

And about the tool you mentioned, sure, if you open sourced it, then I would love to take a look.

zoejules commented 10 years ago

Thank you for your help! I guess you can close this case. As for the tool I mentioned I would be too embarassed to publish it it's very clumpsy... :) But if you are interested I will send it to you in a zip file or share it in google docs. BTW it's in c#. It's only value is to see some code examples how to communicate with an NREPL server through a socket in .Net.

Anyway is there a better place to ask public questions? I still have one question: what is your workflow when you use lein-droid? Do you run a "lein droid doall" and then use the command line repl to update defn's etc. or you have some editor with integrated repl support?

alexander-yakushev commented 10 years ago

Step by step:

  1. The tool: sure, if you don't want to publish it, just send me an archive. I used to work with C#, so I hope I will figure it out:)
  2. You can ask questions on the mailing list: https://groups.google.com/forum/#!forum/clojure-android . It is not very active, but I monitor it and sometimes you can get answers from other few people involved with CoA.
  3. I use Emacs+nrepl.el (which is Cider now). Eclipse also has a support for nRepl, via Counterclockwise plugin. I know Vim users can also connect to nRepl server through fireplace.vim. Not sure about others, but basically Clojure on Android applications have nRepl server running on their side, so anything that can connect to it can work with it. After lein droid forward-port is executed (doall does it as well), you can connect to localhost on port 9999 (by default, can be changed in project.clj and there is your REPL).