Code Monkey home page Code Monkey logo

Comments (15)

rybandrei2014 avatar rybandrei2014 commented on July 19, 2024 1

This piece of code freezes the repl when audio-samples array is big enough:

(->> (python/enumerate audio-samples)
     (map (fn [[idx item]]
            (->> audio-frame-rate
                 (/ idx)
                 (* twoPiF)
                 Math/sin
                 (* item)
                 int)))
      doall
      (arr/array array-type))

I tried both implementations and ended up with one that relies on run-simple-string as it was the fastest one. It's approximately 25-30 times faster than my original implementation, so I am more than satisfied with performance.
I will definitely stop by help wanted topic for future research, thanks for a tip and your help :)

from libpython-clj.

jjtolton avatar jjtolton commented on July 19, 2024

If you could share a little bit more context then I could do some benchmarks :)

My guess thought is that this part is adding a lot of unneccessary expensive overhead:

(-> (libpy/PySequence_GetItem samples idx)
                                            wrap-pyobject
                                            ->jvm)

Maybe try something like:

(require '[libpython-clj.require :refer [require-python import-python]])
(require '[libpython-clj.python :as py])
(import-python)
(->> (python/enumerate samples)
     (map (fn [[idx sample]]
               (let [time (/ idx frame-rate)
                     gain (* scale
                             (Math/sin (* twoPiF time)))]
                 (int (* gain sample))))))

map-indexed is another option

(map-indexed (fn [idx sample]
                 (let [time (/ idx frame-rate)
                       gain (* scale
                               (Math/sin (* twoPiF time)))]
                   (int (* gain sample))))
               samples)

The key is, don't do

(-> (libpy/PySequence_GetItem samples idx) wrap-pyobject ->jvm)

if you don't have to!

from libpython-clj.

rybandrei2014 avatar rybandrei2014 commented on July 19, 2024

Thank you for a quick reply :) Unfortunately, I already tried both implementations:

  • map-indexed into array.array is two times slower than my implementation
  • enumerate into map into array.array freezes the repl when lazy-seq is evaluated...

The main problem I'm solving is to find the way to traverse the python array straight away without creating intermediate clojure vector, as I have to get an output in a form of python array...

So I tried this implementation in order to work straight away with input python array, but it was also slow and inefficient...

(with-gil
          (dorun (map-indexed
                  (fn [idx item]
                    (let [time (/ idx audio-frame-rate)
                          gain (* scale
                                  (Math/sin (* twoPiF time)))]
                      (libpy/PySequence_SetItem samples
                                                idx
                                                (int (* gain
                                                        item))
                                                )))
                  samples))
          samples)

Is there a better way to perform such processing?

P.S. By the way samples is just a python array of type "i", which contains samples of audio data with sample width of 2 bytes:

samples = array.array("i", audio_data)

from libpython-clj.

jjtolton avatar jjtolton commented on July 19, 2024

Do you think you could post the code that causes the freeze? I'd like to look at that separately.

So -- in general what I will say is that overall, libpython-clj is heavily optimized for exploratory development and (ideally) predictable behavior perhaps at the expense of clock cycles in certain circumstances (although we're working on it).

There are a few optimizations if you're willing to sacrifice some readability. First, you can save quite a bit of overhead if you're willing to use a Python function and keep the data "in Python". Secondly, while lists and vectors are typically copied, more complex objects are bridged instead of copied. So, you might want to consider populating an array type followed by a doto. This should also save a good amount of overhead.

For the first example:

(let [{{:strs [calculate_gains]} :globals}
(py/run-simple-string "
import math
def calculate_gain(samples, audio_frame_rate, scale, twoPiF, idx):
    time = idx / audio_frame_rate
    gain = scale * math.sin(twoPiF * time)
    samples[idx] = int(gain*time)

def calculate_gains(samples, audio_frame_rate, scale, twoPiF):
    for idx, sample in enumerate(samples):
        calculate_gain(samples, audio_frame_rate, scale, twoPiF, idx)
")] 
    (def calculate-gains calculate_gains))

(my parens may be unbalanced, apologies)

Second option (zero copy route) may look something like:

(doto (arr/array samples)
  step1
  step2 
  step3
  ...etc)

I realize that may be a bit unsatisfactory -- this is partly where the need for library authors comes into play for making clean APIs on top of domain specific applications. @cnuernber is excellent at heavy data processing, he might have some additional suggestions.

from libpython-clj.

jjtolton avatar jjtolton commented on July 19, 2024

I'd also encourage you to stop by our help wanted topic. Lots of smart folks in there. Since you're working with numpy I'm sure we can leverage that since it's blazingly fast!

from libpython-clj.

jjtolton avatar jjtolton commented on July 19, 2024

Glad to hear it! One other thing I'd like to add is if you don't like the run-simple-string approach you can also move that code to a .py file and load it in with require-python! You just have to make sure it's on your PYTHONPATH.

We're hoping in the future that library authors will provide some cool APIs for things like audio processing. If you're interested in working on that or know anyone who is let me know :)

from libpython-clj.

cnuernber avatar cnuernber commented on July 19, 2024

Is that python array something that converts to a numpy object? For large arrays using numpy and zerocopy pathways is going to be a lot faster plus then you can use a parallel-for type thing to mutably update all the elements in place.

from libpython-clj.

cnuernber avatar cnuernber commented on July 19, 2024

Closing this as it appears @dragoon000320 is satisfied :-).

Lots of options, run-simple-string, :reload with your own custom python module, and zerocopy pathways to get a tensor/nio buffer you can manipulate however you like.

from libpython-clj.

rybandrei2014 avatar rybandrei2014 commented on July 19, 2024

@jjtolton I am currently working on implementation of clojure wrappers for some solid Python audio processing libraries. So yes I am interested in working on that :)

from libpython-clj.

rybandrei2014 avatar rybandrei2014 commented on July 19, 2024

@cnuernber if it's okay, could you please provide more info regarding zerocopy pathways and parallel-for, as I am not familiar with these. Maybe you can redirect me somewhere, where I read about it... Thank you in advance :)

from libpython-clj.

cnuernber avatar cnuernber commented on July 19, 2024

@dragoon000320 - Not directly but I wouldn't mind writing up a short document on how to do your exact pathway assuming 'array' is (or is convertible to without copying the data) a numpy array.

from libpython-clj.

cnuernber avatar cnuernber commented on July 19, 2024
;; ======================================================================
user> (require '[libpython-clj.python :as py])
nil
user> (require '[libpython-clj.require :refer [require-python]])
nil
user> (require '[tech.v2.datatype :as dtype])
nil
user> (require '[tech.parallel.for :refer [parallel-for]])
nil
user> (require-python '[numpy :as np])
:ok
user> (def samples (np/linspace 0 4 :num 10000 :dtype :float32))
#'user/samples
user> samples
[0.0000000e+00 4.0004001e-04 8.0008002e-04 ... 3.9991999e+00 3.9995999e+00
 4.0000000e+00]
user> (def nio-buf (dtype/as-nio-buffer samples))
#'user/nio-buf
user> nio-buf
#object[java.nio.DirectFloatBufferU 0x6efd3494 "java.nio.DirectFloatBufferU[pos=0 lim=10000 cap=10000]"]
user> (let [audio-frame-rate (float 1000)
            twoPiF (float (* 2 Math/PI))
            scale (float 1.5)]
        (parallel-for 
         idx
         (dtype/ecount nio-buf)
         (let [time (/ idx audio-frame-rate)
               gain (* scale (float (Math/sin (* twoPiF time))))]
           (.put nio-buf idx gain))))
nil
user>
user> samples
[ 0.          0.00942472  0.01884906 ... -0.02827004 -0.01884644
 -0.00942209] 

from libpython-clj.

cnuernber avatar cnuernber commented on July 19, 2024

There is a lot there that is key to getting things fast. Using explicit typing: (float x) is key, don't use ^float x x.

Also putting unchecked-math at the top is going to have an effect in this case.

If you want to really go fast then use the primitives from fastmath for the math. Same outline, just require/use fastmath for Math/sin and friends.

from libpython-clj.

rybandrei2014 avatar rybandrei2014 commented on July 19, 2024

@cnuernber Thank you for your help and effort, I will definitely try it in my code and also take a look at the tech API :)

from libpython-clj.

cnuernber avatar cnuernber commented on July 19, 2024

@dragoon000320 - you are welcome!

Also, there are some serious experts lurking on the zulip libpython-clj-dev area so I may have missed something or who knows but opening up the problem to more eyes will get some insightful comments I am sure.

from libpython-clj.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.