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

Container of filters. How ? #21

Open
dinaiz opened this issue Oct 29, 2014 · 14 comments
Open

Container of filters. How ? #21

dinaiz opened this issue Oct 29, 2014 · 14 comments

Comments

@dinaiz
Copy link

dinaiz commented Oct 29, 2014

Hi,

Nice project but I'm stumbling on a problem. The class SimpleFilter is template and has no non-template base which makes impossible to put it in a container.

I tried to make a container of Filter and use "new Dsp::SmoothedFilterDesign Dsp::RBJ::Design::LowPass,2,Dsp::DirectFormI(1024);" to populate it but my sound is completely garbled ! Does anyone knows how can I do ?

@sagamusix
Copy link

Probably no longer relevant, but you cannot create a container containing child classes of polymorphic classes, you have to use pointers, e.g. std::vector<std::shared_ptr<Dsp::Filter>>.

@dinaiz
Copy link
Author

dinaiz commented Sep 22, 2016

I found another solution for this problem -don't remember which one though- but thanks for the hint ! :)

@Eugene-Y
Copy link

Eugene-Y commented Nov 8, 2016

So is there some elegant way to create a universal container for all the filter types so that data locality is preserved? One of the main problems in realtime systems is data access speed so one has to avoid creating objects on the heap.

@galchinsky
Copy link
Collaborator

@1eqinfinity I would use a tagged union with needed template instances. Not very elegant but very local and thus fast, also no virtual functions would be used.

Another hack is to make an oversized fake child class. But you'll have to control that you don't put large objects to your vector.

If you really need polymorphism and don't like hacks, you could use bumping pointer allocator to allocate objects in stack-like pool instead of heap. This pool could be allocated on stack itself. So you'll have to use pointers and virtual functions but the data are stored locally.

@sagamusix
Copy link

sagamusix commented Nov 8, 2016

Does it really matter if the object is on the stack or heap once it is created? I mean, you do not create those filter objects in your inner loops, you create them once at startup. If you create them all at the same time, chances are also high that they are all placed next to each other in memory.
Apart from that, if you absolutely must be able to place them in an array, you could probably determine which of the filter classes is the biggest (as @galchinsky just replied as I was typing, a union might work, but I am not sure if that works because the filter classes are not POD!) and only create objects of that size and put the smaller ones in such a big object. Either way, it's not "elegant". ;)

@Eugene-Y
Copy link

Eugene-Y commented Nov 11, 2016

@galchinsky @sagamusix Thank you!
For some reason I didn't get any notification that you answered the thread.
Right now I'm trying to make things work like this: allocating a storage (big enough to hold any Dsp::Filter) as a member of a class, and then use placement new() operator for creating an actual filter at that storage.
I'm still clearing out some details about correct alignment though. Getting (un)relevant error LNK2019 and so on. Need to catch up on this.
I'll post here the results.

@sagamusix I must not place filters into array, but right now I'm trying to place them as members of objects that are arranged in an array. I sure don't create filters in tight loops but want to use them in realtime audio. I know that they will anyway be cached, but even rarely fetching them from the heap doesn't look good to me.

@Eugene-Y
Copy link

Eugene-Y commented Nov 16, 2016

Sooo... I had some hard time setting up correct building and linking properties in my VS15 solution, but it compiles now. Thanks to @vinniefalco for leaving a note about VS linking bug, it was a nice starting point.
After reading about low level memory management and increasing performance with pools I feel overwhelmed to put it softly. I only became aware of the new horizons of my ignorance. For now I don't have an elegant solution, but that's what I tried:

  • Using big std::array as a storage partially works. Invoking virtual ~Filter() on existing filter in the storage through the valid Dsp::Filter * causes crash. Calling other stuff, including setParams() and process(), works. As far as I understand it i so because of the structure of filter objects in memory.
  • Using some big Dsp::Filter as a storage works fine.

So my guess is that if one wants to use, say, std::array as a storage, one has to use custom allocator.
Moreover, using some container of big Dsp::Filters as a storage would also be better with a custom allocator to decrease fragmentation and gain efficiency.

Right now I'm trying to figure out the biggest filter type during compilation to avoid templatization of the aggregating class, but I'm not sure that my beard is long enough.

@sagamusix
Copy link

Before you go through all the hassle of this - did you verify that just allocating each filter separately and putting pointers into a vector is indeed too slow for your scenario? You know, don't optimize where there is nothing to optimize... ;)

@Eugene-Y
Copy link

Eugene-Y commented Nov 16, 2016

I totally get what you mean and agree with you that I should measure first. It is easy to run basic things and get real performance numbers right away with just one selected filter type. I'll do it even out of sheer interest.
At the same time, I'm writing a musical synth, it has n voices, each voice has m filters, that's 30+ realtime filters on average. After reading/listening to guys from companies that write their own pro audio products, I guess the difference will be significant. One synth programmer said that cache-friendly code made his synth run ~3x faster. Many of them say that one of the main realtime DSP rules is never use new for objects that are used often, especially in loops.
We'll look at the numbers.

@sagamusix
Copy link

Fair enough, doing things for exercise and out of interest is of course nice.
However, I would argue that if you are looking into this kind of performance improvement, you would be better off rolling your own filters. Not that I want to say that Vinnie's code is bad or anything, in fact it's a great source for learning, but once you look into optimizing your inner loops, you should start thinking about ripping out filters and inlining them into your loop directly. After all, if you write a synthesizer, you typically do not need all of the filter types present in this library but typically only a handful.

@vinniefalco
Copy link
Owner

DSPFilters is not intended as the end-all of IIR filtering, it offers reasonable performance and a launch pad for people who want to get into filtering. As @sagamusix said, you will always get better results hand-rolling code.

@Eugene-Y
Copy link

Eugene-Y commented Nov 16, 2016

Yep, writing my filters... after some self-educating in this area I'm past the time when this idea scared me, but still there's a lot of work, and Vinnie already did most of what I need. Perhaps writing my own stuff based on Vinnie's is what I should do.
@vinniefalco can you think of specific design parts of your code that should be re-designed for a project like musical synth?

@vinniefalco
Copy link
Owner

There's no need for a synth to type-erase the filter, or to support changing the order at run-time.

@Eugene-Y
Copy link

Eugene-Y commented Nov 18, 2016

So I ran the performance tests o_O
I skipped the debug results because the CPU load was through the roof, the following is for the release configurations only. Values are given in inclusive samples.

Btw, my naive assumption that using polymorphic classes with the biggest flt as a container would work appeared to be wrong for release builds: got memory errors on most of the methods calls after the compiler's optimizations.

I used 32 fixed type (Chebyshev II 4-th order LPF) filters, ran the test for 1 minute with continuous per-sample cut-off frequency modulation, on 48KHz sr.

  • Filters in aggregated class instances:
    -- stack: modulation 21766, processing 1438
    -- heap: modulation 21957, processing 1360
  • Filters in continuous array:
    -- stack: modulation 21643, processing 895
    -- heap: modulation 21973, processing 1169

The difference is insignificant. Because the optimizations were so good and/or because the rest of my code is so amazingly good/bad.

So if there's only one parameter that needs to be modulated, Dsp::Filter could be extended with methods such as int getNumCoefficients(), double getCoefficient(int idx) and void setCoefficient(int idx) to save fine-grained spectrum of precomputed coefficient values in a static array during compilation and simply interpolating between these values during modulation. I didn't go through all the design down to the smallest cascade, but it looks that there are no such methods yet.

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

No branches or pull requests

5 participants