vinniefalco / DSPFilters

A Collection of Useful C++ Classes for Digital Signal Processing
1.83k stars 375 forks source link

Container of filters. How ? #21

Open dinaiz opened 9 years ago

dinaiz commented 9 years ago

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 commented 8 years ago

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 commented 8 years ago

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

Eugene-Y commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

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:

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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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

Eugene-Y commented 7 years ago

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.

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.