0

I'm using this kind of code for some different plugins I'm making:

#include <iostream>
#include <functional>

template <class T>
struct SharedDataEngine {
    T mSharedData;
    std::function<void(T &destination)> mCopySharedData;

    SharedDataEngine(std::function<void(T &destination)> copySharedData) : mCopySharedData(copySharedData) {
    } 

    void storeSharedData() {
        if (mCopySharedData != NULL) {
            mCopySharedData(mSharedData);
        }
    }
};

struct Plugin {
    float mPhase = 10.0f;

    struct SharedData {
        float mPhase;
    };
    SharedDataEngine<SharedData> mSharedDataEngine{[this](SharedData &destination) {
        destination.mPhase = mPhase;
    }};     
};

int main() {   
    Plugin plugin;

    std::cout << plugin.mSharedDataEngine.mSharedData.mPhase << std::endl;

    plugin.mPhase = 20.0f;
    plugin.mSharedDataEngine.storeSharedData();

    std::cout << plugin.mSharedDataEngine.mSharedData.mPhase << std::endl;
}

It must be soft on runtime, since it deals with audio at higher sample-rates. I'd like to ensure that functional w/ lambdas won't involve run-time "extra"processing.

Thus, would be nice to me to understand how compiler will translate that mCopySharedData within the structs. Any chance to get some fancy pseudo-code/c++ style code?

To see if there are not any "magic" once it calls that function. I could use godbolt of course and see myself the code, but that's assembly, not really my kind of language :)

EDIT: I wish it would translate mCopySharedData(mSharedData) into somethings like this:

mSharedData.mPhase = pointerToPlugin->mPhase;

Is it correct?

markzzz
  • 47,390
  • 120
  • 299
  • 507
  • 5
    There is generally going to be some run time overhead. AFAIK all implementations use type erasure so there needs to be some sort of polymorphism. related: https://stackoverflow.com/questions/14936539/how-stdfunction-works – NathanOliver Apr 09 '20 at 13:03
  • short answer: `std::function` needs a level of indirection that you can get rid of if you can restrict yourself to one particular type of callable – 463035818_is_not_an_ai Apr 09 '20 at 13:04
  • 2
    [Related](https://stackoverflow.com/q/5057382/3233393). – Quentin Apr 09 '20 at 13:04
  • Ever heard of "Premature optimization is the root of all evil"? If you can get rid of any indirection and call the function you would have bound to a std::function do it, otherwise just write that code like this first, and measure later. I bet the overhead of std::function won't be the problem. – Superlokkus Apr 09 '20 at 13:24
  • 1
    Does this answer your question? [What is the performance overhead of std::function?](https://stackoverflow.com/questions/5057382/what-is-the-performance-overhead-of-stdfunction) – Superlokkus Apr 09 '20 at 13:29
  • Just to let sure you understand what I'm asking for: that [this] how is translated by c++? A simply pointer to the calling object? and than compiler will simply do pointer->mPhase for example? – markzzz Apr 09 '20 at 13:58
  • 1
    lamdba are not `std::function`. lambda translates mainly as struct with `operator()` matching definition, with member equal to the capture. – Jarod42 Apr 09 '20 at 14:00
  • Check the Edit I did: can you confirm? – markzzz Apr 09 '20 at 14:06
  • @markzzz With `[this](SharedData &destination) { destination.mPhase = mPhase; }` What that is going to do is create a functor that has a pointer as a member and that pointer will point to what `this` points to. It will then use that pointer to access `mPhase`. So yes, for that there is basically no overhead. Where the overhead is, is the function call of the `std::function`. Since `std::function` uses type erasure, there needs to be some dynamic dispatch on that function call and that's where most of your overhead will be. – NathanOliver Apr 09 '20 at 14:46
  • @NathanOliver thanks for the reply. I don't really see "how much" overhead it must be on calling `mCopySharedData(mSharedData)`: isn't just a pointer to a function? Could you show to me a pseudo code of this dispatch? Not sure I catch it, please – markzzz Apr 09 '20 at 15:02
  • @markzzz Capturing lambdas can't decay to function pointers so what you are copying is a class object that has at least a pointer stored in it. Then that objects needs to get stored into some class that `std::function` uses to erase the type of the object. That also wont be a lot of overhead but there is a little since there is going to be a dynamic allocation (it's going to call `new`). Really the "big" cost of `std::function` comes from using the `std::function` (i.e., calling the function). That's where the virtual dispatch happens and that is the costly bit. – NathanOliver Apr 09 '20 at 15:08
  • @NathanOliver are you saying that every time it calls myCopySharedData it calls new and does memory allocation? – markzzz Apr 09 '20 at 16:04
  • @markzzz No, that only happens when you do `mCopySharedData(copySharedData)`. When you do `mCopySharedData(mSharedData)` you'll most likely get a virtual function call, which is where most of the overhead is. – NathanOliver Apr 09 '20 at 16:06
  • So basically the overhead is similar to a virtual function call? – markzzz Apr 09 '20 at 16:10
  • 1
    Pretty much. You're basically using a polymorphic functor, so it's initialized dynamically and all function calls are dynamically dispatched. The `std::function` is just a wrapper that hides that from you. – NathanOliver Apr 09 '20 at 16:14
  • So I can live with it :) Thanks man – markzzz Apr 09 '20 at 16:16

0 Answers0