In this second version of my testing patch, I've slightly changed the method of testing for the completion of the 100,000 sends, using a [sel 100000] (before I was using the left-most bang on a [t b b] object). This tightens up the programmatic steps of the test.
Another change was to remove the [number] UI box from the outlet of the receiving side of each test. This has reduced the time each test takes, but also removes additional factors from having an effect on the tests.
I added an analysis of the effect of [speedlim] on these tests.
Finally, I've added a number of methods suggested by other Max uses, including direct patching, [pvar] (which I'd never used before, but certainly will now), and [sig~] -> [send~] -> [snapshot~], again, which I'd never used.**End of update notes**
Hey all. I've been trying out various methods of passing values around my patches and thought I'd put together a patch to demonstrate the performance of each method.
First off - there are benefits to each method, so there is often more to consider than the speed (especially when dealing with the pattr system). The [value] object sets are not tested here as they require a [bang] to output, so doesn't quite fit this testing method.
My test jams 100,000 incrementing values using [uzi] through the given method of remote value transfer and displays the time taken to process each step. Each test uses a [sel 100000] to trigger once the test has been completed.
On with the tests!!
This test uses direct patching cable as a control case. This is the standard way to connect things in Max. According to this test, directly connecting objects with patch cables is the fastest method.
This is normally the first "wireless" method of value transfer learnt by people in Max. I would personally use this method for values streaming in high volume if performance is a must.
The [forward] method is slightly different in that it can dynamically change the address it sends to. It is slightly slower than [send] by ~20%. This won't be probably most of the time, as the numbers are already very low.
Send (streaming value through [number] object [~27ms]
I did this test to check how the [number] UI object handles this test. It takes my system approximately 13ms of CPU time to push 100,000 integers through a Max/MSP [number] object.
We can now easily calculate how long Max takes to process a single update to the [number] object: / =
100,000 / 5ms = 0.00013ms per [number] object update
This test should be paid close attention to for those wanting to get more out of their patches.
Send With Routing [~26ms]
The same as [send]/[forward], only this is to test the added cycles needed to [prepend] the data with an identifier and then check and strip the identifier with [route]. Again, the data is not stored.
I would personally be cautious of pumping lots of differently addressed, high frequency data streams in here (but I totally do sometimes). Although the difference in this test is negligible, I would be very interested in examining how well this method scales with multiple sources and destinations going through the same [send]/[receive] pair.
I find this method very useful for sending "control" or "menu" type messages, primarily from user-interface interactions. It allows me to funnel various functions in to one area.
This object references a UI object with scripting name, updating as frequently as the object. I'm unsure of the way this works behind the scenes in the Max framework, however this is a very impressive method of both transferring a value AND temporarily storing it. None of the previous methods recall its final value, this does.
One major issue I had with this test over the others is that it REQUIRES a Max UI object with a scripting name to attach itself to. UI objects means a shit load of overhead when jamming this many values through them (as seen in the earlier test). So I made another test....
pvar (with [speedlim] @ 1 before UI object) [~6ms]
What this is doing is preventing the [number] from attempting 100,000 UI updates. Behind the scenes this would:
1) Send the first value "1"
2) [speedlim] immediately closes a virtual gate for 1ms --- meanwhile, uzi keeps ticking.
3) 1ms later, [speedlim] allows another value through --- uzi has counted up to (100,000 / 5ms) 20,000
Until it reaches 100,000 ~4ms later
Basically, [speedlim] allows [uzi] to work at full speed, only waiting for a single value to go through to the UI object once every 1ms.
Note that this is really just to highlight how powerful [speedlim] can be to speed up your patches (if you don't need every value). The overall idea of this patch is to demonstrate how long each method takes to process the entire 100,000 values.
Pattr Receive Bound (no bindto on send) [~319ms]
Here we have a [pattr] object being fed a steam of data. On the other end of the test is a blank [pattr] object that is initialised on load with (bindto ).
Once in the territory of the pattr system, things slow down a lot. Because of this, I would recommend using the pattr methods ONLY when dealing with UI interaction with objects in other patchers.
The major benefit of this method is obviously that the pattr system is designed to easy store the values of objects using the [pattrstorage] object. The added benefit of this is also that pattr objects can be bound to others with neat path addressing through script names. [pattrmarker] is very useful here.
Pattr Receive Bound (bindto on send) [~257ms]
Now - this was a HUGE surprise.
We've got the EXACT same method as the previous - only I've added a [number] object to the (bindto) outlet of the [pattr] object. As we saw in previous tests, adding a [number] object in the chain should slow things down a lot, but in this case it actually speeds things up by ~19%.
If anyone from Cycling knows the reason why this result is faster than the previous, I'd love to hear why.
Pattr Send Bound [~439ms]
Now this was actually a surprise to me. I had no idea that SENDING through a blank [pattr] bound using (bindto) would be slower than sending the data through the other end, receiving on the bound side. Anyone know why this might be?
Again, only use this when dealing with UI interaction with remote/hidden pattr objects.
Pattr Forwarding [~223ms]
This is about the same as the Bound Pattr Receive method. No real surprises here. I suppose the limitation of this method is that it can only go one way. The benefit here over PattrHub is that the destination only needs to be set once.
Again, very similar to Pattr Forwarding, only this is slightly more flexible in that it can be send a (get) message to pull the value. A [prepend] is needed here to add the name of the pattr object before each message, and it is probably due to this process that this method is slightly slower than Forwarding.
Now, this test required a slightly modified version of the setup, due to the way I was checking for 100,000. Thought this shouldn't be effecting the speed in any way.
This test is the most inconsistent in this patch. The result ranged from 5ms to 17ms - with no difference having overdrive on or off.
With the added requirement of requiring DSP to be on and some additional work arounds and patching to make this feasible in a data streaming context, I would most likely avoid this method and simply use [send] or [pvar].
I did this test and write up for discussion and awareness. It's to this end that I welcome you all to add to, correct or improve this test.
If you disagree/agree with me or just want to highlight something, please say so!