Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Memory Allocator Questions #73

Open
capocasa opened this issue Aug 1, 2017 · 13 comments
Open

Custom Memory Allocator Questions #73

capocasa opened this issue Aug 1, 2017 · 13 comments

Comments

@capocasa
Copy link
Contributor

capocasa commented Aug 1, 2017

Hello,

as always, first and foremost thank you very much for creating and participating in Faust!

Let me know if i should repost this question at a more appropriate place.

In the Faust architecture/supercollider.cpp, initState is currently called from the library load hook.

I would like to move initState to the UGen constructor hook to adhere to SuperCollider convention and to initialize with the correct server sample rate, which is unavailable at library load time. This requires realtime safe code, chiefly for memory allocation.

(1) If I use the new custom memory allocation (-mem mode) with SuperCollider's realtime safe memory allocation, is all memory allocation performed using the custom allocator, including the Faust class *new and *delete operators? Or do I need to manually overload *new and *delete as well?

(2) Are there any other non-realtime-safe operations that Faust code may perform that I may need to consider?

Thanks and have a great time, Carlo

@sletz
Copy link
Member

sletz commented Aug 1, 2017

  1. Nope, you'll have to overload *new and *delete (see the end of http://faust.grame.fr/news/2017/06/16/custom-memory-allocator.html, Full control the DSP memory allocation can be done using C++ placement new:). It can be done by subclassing mydsp generated class.

  2. No. You should also optimize allocation in initState by making dsp->classInit(sampleRate); be called only once (since classInit method allocates class level (= shared between DSP instances) stuff).

@capocasa
Copy link
Contributor Author

capocasa commented Aug 1, 2017

Great! Sorry I missed that- now I know. Yes, static call only once.

@capocasa
Copy link
Contributor Author

capocasa commented Aug 1, 2017

Oh, hey Stephane! I just completed my slow realization that you're both a Faust guy and on the SuperCollider dev mailing list. That's great!

@sletz
Copy link
Member

sletz commented Aug 8, 2017

Anything new using custom memory allocator on your side?

@capocasa
Copy link
Contributor Author

capocasa commented Aug 9, 2017

Yes!

If I am interpreting this right, table initialization code (e.g. fill the table with a bunch of cosines) may not be realtime safe after all, so my current thinking is that static table initialization may better remain at server boot time, where realtime operation does not matter (so the custom allocator is not needed). Please do offer your perspective if it differs.

So my effort right now is to figure out how to best patch scsynth and supernova to make the server sample rate available (and perhaps some other info) at in the boot hook (*load).

To accomodate DSP programs that are heavy on instance tables, I am thinking it may be necessary to move them to an asynchronous initialization OSC command, as a second project, to avoid a potentially unsafe CPU spike. This would increase UI complexity so it could be optional, and should be benchmarked for necessity.

@sletz
Copy link
Member

sletz commented Aug 9, 2017

Having "sample rate available " at server boot time is certainly useful and would solve the current "static" compilation model.

Concerning using custom allocator, it will certainly be needed if we go also for a mode dynamic compilation model using libfaust, as described in some older mails.

@capocasa
Copy link
Contributor Author

capocasa commented Aug 9, 2017

Currently my view is that the code and architecture of faust/supercollider.cpp is good, and it should be incrementally improved.

I enjoyed exploring libfaust a bit by reviewing one of the threads. My impression is that the main benefit would be the potential for more efficient cross-platform distribution.

However, that is a problem I currently believe would be better solved by the current effort to support UGen binary libraries in SuperCollider's package manager ("Quarks") with an automated cross-platform build system.

Do you see any other arguments I may have missed for either direction?

@sletz
Copy link
Member

sletz commented Aug 9, 2017

One of the main point to support libfaust base UGen would be to allow "live coding".

@capocasa
Copy link
Contributor Author

capocasa commented Aug 10, 2017

I see! Well that is very exciting indeed.

I think we are talking about two distinct use cases for Faust within SuperCollider here- one, a convenient tool efficiently write beautiful algorithms that become SuperCollider UGens. Here, the static nature is a virtue: No CPU overhead to create the synth, the user uses the UI for high-level controls, and doesn't have to know anything about Faust to use it.

Two, a Faust livecoding system- This is for much more technical users who know what Faust is, desire the flexibility, and are able to accomodate CPU spikes. This should probably be implemented as a single "LiveFaust" UGen, along with a convenient API on the language side.

So, in my estimation, we should have both, as separate projects, so we don't have to make compromises for either use case. Livecoding users may, additionally, enjoy that they can make static compiled UGens from their creations when they discover something really good in their experimentation.

