Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom allocators vs. promises and packaged tasks

Are the allocator-taking constructors of standard promise/packaged_task supposed to use the allocator for just the state object itself, or should this be guaranteed for all (internal) related objects?

[futures.promise]: "...allocate memory for the shared state"
[futures.task.members]: "...allocate memory needed to store the internal data structures"

In particular, are the below bugs or features?

*MSVC 2013.4, Boost 1.57, short_alloc.h by Howard Hinnant

Example 1

#define BOOST_THREAD_VERSION 4
#include <boost/thread/future.hpp>
#include "short_alloc.h"
#include <cstdio>

void *operator new( std::size_t s ) {
    printf( "alloc %Iu\n", s );
    return malloc( s );
}

void operator delete( void *p ) {
    free( p );
}

int main() {

    const int N = 1024;
    arena< N > a;
    short_alloc< int, N > al( a );

    printf( "[promise]\n" );
    auto p = boost::promise< int >( std::allocator_arg, al );
    p.set_value( 123 );

    printf( "[packaged_task]\n" );
    auto q = boost::packaged_task< int() >( std::allocator_arg, al, [] { return 123; } );
    q();

    return 0;

}

Output:

...
[promise]
alloc 8
alloc 12
alloc 8
alloc 24
[packaged_task]
alloc 8
alloc 12
alloc 8
alloc 24

FWIW, the output with the default allocator is

...
[promise]
alloc 144
alloc 8
alloc 12
alloc 8
alloc 16
[packaged_task]
alloc 160
alloc 8
alloc 12
alloc 8
alloc 16

Example 2

AFAICT, MSVC's std::mutex does an unavoidable heap allocation, and therefore, so does std::promise which uses it. Is this a conformant behaviour?

like image 428
vpozdyayev Avatar asked Jan 21 '15 14:01

vpozdyayev


1 Answers

N.B. there are a couple of issues with your code. In C++14 if you replace operator delete(void*) then you must also replace operator delete(void*, std::size)t). You can use a feature-test macro to see if the compiler requires that:

void operator delete( void *p ) {
    free( p );
}
#if __cpp_sized_deallocation
// Also define sized-deallocation function:
void operator delete( void *p, std::size_t ) {
    free( p );
}
#endif

Secondly the correct printf format specifier for size_t is zu not u, so you should be using %Izu.

AFAICT, MSVC's std::mutex does an unavoidable heap allocation, and therefore, so does std::promise which uses it. Is this a conformant behaviour?

It's certainly questionable whether std::mutex should use dynamic allocation. Its constructor can't, because it must be constexpr. It could delay the allocation until the first call to lock() or try_lock() but lock() doesn't list failure to acquire resources as a valid error condition, and it means try_lock() could fail to lock an uncontended mutex if it can't allocate the resources it needs. That's allowed, if you squint at it, but is not ideal.

But regarding your main question, as you quoted, the standard only says this for promise:

The second constructor uses the allocator a to allocate memory for the shared state.

That doesn't say anything about other resources needed by the promise. It's reasonable to assume that any synchronization objects like mutexes are part of the shared state, not the promise, but that wording doesn't require that the allocator is used for memory the shared state's members require, only for the memory needed by the shared state itself.

For packaged_task the wording is broader and implies that all internal state should use the allocator, although it could be argued that it means the allocator is used to obtain memory for the stored task and the shared state, but again that members of the shared state don't have to use the allocator.

In summary, I don't think the standard is 100% clear whether the MSVC implementation is allowed, but IMHO an implementation that does not need additional memory from malloc or new is better (and that's how the libstdc++ <future> implementation works).

like image 175
Jonathan Wakely Avatar answered Sep 23 '22 10:09

Jonathan Wakely