Hello Julien,

I am in a similar situation to yours: I am writing a Clojure library
to parse Starcraft replay files, but my Clojure code is very far from
nearly equivalent Java code.  Currently, on my home PC, parsing 1,050
files takes ~70 seconds with Clojure and about 12 with Java.  The code
is available at this URL: http://bitbucket.org/gnuvince/clj-starcraft/.

The Clojure used to take nearly three minutes (even >10 minutes at the
very beginning) to complete.  Here are some performance tips that
helped me shave off a lot of processing time.

- (set! *warn-on-reflection* true).  This is one of the most important
thing you can do; the Java reflection library is a lot slower than
direct calls (obviously), so make sure that all the type hints are in
place to have direct calls.  Put it at the top of your main script and
fix every warning it gives you.  It was, for me, the most important
change to make.  It brought down performance from over 10 minutes down
to about 3.

- In tight loops, coerce your numbers to their equivalent Java
numerical primitives.

- let bindings are a lot faster to look up than vars.  If you have a
var that is looked up very frequently, consider the following idiom to
close over the var:

(def *my-var* <some value>)
(let [local-my-var *my-var*]
  (defn my-fn [x y]
    ...))

- In tight loops, avoid using destructuring binding if you're binding
from a vector.  Consider the following snippets:

user> (let [v [1 2 3]] (time (dotimes [_ 1e6] (let [[a b c] v] (+ a b
c)))))
"Elapsed time: 226.992598 msecs"

user> (let [v [1 2 3]] (time (dotimes [_ 1e6] (let [a (v 0), b (v 1),
c (v 2)] (+ a b c)))))
"Elapsed time: 151.938344 msecs"

The second one is definitely uglier, but I was able to go down from 80
seconds to 70 in my program.

- Like David mentioned earlier, use two parameters for the math
primitives.

- -Xprof and -Xrunhprof:cpu=samples are your friends

- A tip given to me on #clojure by Cark: try to "compile" some of your
code down to fns.  I was able to get a very appreciable speed boost by
"compiling" my reader functions to fns (see compile-field and compile-
record in unpack.clj)

I hope some of these help.  I realize that not all are directly
applicable to your case, but I figure other people may find them
useful.

Cheers,

Vincent.

On May 9, 6:53 pm, Julien <julien.ma...@gmail.com> wrote:
> I'm interested to do audio synthesis and eventually audio DSP on the
> JVM using the Java sound API and I think that it could be fun to do
> that following the functional programming paradigm.
> I don't intend to build a huge library but just to try some experiment
> on my own to better understand how digital audio synthesis work.
>
> Here is a piece of code that is filling up a byte array putting into
> it audio samples calculated from the math sin function.
> I'm trying to convert this java code into clojure. While the java
> version is almost instantaneous my clojure version is way too slow. So
> I figured I must be doing something wrong.
>
> It is assuming that the audio channel is mono and sample size of 16
> bits. The sample rate is set to 44100Hz.
>
> -java code
> int EXTERNAL_BUFFER_SIZE = 128000;
> byte[]  sampleArray = new byte[EXTERNAL_BUFFER_SIZE*20];
>
> float frequency = 2000; //  Hz
> double sampleInterval = frequency/sampleRate;
> short sample;
> float impulseTrain = 0;
> for(int sampleIndex=0;sampleIndex+1<sampleArray.length;sampleIndex+=2)
> {
>         sample = (short) (Short.MAX_VALUE * Math.sin(impulseTrain * 2 *
> Math.PI));
>         sampleArray[sampleIndex] = (byte) (sample >> 8);
>         sampleArray[sampleIndex+1] = (byte) (sample & 0xFF);
>
>         impulseTrain += sampleInterval;
>         if(impulseTrain>1) impulseTrain -= 1;
>
> }
>
> -my clojure function
> (defn byte-array-sound [frequency sample-rate nb-frame]
>         (let [sample-array (make-array (. Byte TYPE) (* nb-frame 2))
>                                 sample-interval (/ frequency sample-rate)
>                                 limit (alength sample-array)]
>                 (loop [sample-index 0
>                                          impulse-train 0]
>                                 (let [sample (short (* (Short/MAX_VALUE) 
> (Math/sin (* impulse-
> train 2 (Math/PI)))))]
>                                         (aset sample-array sample-index (byte 
> (bit-shift-right sample
> 8)))
>                                         (aset sample-array (+ sample-index 1) 
> (byte (bit-and sample
> 255))))
>                                 (if (< (+ sample-index 2) limit)
>                                         (recur (inc (inc sample-index))
>                                                                  (let 
> [next-impl-tr  (+ impulse-train sample-interval)]
>                                                                          (if 
> (< next-impl-tr 1)
>                                                                               
>   next-impl-tr (- next-impl-tr 1))))
>                                         sample-array))))
>
> With a byte array of 2 560 000 bytes the java version is very fast,
> may be because of some optimizations somehow not available from
> clojure.
> I tried the clojure function with a much smaller byte array of 100 000
> bytes and it is very slow.(about 40 sec to return)
> May be clojure isn't optimize to handle primitive types as java is or
> I need to learn my clojure.
>
> I figured those functions out by myself so there might be another way
> to achieve the same thing more efficiently.
> My knowledge about audio synthesis, DSP is just growing.
>
> I'd appreciate any suggestions.
--~--~---------~--~----~------------~-------~--~----~
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
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