3

This answer states that std::bind returns an object by value, and this comment implies that assigning to std::function will cause a heap allocation to store the value returned by std::bind.

Is there a way to avoid this heap allocation and pass the return value of std::bind to another function directly by value?

If so, what would the method signature replace std::function with?


To be more explicit, I have a function like the following.

void runThisFunction(std::function<void()> func);

Suppose there is a function foo with the following signature.

void foo(int a, int b);

Now, I would invoke runThisFunction as follows.

runThisFunction(std::bind(foo, 1, 2));

In this invocation, the output of std::bind is converted into an std::function, and dynamic memory allocation occurs as part of this process.

Is it possible to replace std::function with some other declaration that would would receive the output of std::bind directly by value, thereby avoiding the dynamic memory allocation?

Community
  • 1
  • 1
merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • 2
    does `template void runThisFunction(T func)` count? – Piotr Skotnicki Apr 14 '16 at 08:21
  • @PiotrSkotnicki, That feels like it might work. It does suffer from the usual problem with templates (generating one copy of code for every type), but I suppose it can't be helped. – merlin2011 Apr 14 '16 at 08:29
  • 2
    Not sure why this question was downvoted. It simply did not occur to me to use a template to solve this problem, although it seems obvious in hindsight. – merlin2011 Apr 14 '16 at 08:33
  • If you don't like templates, and the callers don't vary enough to actually need them, you could try `void runThisFunction(decltype(std::bind(foo, 1, 2))& func);` - I suspect you'd be able to call it with any `std::bind()` invocation where the argument's static types match `foo`, `1` and `2`, but you'd have to dig into the Standard to see if that's guaranteed. – Tony Delroy Apr 14 '16 at 10:07
  • How often are you running this function? On the equivalent of a per-pixel per-frame basis or similar? (like many millions of times/second?) – Yakk - Adam Nevraumont Apr 14 '16 at 14:39

4 Answers4

4

Is it possible to replace std::function with some other declaration that would would receive the output of std::bind directly by value, thereby avoiding the dynamic memory allocation?

Yes it is; but the type returned by std::bind is unspecified, so you will need to use a template to capture the type;

template <typename F>
void runThisFunction(F func);

On the memory allocation...

In this invocation, the output of std::bind is converted into an std::function, and dynamic memory allocation occurs as part of this process.

Dynamic memory may be used (but not always), it depends on the size of the functor being bound into the std::function and the quality of implementation.

Further, the C++ spec has this §20.12.12.2.1/11;

[Note: Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, where f is an object holding only a pointer or reference to an object and a member function pointer. — end note ]

I would not be too concerned about the memory allocation even if there is one. Unless the code is performance critical and you have measured it as such, the indirection required should not be a problem.

Bear in mind that for your case, the foo being bound in the bind is a pointer and it is likely that there would be no dynamic memory allocation anyway.


I started looking at this because I measured cache misses on the conversion due to unexpected slowness detected through instrumentation

So you have some measured concerns about the performance... there are alternatives to using std::bind paired with std::function. std::bind is useful general purpose binder, but that doesn't mean it will also be performant enough - make your own. A custom functor could be more performant. A lambda based implementation would also be good to look at. Don't forget either that the function foo can be used with std::function as well and then you forgo the functor/binder completely (caveat, the signatures need to match).


A side note on how "small" the object needs to be before the "small object" optimisations mentioned in the quote above kick in seems to vary between the library implementations quiet a bit.

Here on coliru (libstdc++), the size of the argument for std::function needs to be 16 bytes or less, on MSVC, the limit is 32 bytes (both of these look to be 32-bit platforms). With a clang++ (libc++) 64-bit compile, this limit is 24 bytes... It really is up to the implementation how much space they will allow for before new allocations need to be made.

I'm not sure how critical the performance is, but calculating this limit for your targets could also be done and then optimisations applied such that the arguments for the std::function are kept below these limits; e.g. using a pointer or reference (also std::ref) to a struct for arguments (but care must be taken that these are not left dangling).

Community
  • 1
  • 1
Niall
  • 30,036
  • 10
  • 99
  • 142
  • I started looking at this because I measured cache misses on the conversion due to unexpected slowness detected through instrumentation. I think this answer will work, but I'll accept after I test it tomorrow. Thanks! – merlin2011 Apr 14 '16 at 08:31
  • This answer works, but making the receiving function templated turns out to be more painful than I expected, because it means pulling a lot of state into a header file that I had originally hidden neatly into the cc file. – merlin2011 Apr 14 '16 at 18:33
  • It is one of the draw backs, unspecified types tend to do that. I would say you could investigate a redesign, but that is whole other problem and could also be overkill. – Niall Apr 14 '16 at 19:24
2

If performance is important but you still want a concrete interface, consider binding to a lambda rather than a binder object (via std::bind).

I ran this test on gcc 5.3 (libstdc++, -O2)

#include <functional>

void foo(int, int);

void runThisFunction(std::function<void()> func);

void test()
{
  runThisFunction(std::bind(&foo, 1, 2));
}

void test1()
{
  runThisFunction([]{ foo(1, 2); });
}

test() results in a call to new. However, the small function optimisation in std::function is able to detect that the lambda in test1() is small enough and a call to new is not emitted into the code:

(note: exception handling code removed for clarity):

std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation):
        testl   %edx, %edx
        je      .L3
        cmpl    $1, %edx
        jne     .L2
        movq    %rsi, (%rdi)
.L2:
        xorl    %eax, %eax
        ret
.L3:
        movq    typeinfo for test1()::{lambda()#1}, (%rdi)
        xorl    %eax, %eax
        ret
std::_Function_handler<void (), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&):
        movl    $2, %esi
        movl    $1, %edi
        jmp     foo(int, int)
test1():
        subq    $40, %rsp
        movq    %rsp, %rdi
        movq    std::_Function_handler<void (), test1()::{lambda()#1}>::_M_invoke(std::_Any_data const&), 24(%rsp)
        movq    std::_Function_base::_Base_manager<test1()::{lambda()#1}>::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager<test1()::{lambda()#1}> const&, std::_Manager_operation), 16(%rsp)
        call    runThisFunction(std::function<void ()>)
        movq    16(%rsp), %rax
        testq   %rax, %rax
        je      .L7
        movl    $3, %edx
        movq    %rsp, %rsi
        movq    %rsp, %rdi
        call    *%rax
.L7:
        addq    $40, %rsp
        ret
typeinfo for test1()::{lambda()#1}:
typeinfo name for test1()::{lambda()#1}:
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • The guaranty of no allocation in `std::function` is for pointer and `reference_wrapper` (and encouraged for small object). Your lambda is not capturing anything here (so may be converted in function pointer). Things might be different with capture. – Jarod42 Apr 14 '16 at 10:53
  • @Jarod42 yes, the small function optimisation will have a limit on its aggressiveness depending on the standard library implementation. However, I can't speculate on future scenarios the OP might have - this is the one he presented. – Richard Hodges Apr 14 '16 at 11:09
  • It's interesting to know that lambda performs better under some circumstances, considering the fact that I switched to `std::bind` specifically because [lambda didn't work under gcc](http://stackoverflow.com/questions/35474417/is-it-possible-to-capture-a-variable-number-of-parameters-in-a-lambda). – merlin2011 Apr 14 '16 at 17:44
  • @merlin2011 interesting, but you saw the workaround? to pass the callable by value? I would have thought that copy-elision will mean that it's no slower. – Richard Hodges Apr 14 '16 at 18:09
  • @RichardHodges, I used `std::bind` specifically because the lambda with varargs would not compile. – merlin2011 Apr 14 '16 at 18:27
1

By using reference wrapper, you should avoid the heap allocation:

auto f = std::bind(foo, 1, 2);
runThisFunction(std::ref(f));

This because reference_wrapper is a small object and std::function is encouraged to avoid allocation for small (see [func.wrap.func.con]):

Note: Implementations are encouraged to avoid the use of dynamically allocated memory for small callable objects, for example, where f’s target is an object holding only a pointer or reference to an object and a member function pointer.

It will only work if runThisFunction() is not storing the value of the std::function for calling after the lifetime of f.

ysdx
  • 8,889
  • 1
  • 38
  • 51
  • 1
    Neat alternative with the `std::ref`. I don't think a simple invocation of the function is all the `runThisFunction` does, thus this needs to come with the caveat that the bind `f` needs to remain alive for at least as long as the `runThisFunction` requires the `std::function` (and whatever else `runThisFunction` feeds the function to, e.g. a list of some sort). – Niall Apr 14 '16 at 11:23
-1

very simple (at least for gcc):

int someFunc(int a, int b) { return a+b; }
//....
int b = (std::bind(someFunc,3,4))(); // returns 7
Andrei R.
  • 2,374
  • 1
  • 13
  • 27