Personally, my main aim is to have all of the best sounding algorithms as static UGens withing SuperCollider so "somewhat technical" users can write a few lines of code and go "wow". And also to get rid of as many "gotchas" as possible so they don't get frustrated.

I may be able to help planning architecture for livecoding though- For example, @PaulBatchelor just let me know he would be interested in having his own "Sporth" DSP language within SuperCollider. If "seperation of concerns" is done cleanly, I think there is a lot of potential for code-reuse between his aim and a Faust livecoding system.

Excited to hear your thoughts on this analysis.

@sletz
Copy link
Member

sletz commented Aug 10, 2017

I agree with the distinct use cases. For libfaust/JIT stuff, the point was to see if compilation could be separated in on 2 parts:

In the model I’m thinking of, Faust DSP ==> LLVM IR ==> JIT ==> machine code steps would be done on sclang side (so that DSP programing error can simply be reported here…), and the machine code only would be sent to scsynth side, then the « dynamically link in memory » step is never supposed to fail, could be done using NRT model, until the executable code is ready to be started.

(http://new-supercollider-mailing-lists-forums-use-these.2681727.n2.nabble.com/LLVM-based-libfaust-integration-td7632284.html)

@capocasa
Copy link
Contributor Author

Maybe we should back up a step here- error reporting does not have to be on the language side. It can be, and when it is it is usually more detailed, because more information is available, but UGens can and do report errors- server stdout just goes into the IDE console along with everything else. It would still likely be desirable to place some part of compilation on the language side, however there is more flexibility on where to draw the line when you can still report the occasional error from the server.

I advise against sending machine code to the server- the server can be remote, and we cannot have the server execute machine code it received over the network, even in a secure LAN. Also, you may be operating a client on Windows and a server on realtime-patched linux, so the OS would need to be negotiated, and cross compilers installed.

A little known UGen feature is to receive OSC commands (as far as I can tell from research). LLVM IR is cross-platform bytecode, isn't it? So you send the server an OSC command containing an ID and bytecode, and the UGen responds by compiling and storing it outside the realtime thread, as it does for synthdefs. An additional command creates a synth from it, as is done for synths. This is completely analogous to how synth definition and creation is currently done, and would immediately be understood by more technical users.

A more flexible approach would be to compile an ID and the bytecode with a server command as above, and then have a UGen with a variable amount of inputs, the first of which is the ID. I think this is better- it's a little more complex to understand but it allows use of sclang as a glue language, and sclang is very good at that.

Does this make sense?

Carlo

@sletz
Copy link
Member

sletz commented Aug 10, 2017

  1. dealing with machine code is indeed more complex to handle, but not completely impossible (I did some LLVM based cross-compilation tests here...)
  2. LLVM IR is not cross-platform in general, but should be in the Faust specific context use case (that is the generated LLVM IR is a "controlled" subset of LLVM IR). Then your two described approaches could be tested.

@capocasa
Copy link
Contributor Author

capocasa commented Aug 10, 2017

Sounds great!

So I guess the next step would be how to integrate the Faust code. I suppose Strings could be used, like I do with SpaceTracker, but that seems a bit suboptimal- perhaps felix's preprocessor would be a nice place to start. It uses delimiters to seperate code much like web server languages do.

(fork{
s.bootSync;
f = |
import("stdfaust.lib");
phasor(f) = f/ma.SR : (+,1.0:fmod) ~ _ ;
osc(f) = phasor(f) * 6.28318530718 : sin;
process = osc(hslider("freq", 440, 20, 20000, 1)) * hslider("level", 0, 0, 1, 0.01);
|;
s.sync;
{
arg freq = 440;
LiveFaust.ar(f, freq)
}.play;
)

Or go further and create a shorthand version

(
{
|
import("stdfaust.lib");
phasor(f) = f/ma.SR : (+,1.0:fmod) ~ _ ;
osc(f) = phasor(f) * 6.28318530718 : sin;
process = osc(hslider("freq", 440, 20, 20000, 1)) * hslider("level", 0, 0, 1, 0.01);
|.ar(freq);
}.play
)

Even shorter
(
|
import("stdfaust.lib");
phasor(f) = f/ma.SR : (+,1.0:fmod) ~ _ ;
osc(f) = phasor(f) * 6.28318530718 : sin;
process = osc(hslider("freq", 440, 20, 20000, 1)) * hslider("level", 0, 0, 1, 0.01);
|.play; // param mapped directly to synth
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants