JavaScript performance VS max objects.

patrickkidd's icon

I am a very experienced computer programmer, and as I am becoming a moderately experienced Max For Live developer I am starting to feel like a lot of my programming tasks would be simpler to program in JS VS using Max objects. What is everyone's take on general performance of JS over native max objects? Are there times when one is better than the other? If I can write well optimized javascript is it worth porting at least the more complex app logic components in my projects to JS? Some basic design patterns are just so much easier to do in JS than using Max objects.

Having developed professional scripting engines myself, my intuition is that even though they use more 'code' the native max objects should be faster since they run in C++, while javascript is generally slower. But my experience comes from the pre-V8 era when interpreted languages were always a bit slower.

Thoughts? Thanks!

patrickkidd's icon

Wow, after easily re-writing a complex component of my patch in js, I almost don't care about the performance! Especially with being able to share function pointers via global objects, this makes things much easier to code and test.

My question still stands, though. I would love to hear about any anecdotal or empirical information on js performance in Max.

Here is a screenshot of how I imagine all max patches with complex logic look before they are ported to javascript. It looks like unreadable spaghetti...totally unmaintainable. Look familiar?

2014-11-30_19-50-46.jpg
jpg
Evan's icon

Yes it does look familiar! This is one of the reasons I started doing a lot of my logic in JS (as well as anything that requires iteration).

I too would be interested if anyone has any information on any potential performance hits we might take by doing this in JS.

P.S new to text coding - 'function pointers via global objects'. sounds fancy. care to elaborate at all?

patrickkidd's icon

A function pointer is a term from C programming that means you have a reference to a function that can be passed around your code, reassigned like a variable, called, etc. in scripting languages like JavaScript and Python, (unlike C) functions are objects just like everything else and are just another variable that you can assign to new names, pass to new objects, and of course call.

My reply was about how helpful it is to be able to create a Global object also assign a function to it so that other scripts can call it. This means that the max implementation of globals in JS is a bit more pure than just passing single attribute values back and forth between objects.

This really opens the door to more involved Ja programming in max because you can make callbacks without having to write a bunch of max-object level communication stuff.

broc's icon

I think it was mentioned somewhere that JS always runs on low priority threads.
Consequently, it should never be used for time-critical operations (?)

Lee's icon

correct - JS runs in the low pri thread along with the UI and can get blocked by UI operations. There is also a cost of crossing the boundary into js/back from js so this also needs to be factored in.

patrickkidd's icon

That inplemntation make sense as JS has to be single threaded. Also great for max for live because there is typically no GUI to hog the cpu.

I assume then that midi in Live is handled in the containing track's audio thread? Then this same thread is used to enter in Max's control-rate execution graph? And then there is an asynchronous message to go from Max's audio thread to Max's own global JavaScript thread, then the same path back to Live again?

I'm just trying to sort out worse case scenario in timing so long as best case programming practices are used.

Also, what do we know about the performance of executing the JavaScript engine's byte code by itself? What overhead are we incurring just for porting application logic from pure max objects to JavaScript?

Those questions should complete the picture.

do.while's icon

Hi !
my humble advice ... if u are going to recreate some patch logic with the code (as i do all the time) i would go for writing C external .

As if u are wondering about efficiency ? there is no question .

Javascript is very fast in Max as long as its procedures are relying on the same js document/object encapsulation . Using Global may slow it down a bit (at least ive experienced it some time ago ,and im nearly sure there was some improvements done) .

Also note that JS is processed with its interpreter engine at runtime . It means there is another layer of "computation/processing" to count in beside low priority thread issue (if thats also your concern).There is overhead but its still fast anyway .

Ive done really complex UI system that is responsive for every midi message without any real interruption . and im talking about drawing here which was my biggest concern if to touch it through JS or not . Im happy .

So for me , any "user input" stage can be threaten through JS , any "output stage" should be considered as crucial where i would pick C external writing . Which really pays off .
Here u can care for measurements or even not :)

patrickkidd's icon

Excellent response, thank you. I was looking for a basic feel of the responsiveness of the JS engine, it it sounds like overall it is quite positive. Especially if you were drawing at control rate which is probably the most hard core task someone can expect to do reliably with a good scripting engine implementation these days.

Most of the work I am doing is control rate anyway, as I'm not much of a DSP guy. But if things ever get crazy there will always be the c external route.

Thanks!

do.while's icon

great ... but u have to experience it . as you were asking quite deep questions here .

it may be that u are looking for something much more responsive which u will not be able to recreate in JS ... never think of timing stuff as mentioned before , dont try to manipulate midi streams for "further max processing".
Use JS for updating your patch settings for further processing as this stuff is taking most of the space in patches .

Dont affraid of DSP . doing some data logic in C will mirror what u would do in JS but with different language habits . u dont need to do real DSP , buffer processing , interpolation or filters for that matter .really its not that crazy .
i promise .... well ok it is crazy but sometimes only :D

and if u really looking for numbers . please try to measure some things . i would love to see some numbers too .

patrickkidd's icon

