2012-09-17 4 views
9

Alcune fonti sugli Internet (in particolare this one) affermano che std :: function utilizza ottimizzazioni a chiusura ridotta, ad es. esso non alloca heap se dimensioni di chiusura è inferiore a una certa quantità di dati (link qui sopra indica 16 byte per gcc)g ++: std: la funzione inizializzata con il tipo di chiusura utilizza sempre l'allocazione dell'heap?

Così sono andato a scavare attraverso g intestazioni ++

Sembra che se non viene applicata è deciso tale ottimizzazione da questo blocco di codice "funzionale" intestazione (g ++ 4.6.3)

static void 
_M_init_functor(_Any_data& __functor, _Functor&& __f) 
{ _M_init_functor(__functor, std::move(__f), _Local_storage()); } 

e alcune linee scorrimento:

static void 
_M_init_functor(_Any_data& __functor, _Functor&& __f, true_type) 
{ new (__functor._M_access()) _Functor(std::move(__f)); } 

static void 
_M_init_functor(_Any_data& __functor, _Functor&& __f, false_type) 
{ __functor._M_access<_Functor*>() = new _Functor(std::move(__f)); } 
    }; 

esempio se _Local_storage() è true_type, di posizionamento nuova si chiama, in caso contrario - regolare nuovo

defintion della _Local_storage è la folowing:

typedef integral_constant<bool, __stored_locally> _Local_storage; 

e __stored_locally:

static const std::size_t _M_max_size = sizeof(_Nocopy_types); 
static const std::size_t _M_max_align = __alignof__(_Nocopy_types); 

static const bool __stored_locally = 
(__is_location_invariant<_Functor>::value 
&& sizeof(_Functor) <= _M_max_size 
&& __alignof__(_Functor) <= _M_max_align 
&& (_M_max_align % __alignof__(_Functor) == 0)); 

e infine: __is_location_invariant:

template<typename _Tp> 
struct __is_location_invariant 
: integral_constant<bool, (is_pointer<_Tp>::value 
       || is_member_pointer<_Tp>::value)> 
{ }; 

Così. per quanto posso dire, il tipo di chiusura non è né un puntatore né un puntatore membro. Per verificare che ho anche scritto un piccolo programma di test:

#include <functional> 
#include <iostream> 

int main(int argc, char* argv[]) 
{ 
    std::cout << "max stored locally size: " << sizeof(std::_Nocopy_types) << ", align: " << __alignof__(std::_Nocopy_types) << std::endl; 

    auto lambda = [](){}; 

    typedef decltype(lambda) lambda_t; 

    std::cout << "lambda size: " << sizeof(lambda_t) << std::endl; 
    std::cout << "lambda align: " << __alignof__(lambda_t) << std::endl; 

    std::cout << "stored locally: " << ((std::__is_location_invariant<lambda_t>::value 
    && sizeof(lambda_t) <= std::_Function_base::_M_max_size 
    && __alignof__(lambda_t) <= std::_Function_base::_M_max_align 
    && (std::_Function_base::_M_max_align % __alignof__(lambda_t) == 0)) ? "true" : "false") << std::endl; 
} 

e l'output è:

max stored locally size: 16, align: 8 
lambda size: 1 
lambda align: 1 
stored locally: false 

Quindi, le mie domande è la seguente: è INIZIALIZZA std :: funzione con lambda si traduce sempre con il mucchio allocazione? O mi sta sfuggendo qualcosa?

+0

Confermo i vostri risultati da questo programma: http://ideone.com/kzae6U Puoi controllare clang (http: // melpon.org/wandbox/) che lo stesso programma fa solo allocazione di memoria per acquisizione molto grande ... – PiotrNycz

risposta

1

std :: L'allocazione della funzione è un dettaglio di implementazione; tuttavia, l'ultima volta che ho controllato, 12 byte è la dimensione massima del functor per msvc, 16 per gcc, 24 per boost + msvc.

+0

adzm: sì, questo è menzionato nel link all'inizio della domanda. Tuttavia, non vedo che questo sia effettivamente il caso di g ++ –

2

Scommetto che se hai aggiunto questo:

std::cout << "std::__is_location_invariant: " << std::__is_location_invariant<lambda_t>::value << std::endl; 

si otterrebbe indietro:

std::__is_location_invariant: 0 

Almeno questo è quello che ideone says.

+0

Sì, questo è praticamente il mio test. La domanda è: questo è definitivo? Dr. Dobbs ha torto e abbiamo sempre allocazioni di heap? –

+0

@AlexI., Dipende molto dal compilatore. – MSN

7

A partire da GCC 4.8.1, la funzione std :: in libstdC++ ottimizza solo i puntatori a funzioni e metodi. Quindi, indipendentemente dalla dimensione del tuo functor (incluso lambda), l'inizializzazione di una funzione std :: attiva l'allocazione dell'heap. Sfortunatamente non esiste alcun supporto per gli allocatori personalizzati.

Visual C++ 2012 e LLVM libC++ evitano l'allocazione per qualsiasi funtore sufficientemente piccolo.

Nota: per questa ottimizzazione, il proprio funtore deve soddisfare lo std :: is_nothrow_move_constructible. Questo è per supportare noex std :: function :: swap(). Fortunatamente, lambda soddisfa questo requisito se tutti i valori catturati lo fanno.

È possibile scrivere un semplice programma per controllare il comportamento su vari compilatori:

#include <functional> 
#include <iostream> 

// noexpect missing in MSVC11 
#ifdef _MSC_VER 
# define NOEXCEPT 
#else 
# define NOEXCEPT noexcept 
#endif 

struct A 
{ 
    A() { } 
    A(const A&) { } 
    A(A&& other) NOEXCEPT { std::cout << "A(A&&)\n"; } 

    void operator()() const { std::cout << "A()\n"; } 

    char data[FUNCTOR_SIZE]; 
}; 

int main() 
{ 
    std::function<void()> f((A())); 
    f(); 

    // prints "A(A&&)" if small functor optimization employed 
    auto f2 = std::move(f); 

    return 0; 
}