web-dev-qa-db-fra.com

Quelle est la surcharge de performances de std :: function?

J'ai entendu sur un forum en utilisant std::function<> entraîne une baisse des performances. Est-ce vrai? Si c'est vrai, est-ce une grosse baisse de performance?

59
user408141

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

Cela ne détermine pas "oui ou non" pour augmenter la fonction. La baisse de performance peut être bien acceptable compte tenu des exigences du programme. Plus souvent qu'autrement, certaines parties d'un programme ne sont pas critiques en termes de performances. Et même alors, cela peut être acceptable. C'est seulement quelque chose que vous pouvez déterminer.

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

14
UncleBens

Il y a, en effet, des problèmes de performances avec std:function qui doit être pris en compte lors de son utilisation. La force principale de std::function, à savoir son mécanisme d'effacement des caractères, n'est pas gratuit et nous pourrions (mais pas nécessairement) en payer le prix.

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

En gros (en fait, les choses sont plus compliquées que ça) 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 du std::function objet, le pointeur intérieur se balancera. Pour éviter ce problème std::function peut faire une copie de l'objet sur le tas via un appel à operator new (ou un allocateur personnalisé). L'allocation dynamique de mémoire est ce que les gens appellent le plus comme une baisse des performances 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 mémoire.

http://drdobbs.com/cpp/232500059

74
Cassio Neri

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 également d'autres facteurs, mais c'est le principal.

Il est vrai que vous avez besoin de quelque chose pour comparer, vous ne pouvez pas simplement dire que cela `` réduit les frais généraux '' par rapport à 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 ce n'était pas nécessaire depuis le début

11
lurscher

Premièrement, la surcharge diminue avec l'intérieur de la fonction; plus la charge de travail est élevée, plus les frais généraux sont faibles.

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 via des appels virtuels, essayez-les. Personnellement, j'aime vraiment l'idée de ne pas forcer les interfaces et l'héritage sur les clients de mes classes.

10
Sebastian Mach