Interesting. Mostly right now I am writing a midi filter that controls a whole bunch of Ableton Looper objects. For example it queues incoming midi messages while one looper is recording, so that it can start the next looper immediately following. so far the latency is unnoticeable, but this sort of thing is just too complicated to program using max objects, as showing in the little snippet that was about 10% of the total device's app logic.

Would you suggest manipulating midi streams in C then? Or just getting so good at max patching as to be able to do it really cleanly there (seems like that would take a year or so of recreation-quality time).

do.while's icon

Hi Patrick
I like this looper idea . Anyhow , ive been processing midi streams in my last project quite heavily . I tried JS first , then i had to rewrite it in C. Due to measurements ,and data behavior . At first i could not tell by ears if there are some real interruptions . But as soon as devices were reused (multi instance) i could notice lags . Anything "immediately" has to be concerned as "crucial" .

Anyway . im similar to you . There are things that i dont want to even think of how to do in max natively anymore because "i see" the code already mindwise . And most of the times its about faster result than patching it all with objects wire by wire .

But that is not good habit of mine i guess :) i should stay true to max , be evangelist and when someone need help i should give her or him my advise/help as fast as possible . So im patching it first just to get use to max objects attributes even if its taking time , its fun or its even love . Prototyping is good idea , as a beside effect im learning a lot through .

When it comes to end-product then its about the project not the religion i choose . i have to have strategy and be able to decide whats the most efficient approach i can take , what to omit and any other circumstances .

In situation like yours i would pick coding it . im encouraging u to at least try the SDK . But if its going to be device for live/stage performance ... well u have to take time , as any bugs , memory leaks will lead to disaster ... Prototype it first in max even if it will become spagetti . Stability is your goal here Patrick

patrickkidd's icon

Sounds like you enjoy a healthy mix of approaches. I will have a look at the SDK for sure then, but I am developing on Mac and deploying to windows (I built an Intel NUC Ableton pedalboard) so hopefully there is some sort of binary compatibility.

Also, if Max's JS engine is anything like the one I developed for EastWest Play (http://sounds online.com), it uses a single thread lock for all max devises in the current process (i.e. Ableton Live.exe or Max.exe). if that's the case then JS execution could get really thrashy aince each mixer channel has its own thread and will be competing for that one lock. If there is only one thread accessing the JS engine at a time, then you are only subject to the overhead of executing the JS byte code itself. In the case of my Python engine inside EW Play, the code execution was quite fast, but becomes problematic as you add mixer channels.i would love to hear more inplemntation details about this from the C74 engineers.

At any rate, this is a great academic and practical conversation.

patrickkidd's icon

I thought I would follow this thread up a little by saying that I refactored my looper device in to javascript and found it to be a slam dunk in both performance and maintenance. Granted, I'm not doing heavy "critical" processing, but I am intercepting midi messages, then updating Push's grid + arming tracks + updating a local GUI, and then sending the message on through the LiveAPI to the respective device and not noticing much of a lag. I would say that my code is highly optimized at the code-level. This is with a pedal-operated bass + violin looping device at minimum latency with an RME audio interface.

The patch loads faster, is more maintainable because I have separated some of the slow-initialization routines into separate js objects, and is of course much easier to develop than max objects.

I'm sure that performance will become an issue with more involved processing in JS. But I wanted to throw out that little plug in case other people are reading this thread in the future.

Peace!

do.while's icon

haha thats fantastic Patrick . Can you elaborate more on how the midi is consumed in general ? considering stream branching and stuff . im curious when and where midi ends up in your situation .
Anyway , glad you have reserved your time for it .

Floating Point's icon

curious to know how many lines of js code are you talking here, in your patch overall just as a rough point of reference?

patrickkidd's icon

Basically I wanted to build a stage looper with several channels for each of violin, piano, bass, synth/piano on Push, etc, all controlled by a midi foot pedal (FCB1010). And I wanted to use Push to control the usual session view stuff like levels, send, mute, device macros, etc. Clip firing is out of scope, but the push button matrix is customized to show the looper states and mixer levels for each channel. It's hard to explain without a video, sorry.

You start recording a loop by either tapping a foot pedal for that channel or tapping the respective button on the push grid. These buttons basically just pass on note on messages to that channel's Looper device transport button. The max device takes midi input via channel 16 as routed in the I/O section of the Live track. It then just performs GUI updates on the top 4 of Push's grid buttons for that track , and sends midi output via channel 15 also set in that live track's I/O. It also has listeners for the Looper's "State" param to keep the Push's grid updated for that parameter. It also observes the mixer channel's level and shows this in real time on the bottom half of the track's column on the grid buttons. If the first looper is recording, you can queue up a second looper to start immediately afterwards for seamless looping.

So the inputs are midi, Push Button_Matrix buttons, observed LiveAPI attributes. The outputs are midi, Looper device params (i.e. reverse).

A very simple allusion to the Max project is outlined in the "Push / M4L Looping Device" here: http://vedanamedia.com/2015/01/ableton-pedalboard/

I hope that answers all of your questions. Sorry, I'm not as focused today as normal.