Hi all
Lately I was asking myself if some of own patching practices regarding performance optimization were justified or based on some wrong beliefs.
I often use [*~ ] as on/off signal gates and now started be concerned about using an object that performs a relatively complex task (multiplication of two floating point numbers) for such a simple task. I imagined that an object that either outputs a copy of the input or outputs zeros would be a less expensive on/off signal gate than [*~]. I created an abstraction containing this:
[inlet~] [inlet] | | | [switch~ ] | [outlet~]
Let's call this abstraction [gate~ ]. It turned out to work as supposed. But is [gate~] really cheaper than [*~ ]? I made a test by connecting lots of [gate~]s to a chain and measure the CPU usage. For simplicity reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
The relatively complex multiplication is _not_ more expensive than the on/off implementation with [switch~ ]. Even when turned off, the [switch~] approach is still more expensive.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved! It also doesn't matter what value the argument has. The plain fact of specifying an argument makes [*~ ] a lot cheaper. I also tested [/~ ], [+~ ] and [-~ ] and the same applies for those. They all have 0.39ct without argument and only 0.2ct with an argument specified. Depending on the kind of patch, this allows for quite a significant performance improvement.
I also measured the ct of a [*~ ] when a signal wire is connected to the right inlet. It costs exactly as much (0.39ct) as when sending messages to the right inlet of a [*~ ] without argument.
My interpretation is that [*~ 0] and [*~ ] are two different objects. The latter always performs a calculation with two signals and implicitly converts a message on the right inlet to a signal, where the former really only deals with messages on the right inlet (and thus is cheaper).
On a completely different note, I wanted to know if it costs anything to have signals entering and leaving subpatches and abstractions a lot, respectively if [inlet~] and [outlet~] add some overhead in CPU time. I chained tens of thousands subpatches together and it seems that does not consume any additional CPU time at all. The values for [inlet~ ] and [outlet~] are much below 0.01ct. I haven't tested the cost, when more than one signal wire are going to an [inlet~], though.
Roman
After looking at d_arithmetic.c, I'm curiouswhy there is scalartimes_perform
and scalartimes_perf8?
-Jonathan
----- Original Message -----
From: Roman Haefeli reduzent@gmail.com To: PD list pd-list@iem.at Cc: Sent: Thursday, December 1, 2011 9:24 AM Subject: [PD] Findings regarding performance
Hi all
Lately I was asking myself if some of own patching practices regarding performance optimization were justified or based on some wrong beliefs.
I often use [*~ ] as on/off signal gates and now started be concerned about using an object that performs a relatively complex task (multiplication of two floating point numbers) for such a simple task. I imagined that an object that either outputs a copy of the input or outputs zeros would be a less expensive on/off signal gate than [*~]. I created an abstraction containing this:
[inlet~] [inlet] | | | [switch~ ] | [outlet~]
Let's call this abstraction [gate~ ]. It turned out to work as supposed. But is [gate~] really cheaper than [*~ ]? I made a test by connecting lots of [gate~]s to a chain and measure the CPU usage. For simplicity reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
The relatively complex multiplication is _not_ more expensive than the on/off implementation with [switch~ ]. Even when turned off, the [switch~] approach is still more expensive.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved! It also doesn't matter what value the argument has. The plain fact of specifying an argument makes [*~ ] a lot cheaper. I also tested [/~ ], [+~ ] and [-~ ] and the same applies for those. They all have 0.39ct without argument and only 0.2ct with an argument specified. Depending on the kind of patch, this allows for quite a significant performance improvement.
I also measured the ct of a [*~ ] when a signal wire is connected to the right inlet. It costs exactly as much (0.39ct) as when sending messages to the right inlet of a [*~ ] without argument.
My interpretation is that [*~ 0] and [*~ ] are two different objects. The latter always performs a calculation with two signals and implicitly converts a message on the right inlet to a signal, where the former really only deals with messages on the right inlet (and thus is cheaper).
On a completely different note, I wanted to know if it costs anything to have signals entering and leaving subpatches and abstractions a lot, respectively if [inlet~] and [outlet~] add some overhead in CPU time. I chained tens of thousands subpatches together and it seems that does not consume any additional CPU time at all. The values for [inlet~ ] and [outlet~] are much below 0.01ct. I haven't tested the cost, when more than one signal wire are going to an [inlet~], though.
Roman
Pd-list@iem.at mailing list UNSUBSCRIBE and account-management -> http://lists.puredata.info/listinfo/pd-list
Le 2011-12-01 à 07:00:00, Jonathan Wilkes a écrit :
After looking at d_arithmetic.c, I'm curiouswhy there is scalartimes_perform and scalartimes_perf8?
One is made for blocksizes at least 8, and the other for blocksizes 1,2,4.
Perf8 is a kind of explicit loop-unrolling, coded to greatly reduce the number of conditional jumps when looping a lot on tight loops. Conditional jumps are extremely slow on modern CPUs, and this affects if/else/for/do/while/switch as well as function pointers and virtual function calls.
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
----- Original Message -----
From: Mathieu Bouchard matju@artengine.ca To: Jonathan Wilkes jancsika@yahoo.com Cc: Roman Haefeli reduzent@gmail.com; PD list pd-list@iem.at Sent: Thursday, December 1, 2011 10:55 AM Subject: Re: [PD] Findings regarding performance
Le 2011-12-01 à 07:00:00, Jonathan Wilkes a écrit :
After looking at d_arithmetic.c, I'm curiouswhy there is
scalartimes_perform and scalartimes_perf8?
One is made for blocksizes at least 8, and the other for blocksizes 1,2,4.
Ok, so perf8 corresponds to "at least 8". Thanks.
Perf8 is a kind of explicit loop-unrolling, coded to greatly reduce the number of conditional jumps when looping a lot on tight loops. Conditional jumps are extremely slow on modern CPUs, and this affects if/else/for/do/while/switch as well as function pointers and virtual function calls.
But there aren't any conditionals inside the loop-- it's just multiplication operators.
-Jonathan
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
Le 2011-12-01 à 08:21:00, Jonathan Wilkes a écrit :
----- Original Message -----
From: Mathieu Bouchard matju@artengine.ca
One is made for blocksizes at least 8, and the other for blocksizes 1,2,4.
Ok, so perf8 corresponds to "at least 8". Thanks.
Well, actually it corresponds to "multiples of 8". You can see that those loops don't check whether there is anything left to do after they process chunks of 8.
But as long as blocksizes remain powers of two, there's no difference between «at least 8» and «positive multiple of 8».
Perf8 is a kind of explicit loop-unrolling, coded to greatly reduce the number of conditional jumps when looping a lot on tight loops. Conditional jumps are extremely slow on modern CPUs, and this affects if/else/for/do/while/switch as well as function pointers and virtual function calls.
But there aren't any conditionals inside the loop-- it's just multiplication operators.
In «for(i=0;i<n;i++) foo[i]=42;» the conditional is «i<n» and the jump is performed if that conditional gives false (0). The compiler rewrites the code in this manner :
i=0; while(i<n) { foo[i]=42; i++; }
and then in this manner :
i=0; here: if (i<n) goto there; foo[i]=42; i++; goto here; there:
But depending on which optimisations are turned on, it may transform it further. What I wrote is just the most basic compilation process.
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
would it make sense to do a general test patch, where these and more
objects could be tested empirically? or, put your patches somewhere, so
that other people can work on them, and have a test-repository?
performance considerations aside, I have a reason to use [*~] with line~,
instead of switch~: to avoid clicks (in case there is audio when the chain
is broken).
João
Hi all
Lately I was asking myself if some of own patching practices regarding performance optimization were justified or based on some wrong beliefs.
I often use [*~ ] as on/off signal gates and now started be concerned about using an object that performs a relatively complex task (multiplication of two floating point numbers) for such a simple task. I imagined that an object that either outputs a copy of the input or outputs zeros would be a less expensive on/off signal gate than [*~]. I created an abstraction containing this:
[inlet~] [inlet] | | | [switch~ ] | [outlet~]
Let's call this abstraction [gate~ ]. It turned out to work as supposed. But is [gate~] really cheaper than [*~ ]? I made a test by connecting lots of [gate~]s to a chain and measure the CPU usage. For simplicity reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
The relatively complex multiplication is _not_ more expensive than the on/off implementation with [switch~ ]. Even when turned off, the [switch~] approach is still more expensive.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved! It also doesn't matter what value the argument has. The plain fact of specifying an argument makes [*~ ] a lot cheaper. I also tested [/~ ], [+~ ] and [-~ ] and the same applies for those. They all have 0.39ct without argument and only 0.2ct with an argument specified. Depending on the kind of patch, this allows for quite a significant performance improvement.
I also measured the ct of a [*~ ] when a signal wire is connected to the right inlet. It costs exactly as much (0.39ct) as when sending messages to the right inlet of a [*~ ] without argument.
My interpretation is that [*~ 0] and [*~ ] are two different objects. The latter always performs a calculation with two signals and implicitly converts a message on the right inlet to a signal, where the former really only deals with messages on the right inlet (and thus is cheaper).
On a completely different note, I wanted to know if it costs anything to have signals entering and leaving subpatches and abstractions a lot, respectively if [inlet~] and [outlet~] add some overhead in CPU time. I chained tens of thousands subpatches together and it seems that does not consume any additional CPU time at all. The values for [inlet~ ] and [outlet~] are much below 0.01ct. I haven't tested the cost, when more than one signal wire are going to an [inlet~], though.
Roman
Pd-list@iem.at mailing list UNSUBSCRIBE and account-management ->
http://lists.puredata.info/listinfo/pd-list
Hi João
On Thu, 2011-12-01 at 16:04 +0100, João Pais wrote:
would it make sense to do a general test patch, where these and more
objects could be tested empirically? or, put your patches somewhere, so
that other people can work on them, and have a test-repository?
I attached my test setup. It's very naive and it doesn't serve much more than to compare different classes on a specific setup (Hardware/CPU, specific Pd version, probably it's even specific to certain compile time flags).
main.pd loads many (actually 4096) instances of [gate~]. I simply edited an instance of [gate~] and compared what CPU meter displayed while main.pd was running. On different computers you probably need to add more or remove instances of [gate1024~] in order to get meaningful results.
I don't think those patches are worth being included in some test repository, mainly because the results are so much dependent on the specific setup. There is no absolute unit (reproducible on any system) for measuring CPU time, or is there?
performance considerations aside, I have a reason to use [*~] with line~,
instead of switch~: to avoid clicks (in case there is audio when the chain
is broken).
I do that as well where applicable. It turns out that [line~] only adds very little overhead when idling (approximately 0.1ct). Of course, it uses more while actually ramping. But you can now test that yourself ;-)
Roman
Hi all
Lately I was asking myself if some of own patching practices regarding performance optimization were justified or based on some wrong beliefs.
I often use [*~ ] as on/off signal gates and now started be concerned about using an object that performs a relatively complex task (multiplication of two floating point numbers) for such a simple task. I imagined that an object that either outputs a copy of the input or outputs zeros would be a less expensive on/off signal gate than [*~]. I created an abstraction containing this:
[inlet~] [inlet] | | | [switch~ ] | [outlet~]
Let's call this abstraction [gate~ ]. It turned out to work as supposed. But is [gate~] really cheaper than [*~ ]? I made a test by connecting lots of [gate~]s to a chain and measure the CPU usage. For simplicity reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
The relatively complex multiplication is _not_ more expensive than the on/off implementation with [switch~ ]. Even when turned off, the [switch~] approach is still more expensive.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved! It also doesn't matter what value the argument has. The plain fact of specifying an argument makes [*~ ] a lot cheaper. I also tested [/~ ], [+~ ] and [-~ ] and the same applies for those. They all have 0.39ct without argument and only 0.2ct with an argument specified. Depending on the kind of patch, this allows for quite a significant performance improvement.
I also measured the ct of a [*~ ] when a signal wire is connected to the right inlet. It costs exactly as much (0.39ct) as when sending messages to the right inlet of a [*~ ] without argument.
My interpretation is that [*~ 0] and [*~ ] are two different objects. The latter always performs a calculation with two signals and implicitly converts a message on the right inlet to a signal, where the former really only deals with messages on the right inlet (and thus is cheaper).
On a completely different note, I wanted to know if it costs anything to have signals entering and leaving subpatches and abstractions a lot, respectively if [inlet~] and [outlet~] add some overhead in CPU time. I chained tens of thousands subpatches together and it seems that does not consume any additional CPU time at all. The values for [inlet~ ] and [outlet~] are much below 0.01ct. I haven't tested the cost, when more than one signal wire are going to an [inlet~], though.
Roman
Pd-list@iem.at mailing list UNSUBSCRIBE and account-management ->
http://lists.puredata.info/listinfo/pd-list
On Thu, Dec 1, 2011 at 11:34 AM, Roman Haefeli reduzent@gmail.com wrote:
Hi João
On Thu, 2011-12-01 at 16:04 +0100, João Pais wrote:
would it make sense to do a general test patch, where these and more objects could be tested empirically? or, put your patches somewhere, so that other people can work on them, and have a test-repository?
I attached my test setup. It's very naive and it doesn't serve much more than to compare different classes on a specific setup (Hardware/CPU, specific Pd version, probably it's even specific to certain compile time flags).
main.pd loads many (actually 4096) instances of [gate~]. I simply edited an instance of [gate~] and compared what CPU meter displayed while main.pd was running. On different computers you probably need to add more or remove instances of [gate1024~] in order to get meaningful results.
Actually, I think you're looking at scalibility--to get a better measurement, you'd need to compare across several numbers on a single machine, e.g. 1024, 2048, 4096, etc... and do a linear regression to separate out the fixed costs and marginal costs.
[inlet~] [inlet] | | | [switch~ ] | [outlet~]
Besides clicking problems which you might not care about, If you send a zero to switch~ while there is still audio in the buffer you'll get the buffer repeated over and over. This is why I use both a *~ AND a switch~.
Best,
J
Let's call this abstraction [gate~ ]. It turned out to work as supposed. But is [gate~] really cheaper than [*~ ]? I made a test by connecting lots of [gate~]s to a chain and measure the CPU usage. For simplicity reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
The relatively complex multiplication is _not_ more expensive than the on/off implementation with [switch~ ]. Even when turned off, the [switch~] approach is still more expensive.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved! It also doesn't matter what value the argument has. The plain fact of specifying an argument makes [*~ ] a lot cheaper. I also tested [/~ ], [+~ ] and [-~ ] and the same applies for those. They all have 0.39ct without argument and only 0.2ct with an argument specified. Depending on the kind of patch, this allows for quite a significant performance improvement.
I also measured the ct of a [*~ ] when a signal wire is connected to the right inlet. It costs exactly as much (0.39ct) as when sending messages to the right inlet of a [*~ ] without argument.
My interpretation is that [*~ 0] and [*~ ] are two different objects. The latter always performs a calculation with two signals and implicitly converts a message on the right inlet to a signal, where the former really only deals with messages on the right inlet (and thus is cheaper).
On a completely different note, I wanted to know if it costs anything to have signals entering and leaving subpatches and abstractions a lot, respectively if [inlet~] and [outlet~] add some overhead in CPU time. I chained tens of thousands subpatches together and it seems that does not consume any additional CPU time at all. The values for [inlet~ ] and [outlet~] are much below 0.01ct. I haven't tested the cost, when more than one signal wire are going to an [inlet~], though.
Roman
Pd-list@iem.at mailing list UNSUBSCRIBE and account-management -> http://lists.puredata.info/listinfo/pd-list
This repeated buffer only applies to signals sent inside that subpatch, and signals sent out with the [s~ ] object. (i didn't test throw~, but i would guess it's the same)
if you just use an [outlet~] object, the buffer is not repeated.
see attached test patch if you don't believe me.
On Fri, Dec 2, 2011 at 12:17 AM, Jaime Oliver jaime.oliver2@gmail.comwrote:
[inlet~] [inlet] | | | [switch~ ] | [outlet~]
Besides clicking problems which you might not care about, If you send a zero to switch~ while there is still audio in the buffer you'll get the buffer repeated over and over. This is why I use both a *~ AND a switch~.
Best,
J
Let's call this abstraction [gate~ ]. It turned out to work as supposed. But is [gate~] really cheaper than [*~ ]? I made a test by connecting lots of [gate~]s to a chain and measure the CPU usage. For simplicity reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
The relatively complex multiplication is _not_ more expensive than the on/off implementation with [switch~ ]. Even when turned off, the [switch~] approach is still more expensive.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved! It also doesn't matter what value the argument has. The plain fact of specifying an argument makes [*~ ] a lot cheaper. I also tested [/~ ], [+~ ] and [-~ ] and the same applies for those. They all have 0.39ct without argument and only 0.2ct with an argument specified. Depending on the kind of patch, this allows for quite a significant performance improvement.
I also measured the ct of a [*~ ] when a signal wire is connected to the right inlet. It costs exactly as much (0.39ct) as when sending messages to the right inlet of a [*~ ] without argument.
My interpretation is that [*~ 0] and [*~ ] are two different objects. The latter always performs a calculation with two signals and implicitly converts a message on the right inlet to a signal, where the former really only deals with messages on the right inlet (and thus is cheaper).
On a completely different note, I wanted to know if it costs anything to have signals entering and leaving subpatches and abstractions a lot, respectively if [inlet~] and [outlet~] add some overhead in CPU time. I chained tens of thousands subpatches together and it seems that does not consume any additional CPU time at all. The values for [inlet~ ] and [outlet~] are much below 0.01ct. I haven't tested the cost, when more than one signal wire are going to an [inlet~], though.
Roman
Pd-list@iem.at mailing list UNSUBSCRIBE and account-management ->
http://lists.puredata.info/listinfo/pd-list
Pd-list@iem.at mailing list UNSUBSCRIBE and account-management -> http://lists.puredata.info/listinfo/pd-list
Le 2011-12-01 à 15:24:00, Roman Haefeli a écrit :
reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
Even though [switch~] does not use tight conditionals, instead checking only once per block, it still takes some time copying stuff and switching contexts. I suppose that this is the kind of thing that Pd could do more efficiently than it does now, if someone is brave enough to edit d_ugen.c... and knows how to do it.
Float multiplications are often so fast nowadays, that the process of reading and writing RAM is often bigger. A lot of machinery in the CPU is dedicated to multiplying floats as fast as possible.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved!
I already explained that on IRC. What part of the explanation was missing ? Here's a copy+paste from the chat.
« that's the difference between times_dsp and scalartimes_dsp... the latter is «obviously» faster... well, it does only ⅔ as much data transfer during the perform-function, so it would make sense. benchmarks might say otherwise, or not. _but_ note that sending a float message to [*~] also means copying that value N times (N=block size... 64 or other) whereas in the scalartimes class such as [*~ 42], sending a float (in right inlet) copies that value only once. »
and later :
« i did not verify it, but it does use twice as much input data. This predicts a possible 3/2 ratio, but there are other reasons why it could be a 2/1 ratio... it depends on the implementation of the cpu itself »
« when multiplications are fast, then the bottleneck is to get the data to move around, and if you have twice as much data, it can be twice slower. In some other situations, it can be even worse than twice slower. Those behaviours are harder to understand than ever because optimisation tricks pile up ever more. »
I mean optimisation tricks of CPUs and motherboards (caches, RAM chips, etc).
Are you receiving what I write on IRC ?
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
Hi Matju
Are you receiving what I write on IRC ?
Not always, I think, but I remember the parts you posted below.
My question ("Is there something more efficient to turn signals on and off than [*~]?") and the things you replied triggered me to do those performance tests. Somehow it is easier for me to deal with stuff that I empirically experience than with written words in IRC where I am not always sure, if I understand the full depth of their meaning. I'm sorry now for not having credited you for bringing me to the topic.
Anyway, the fact that you're explaining stuff is not a reason for me to not to make some tests.
Howsoever, thanks for your explanations.
Roman
On Thu, 2011-12-01 at 11:09 -0500, Mathieu Bouchard wrote:
Le 2011-12-01 à 15:24:00, Roman Haefeli a écrit :
reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
Even though [switch~] does not use tight conditionals, instead checking only once per block, it still takes some time copying stuff and switching contexts. I suppose that this is the kind of thing that Pd could do more efficiently than it does now, if someone is brave enough to edit d_ugen.c... and knows how to do it.
Float multiplications are often so fast nowadays, that the process of reading and writing RAM is often bigger. A lot of machinery in the CPU is dedicated to multiplying floats as fast as possible.
But the really interesting finding comes now. [*~ 0] has only 0.2ct! Almost the the ct value of a plain [*~ ] halved!
I already explained that on IRC. What part of the explanation was missing ? Here's a copy+paste from the chat.
« that's the difference between times_dsp and scalartimes_dsp... the latter is «obviously» faster... well, it does only ⅔ as much data transfer during the perform-function, so it would make sense. benchmarks might say otherwise, or not. _but_ note that sending a float message to [*~] also means copying that value N times (N=block size... 64 or other) whereas in the scalartimes class such as [*~ 42], sending a float (in right inlet) copies that value only once. »
and later :
« i did not verify it, but it does use twice as much input data. This predicts a possible 3/2 ratio, but there are other reasons why it could be a 2/1 ratio... it depends on the implementation of the cpu itself »
« when multiplications are fast, then the bottleneck is to get the data to move around, and if you have twice as much data, it can be twice slower. In some other situations, it can be even worse than twice slower. Those behaviours are harder to understand than ever because optimisation tricks pile up ever more. »
I mean optimisation tricks of CPUs and motherboards (caches, RAM chips, etc).
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
Le 2011-12-01 à 17:37:00, Roman Haefeli a écrit :
My question ("Is there something more efficient to turn signals on and off than [*~]?") and the things you replied triggered me to do those performance tests. Somehow it is easier for me to deal with stuff that I empirically experience than with written words in IRC where I am not always sure, if I understand the full depth of their meaning.
[...]
Anyway, the fact that you're explaining stuff is not a reason for me to not to make some tests.
You are right about wanting to test things. And I do, too. It's important to verify the theory by experience. Some theory is harder to verify though, and in lots of cases, the experience is complicated by lots of other factors that you can't really separate from the thing you're trying to test. Still, it's good to make those experiences, to learn intuition in a way that theory can't do, and to make sure that all those books are not telling you bullshit. :)
Btw, if you try your tests on quite different computers (current cellphones, or very old computers) you may get quite different results, e.g. [switch~] could be much faster than any [*~]. This can be because float multiplication gets done in many small separate steps (instead of a few big steps) which can get a lot longer than just copying memory.
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
On Thu, Dec 1, 2011 at 10:09 AM, Mathieu Bouchard matju@artengine.ca wrote:
Le 2011-12-01 à 15:24:00, Roman Haefeli a écrit :
reason, let's just use an invented arbitrary unit for expressing the CPU time (ct) consumed by an object. It turned out that [gate~] uses 0.52ct when it is on and 0.4ct when it is off. But how much does [*~ ] use? No matter whether turned on or off, [*~ ] uses a stable 0.39ct.
Even though [switch~] does not use tight conditionals, instead checking only once per block, it still takes some time copying stuff and switching contexts. I suppose that this is the kind of thing that Pd could do more efficiently than it does now, if someone is brave enough to edit d_ugen.c... and knows how to do it.
For further explanation: In an abstraction where the block sizes match and there's no switch~, signals are borrowed from the parent context. When switch~ is used, inlet~ and outlet~ signals are not borrowed, even when block sizes match.
block_prolog handles the switch~ behavior, returning a pointer to the end of the block epilog when switched off. This comes before data is copied into buffers in inlet_doprolog.
On the outlet~, there is an un-borrowed signal allocated, and I think it's also assigned to be zero when switched off. This would be the only major point where an assignment operation occurs.
When using [*~ 0], the inlet and outlet are borrowed. The scalar multiply operation is performed in place and no data transfer occurs.
It's all relatively fresh in my mind, but I need a good week's vacation to re-focus and study d_ugen.c some more. I've done enough to understand what's in there, but I've rarely asked the question how to make it work better.
Le 2011-12-01 à 10:39:00, Charles Henry a écrit :
When using [*~ 0], the inlet and outlet are borrowed. The scalar multiply operation is performed in place and no data transfer occurs.
What do you call « data transfer » ? multiplying in place by a constant involves as many reads and writes as doing a (single) copy. This at least needs to stream data from the highest-speed RAM to the CPU and back. It's less noticeable than the copy time of very large buffers (e.g. [table] or [pix_separator]) because those really need big RAM (which is slower), but in any case, calling scalartimes_perf8 (or whatever) means an implicit copy in some kind of way, just like nearly anything else does.
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC
On Thu, Dec 1, 2011 at 4:02 PM, Mathieu Bouchard matju@artengine.ca wrote:
Le 2011-12-01 à 10:39:00, Charles Henry a écrit :
When using [*~ 0], the inlet and outlet are borrowed. The scalar multiply operation is performed in place and no data transfer occurs.
What do you call « data transfer » ? multiplying in place by a constant involves as many reads and writes as doing a (single) copy. This at least needs to stream data from the highest-speed RAM to the CPU and back. It's less noticeable than the copy time of very large buffers (e.g. [table] or [pix_separator]) because those really need big RAM (which is slower), but in any case, calling scalartimes_perf8 (or whatever) means an implicit copy in some kind of way, just like nearly anything else does.
You make a good point--I wasn't counting the data transfer that occurs between registers or the way that the compiler breaks out the steps involved, and of which I am mostly ignorant.
The part I was differentiating from: there's significant data transfer operations that has to do with switch~. The inlet~ inside a subpatch and outlet on the parent from canvases using switch~ have signals that aren't borrowed. This means there's another data transfer (a copy) between signals on the parent and the sub-canvas.
So, using switch~ as in Roman's example involves 2 copy operations on the signals. Is that what we're seeing? I'm not sure how to count the operations--and how to compare the scalartimes perform routines vs inlet_doprolog/inlet_dsp and outlet_dsp/outlet_doepilog (which I'm contending is the difference we're seeing in the performance numbers).
Chuck
Le 2011-12-02 à 12:41:00, Charles Henry a écrit :
You make a good point--I wasn't counting the data transfer that occurs between registers or the way that the compiler breaks out the steps involved, and of which I am mostly ignorant.
Ok, well, when you copy, there is a pipeline that goes from RAM to RAM and goes through the CPU and they're just connected to each other. When you multiply, there is a pipeline that goes from RAM to multiplier to RAM. Depending how the CPU is made, RAM access could be taking turns alternating between reading or writing, or there could be two RAM units, a reader and a writer. I don't know how current machines are made, but differences about this can make a theoretical difference between observing a 3/2 speed ratio and a 2/1 speed ratio between cases of [*~].
Pipelining means that the time of multiplication can be hidden by the time of memory access, as the RAM-access counts as 1 or 2 sub-CPUs, and the multiplier counts as 1 sub-CPU, and they all run at the same time, so, as long as you do many things in a row to keep all parts busy, the total time will be only a bit more than max(time of each sub-CPU) because the instructions' times will overlap as much as they can.
There are also several sub-CPUs for programme-decoding and other stuff I haven't talked about.
Conditional jumps mean that you have to pause the pipeline long enough to get the result of the decision to know what the next thing to do might be. Loop-unrolling (perf8 and such) sets up longer todo-lists to reduce the pausing by a factor of 4 or 8 or more.
So, using switch~ as in Roman's example involves 2 copy operations on the signals. Is that what we're seeing?
I don't know... maybe... I haven't looked much at d_ugen.c... and won't do it now.
| Mathieu BOUCHARD ----- téléphone : +1.514.383.3801 ----- Montréal, QC