2012-06-20 7 views
21

se han hecho propuestas para C++ "delegados" que tienen gastos indirectos más bajos que boost::function:¿Se han utilizado las ideas detrás de Fast Delegate (et al) para optimizar std :: function?

¿Alguna de esas ideas han utilizado para implementar std::function, dando como resultado un mejor rendimiento que boost::function? ¿Alguien ha comparado el rendimiento de std::function con el boost::function?

Quiero saber esto específicamente para el compilador GCC y libstdC++ en las arquitecturas Intel de 64 bits, pero la información sobre otros compiladores es bienvenida (como Clang).

+0

'std :: function' es una interfaz, no una implementación. Si desea preguntar sobre stdlib VC++ 's, libstdC++ o libC++ _specifically_ entonces eso es una pregunta válida, pero tal y como está tu pregunta es demasiado amplia – ildjarn

+1

@ildjarn: Lea la última frase de la cuestión. Pregunta sobre implementaciones específicas, y más específicamente sobre libstdC++. – abarnert

+0

@abarnert: ¿Cómo podría responder sin leerlo? Obviamente lo leí, y lo encuentro demasiado amplio. – ildjarn

Respuesta

28

En libstdC++ 's std::function utilizamos un tipo de unión que está convenientemente dimensionado y alineado para almacenar punteros, punteros a funciones o punteros a funciones de miembros. Evitamos una asignación del montón para cualquier objeto de función que pueden almacenarse en que el tamaño y la alineación, pero sólo si es "ubicación invariante"

/** 
* Trait identifying "location-invariant" types, meaning that the 
* address of the object (or any of its members) will not escape. 
* Also implies a trivial copy constructor and assignment operator. 
*/ 

El código se basa en la aplicación std::tr1::function y esa parte hasn' t cambiado significativamente. Creo que podría simplificarse usando std::aligned_storage y podría mejorarse especializando el rasgo para que se identifiquen más tipos como invariantes de ubicación.

La invocación del objeto de destino se realiza sin llamadas a funciones virtuales, el borrado de tipo se realiza almacenando un único puntero de función en el std::function que es la dirección de una especialización de plantilla de función. Todas las operaciones se realizan llamando a esa plantilla de función a través del puntero almacenado y pasando una enumeración que identifica qué operación se le pide que realice. Esto significa que no hay vtable y solo se necesita almacenar un único puntero de función en el objeto.

Este diseño fue contribuido por el autor original boost::function y creo que está cerca de la implementación de impulso. Consulte el documento Performance para la función Boost.Function por algún motivo. Eso significa que es bastante improbable que el std::function de GCC sea más rápido que boost::function, porque es un diseño similar de la misma persona.

N.B. nuestro std::function no admite la construcción con un asignador aún, las asignaciones que necesite hacer se realizarán usando new.


En respuesta al comentario de Emile expresan el deseo de evitar una asignación del montón por un std::function que mantiene un puntero a la función miembro y un objeto, aquí hay un pequeño truco para hacerlo (pero no lo oyó desde me ;-)

struct A { 
    int i = 0; 
    int foo() const { return 0; } 
}; 

struct InvokeA 
{ 
    int operator()() const { return a->foo(); } 
    A* a; 
}; 

namespace std 
{ 
    template<> struct __is_location_invariant<InvokeA> 
    { static const bool value = true; }; 
} 

int main() 
{ 
    A a; 
    InvokeA inv{ &a }; 

    std::function<int()> f2(inv); 

    return f2(); 
} 

el truco es que InvokeA es lo suficientemente pequeño como para caber en una pequeña memoria intermedia de objetos del function 's, y la especialización rasgo dice que es seguro para almacenar allí, por lo que el function mantiene una copia de esa objeto directamente, no en el montón.Esto requiere a a persistir mientras el puntero a ella persiste, sino que sería el caso de todos modos si el objetivo function 's era bind(&A::foo, &a).

+4

Guau, estrecho de la boca del caballo! Si construí una 'std :: function' a partir de esta expresión' std :: bind (& Object :: memberFunction, objectInstance) ', ¿eso constituiría como" invariante de ubicación "? –

+0

Si pasa un puntero y una enumeración, para mí eso implica que hay algún tipo de instrucción 'switch' en la implementación que cambia de acuerdo con el valor enum. ¿El valor de la enumeración es una constante que puede resolverse en tiempo de compilación? Si no, entonces no veo cómo esto es más eficiente que una búsqueda vtable. –

+4

No, eso no sería invariante en la ubicación, porque el resultado de esa expresión de enlace contendría el puntero a la función miembro _y_ ¡una copia de su objeto! Planeo aumentar el tamaño de la unión para que pueda contener un puntero al miembro y un puntero, de modo que 'bind (& O :: f, & o)' estaría bien, pero eso será relativamente complicado. –

3

Como se señaló en los comentarios, std :: función sólo una interfaz, y diferentes implementaciones pueden hacer cosas diferentes, pero vale la pena señalar que la norma tiene realmente algo que decir sobre este asunto. De 20.8.11.2.1/5 (que se parece más a una dirección IP que una parte de la norma):

Nota: Se anima implementaciones para evitar el uso de dinámicamente memoria asignada para los pequeños objetos que se puede llamar , por ejemplo, donde el objetivo f es un objeto que contiene solo un puntero o referencia a un objeto y un puntero de función miembro. nota -fin

Esta es la manera del estándar de fomentar ejecutores de emplear la "optimización evento pequeño", que fue motivado por los artículos citados en delegados. (Los propios artículos en realidad no hablan de los delegados en el sentido .NET. Por el contrario, utilizan el término "delegado" para referirse a las funciones miembro consolidados.)

Cuestiones relacionadas