82 votes

Quel est le surcoût de performance de std::function ?

J'ai entendu sur un forum utilisant std::function<> entraîne une baisse des performances. Est-ce vrai ? Si c'est le cas, la baisse des performances est-elle importante ?

99voto

Cassio Neri Points 6095

Il y a, en effet, des problèmes de performance avec std:function dont il faut tenir compte lors de son utilisation. Le principal atout de std::function Le mécanisme d'effacement des types n'est pas gratuit, et nous pouvons (mais ne devons pas nécessairement) en payer le prix.

std::function est une classe modèle qui enveloppe les types appelables. Cependant, elle n'est pas paramétrée sur le type appelable lui-même mais seulement sur ses types de retour et d'argument. Le type callable n'est connu qu'au moment de la construction et, par conséquent, std::function ne peut pas avoir un membre pré-déclaré de ce type pour contenir une copie de l'objet donné à son constructeur.

En gros (en réalité, les choses sont plus compliquées que cela) std::function ne peut contenir qu'un pointeur sur l'objet passé à son constructeur, ce qui pose un problème de durée de vie. Si le pointeur pointe vers un objet dont la durée de vie est inférieure à celle de l'objet std::function alors le pointeur interne sera suspendu. Pour éviter ce problème std::function pourrait faire une copie de l'objet sur le tas par le biais d'un appel à operator new (ou un allocateur personnalisé). L'allocation dynamique de la mémoire est ce à quoi les gens font le plus référence comme étant une pénalité de performance impliquée par std::function .

J'ai récemment écrit un article avec plus de détails et qui explique comment (et où) on peut éviter de payer le prix d'une allocation de mémoire.

Utilisation efficace des expressions lambda et de std::function

17voto

UncleBens Points 24580

Vous pouvez trouver des informations dans les documents de référence du coup de pouce : Combien de frais généraux un appel à travers boost::function entraîne-t-il ? et Performance

Cela ne détermine pas un "oui ou un non" à la fonction de boost. La baisse de performance peut être tout à fait acceptable compte tenu des exigences du programme. Le plus souvent, certaines parties d'un programme ne sont pas critiques en termes de performances. Et même dans ce cas, elle peut être acceptable. C'est seulement quelque chose que vous pouvez déterminer.

Quant à la version de la bibliothèque standard, la norme ne définit qu'une interface. Il appartient entièrement aux implémentations individuelles de la faire fonctionner. Je suppose qu'une implémentation similaire à la fonction de Boost serait utilisée.

14voto

phresnel Points 20082

Tout d'abord, l'overhead diminue avec l'intérieur de la fonction ; plus la charge de travail est élevée, plus l'overhead est faible.

Deuxièmement : g++ 4.5 ne montre aucune différence par rapport aux fonctions virtuelles :

main.cc

#include <functional>
#include <iostream>

// Interface for virtual function test.
struct Virtual {
    virtual ~Virtual() {}
    virtual int operator() () const = 0;
};

// Factory functions to steal g++ the insight and prevent some optimizations.
Virtual *create_virt();
std::function<int ()> create_fun();
std::function<int ()> create_fun_with_state();

// The test. Generates actual output to prevent some optimizations.
template <typename T>
int test (T const& fun) {
    int ret = 0;
    for (int i=0; i<1024*1024*1024; ++i) {
        ret += fun();
    }    
    return ret;
}

// Executing the tests and outputting their values to prevent some optimizations.
int main () {
    {
        const clock_t start = clock();
        std::cout << test(*create_virt()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "virtual: " << secs << " secs.\n";
    }
    {
        const clock_t start = clock();
        std::cout << test(create_fun()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "std::function: " << secs << " secs.\n";
    }
    {
        const clock_t start = clock();
        std::cout << test(create_fun_with_state()) << '\n';
        const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
        std::cout << "std::function with bindings: " << secs << " secs.\n";
    }
}

impl.cc

#include <functional>

struct Virtual {
    virtual ~Virtual() {}
    virtual int  operator() () const = 0;
};
struct Impl : Virtual {
    virtual ~Impl() {}
    virtual int  operator() () const { return 1; }
};

Virtual *create_virt() { return new Impl; }

std::function<int ()> create_fun() { 
    return  []() { return 1; };
}

std::function<int ()> create_fun_with_state() { 
    int x,y,z;
    return  [=]() { return 1; };
}

Sortie de g++ --std=c++0x -O3 impl.cc main.cc && ./a.out :

1073741824
virtual: 2.9 secs.
1073741824
std::function: 2.9 secs.
1073741824
std::function with bindings: 2.9 secs.

Alors, n'ayez crainte. Si votre conception/maintenabilité peut s'améliorer en préférant std::function sur les appels virtuels, essayez-les. Personnellement, j'aime beaucoup l'idée de ne pas imposer les interfaces et l'héritage aux clients de mes classes.

13voto

lurscher Points 5057

Cela dépend fortement si vous passez la fonction sans lier aucun argument (n'alloue pas d'espace de tas) ou non.

Cela dépend aussi d'autres facteurs, mais c'est le principal.

Il est vrai que vous avez besoin d'un point de comparaison, vous ne pouvez pas simplement dire que cela "réduit les frais généraux" par rapport au fait de ne pas l'utiliser du tout, vous devez le comparer à l'utilisation d'une autre façon de passer une fonction. Et si vous pouvez simplement vous dispenser de l'utiliser, alors il n'était pas nécessaire dès le départ.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X