1
votes

"Les lambdas sont bon marché" - Vraiment? Dans quelles circonstances?

Cet article déclare que l'utilisation de Lambdas C ++ est "bon marché": Les lambdas ne sont pas magiques - partie 2 Ils montrent comment passer des lambdas aux fonctions / modèles std existants. Un article montre comment utiliser "auto" comme type de retour d'une fonction pour renvoyer un lambda sans utiliser std :: function.

Aucun article que j'ai vu ne montre comment créer vos propres fonctions, esp. fonctions membres de classe, qui prennent un lambda ou plus, sans utiliser std :: function.

Donc, cette déclaration en gras de "lambdas sont bon marché" - est-ce vraiment vrai, dans des scénarios du monde réel?

Comme référence: "Pas cher" est pour moi dans cette quête particulière de résoudre ce problème: raisonnablement utilisable sur des projets bare metal embarqués avec quelques centaines de kilo-octets de mémoire et des vitesses MHz à deux chiffres. (J'ai utilisé un sous-ensemble raisonnable de C ++ dans ce domaine et je cherche ce que je peux utiliser d'autre)

D'après ce que j'ai vu, std :: function n'est pas bon marché. Les lambdas passés en tant que fonction std :: ne peuvent apparemment plus être optimisés en ligne, d'une part. Mais pire, une fonction std :: function fait 32 octets. Apparemment également, si plus que ce nombre est capturé, l'allocation dynamique pourrait être utilisée? Tout cela semble être une mauvaise nouvelle.

Donc, pendant que je cherchais des moyens d'utiliser lambdas sans std :: function, et que je n'ai trouvé qu'un exemple retournant auto, j'ai essayé ceci: J'ai fait une classe très simple qui utilise "auto" pour les types d'argument dans les fonctions membres, et le compilateur en a semblé satisfait (bien que ce ne soit pas aussi "auto-documenté" que std :: function en ce qui concerne les arguments de foncteurs attendus).

struct FuncyClass
{   unsigned func(auto fnx)
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    auto result = fc.func( [](auto x){return x*3;} );
    
    printf("Result: %u\n", result);
    return 0;
}
// Output: "Result: 42"

Mais j'ai le sentiment que ce scénario très simple ne me montre pas d'éventuelles inondations d'erreurs de compilation à venir lorsque cela sera utilisé par des scénarios plus compliqués. Je ne comprends pas profondément ce qui se passe dans les coulisses avec cette syntaxe, ce que fait le compilateur lorsqu'il détermine, à partir de l'utilisation du foncteur, quels arguments et type de retour sont attendus, et comment cette fonction avec des arguments automatiques est implémentée sous le capot .

Est-ce que cela , c'est-à-dire en utilisant des arguments typés automatiquement, est vraiment une manière sensée de rendre vos fonctions membres de classe lambda-personnalisables? Ensuite, cela semblerait "bon marché", car à un moment donné dans mon test, l'exécutable était considérablement plus petit en utilisant auto au lieu de std :: function.

Il est toujours limité, bien sûr: Il n'y a aucun moyen pour une classe de conserver un lambda sans utiliser std :: function (ou un wrapper DIY de type similaire), n'est-ce pas? Serait-il possible d'empêcher les allocations dynamiques de se produire, par exemple ce qui en fait une erreur de compilation lorsqu'un scénario se produit qui nécessiterait que std :: function alloue de la mémoire?


6 commentaires

Lambda lui-même est bon marché. std :: function ne l'est pas, mais vous n'êtes pas obligé de l'utiliser. Les lambdas non capturants sont convertibles en pointeurs de fonction, les lambdas capturants peuvent être passés en tant que modèles.


std :: function est cher car il utilise l'effacement de type. Un lambda est juste un foncteur à main courte et ceux-ci sont "bon marché" en comparaison. Vous pouvez faire de votre classe un modèle et lui donner le type lambda pour éviter d'utiliser std :: function .


"pas obligé d'utiliser std :: function" - ouais, mais c'est malheureusement la "façon de le faire" comme le montre probablement 10 articles / articles de blog que j'ai lus à ce sujet - donc j'ai l'impression que c'est le moyen d'employer utilement des lambdas dans vos propres classes.


Presque toutes les fonctions de bibliothèque standard qui prennent un paramètre de fonction / functor (par exemple std :: sort , etc.) le font sans utiliser std :: function . Ils utilisent des modèles.


@NathanOliver - Je dois explorer que l'utilisation de modèles pour l'éviter, sonne bien, bien que limitant les cas d'utilisation ou compliquant les choses (les modèles peuvent ajouter un fardeau dans certains scénarios, si vous ne voulez pas tout créer des modèles :))


C'est le compromis. Si vous connaissez le type, de nombreuses optimisations peuvent se produire, mais cela rend le code plus complexe car vous devez gérer les types. Si vous passez "sans type" ( std :: function ) alors le code est plus facile à utiliser, mais vous payez une pénalité de performance.


3 Réponses :


7
votes

Les lambdas ne sont que des objets qui surchargent operator () . Vous pouvez les considérer conceptuellement comme équivalents à:

void (*fp)(int) = [](int){ /* ... */ };

Donc, ils ne sont pas plus chers qu'un appel de fonction similaire. std :: function n'est pas nécessaire pour utiliser des lambdas. Vous pouvez utiliser la déduction de type pour les stocker / les transmettre:

template <typename Func>
void foo(Func&& func) { /* ... */ }

Pour les lambdas sans capture, vous pouvez les convertir en pointeurs de fonction implicitement ou explicitement avec operator + code >:

class Lamba {
public:
    auto operator()(...) const { /* ... */ }
};

La raison pour laquelle il est difficile de répondre à votre question est que vous vous demandez si les lambdas sont chères. Mais, en comparaison avec quoi? Si vous voulez savoir s'ils sont suffisamment performants dans votre cas spécifique, vous devrez faire un profilage pour vous-même et le découvrir.


1 commentaires

"par rapport à quoi" - j'ai cependant donné une référence approximative. Il existe des façons plus larges et pratiques de voir les choses, certaines choses peuvent parfois être classées comme étant prohibitives pour toute une classe de scénarios sans mesurer en nanosecondes combien c'est vraiment mauvais, et je soupçonne que cela pourrait être un tel cas. Vous n'insisterez pas pour mesurer avec un microvoltmètre à dérive zéro, que vous souhaitiez remplacer une batterie ou non.



2
votes

std :: function est bien, car il a une syntaxe simple (beaucoup plus facile que les pointeurs de fonction) et il auto-documente le type de fonction requis (par rapport aux modèles, qui nécessitent une documentation explicite) . Je suppose que les articles d'introduction sur les lambdas l'utiliseront, car le coût n'est pas visible tant que vous n'avez pas certaines restrictions.

Il existe deux autres façons d'accepter lambda comme paramètre: les pointeurs de fonction et les modèles.

Non capturant lambda peut être converti implicitement en pointeur de fonction:

struct FuncyClass
{   
    template<typename Func>
    unsigned func(Func&& fnx)
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    int multiplier = 3;
    auto result = fc.func( [multiplier](auto x){return x*multiplier;} );
    
    printf("Result: %u\n", result);
    return 0;
}

Les lambdas de capture et de non capture peuvent être transmis à l'aide de modèles:

struct FuncyClass
{   unsigned func(int(*fnx)(int)) 
    {   return 2 * fnx(7);
    }
};

int main()
{   FuncyClass fc;
    auto result = fc.func( [](auto x){return x*3;} );
    
    printf("Result: %u\n", result);
    return 0;
}


1 commentaires

On dit que peut être passé comme modèle mais que vous ne transmettez rien à la définition de modèle, laissant le compilateur inférer des substitutions à partir de lamda. Ça pourrait être déroutant



1
votes

"Les lambdas sont bon marché"

C'est une notion relative.

Et en pratique, cela dépend beaucoup de votre compilateur C ++.

Essayez d'utiliser un GCC récent (en 2020, utilisez GCC 10 ) et activer les avertissements et les optimisations, donc compilez votre code C ++ -sur la ligne de commande- avec g ++ -Wall -Wextra -O2 au moins.

Ensuite, comparez ou profile votre application

Sous Linux, consultez time (7) et envisagez d'utiliser gprof (1) ou perf (1) (bien sûr, une fois que vous avez débogué votre programme avec GDB ). Avec d'autres systèmes d'exploitation et compilateurs, trouvez un équivalent.

Ce brouillon de rapport peut vous suggérer ce que optimisations qu'un bon compilateur pourrait faire (et parfois pas, à cause de Théorème de Rice ). Il peut parfois arriver qu’une application lambda devienne intégrée .

Si vous aimez le paradigme de programmation fonctionnelle , envisagez également d'utiliser Ocaml , Common Lisp ( SBCL ) ou Haskell . Vous trouverez des cas où il pourrait être en pratique légèrement plus rapide que C ++ (avec GCC ou Clang ), en particulier sous Linux pour les programmes à un seul thread.

En règle générale, j'ai tendance à croire qu'un lambda C ++ est bon marché dès que son corps effectue un travail important, comme itérer (ou rechercher) dans une Conteneur C ++ . Si le corps lambda ne fait qu'une addition entière, la surcharge est significative (à moins que le compilateur ne soit assez intelligent pour l'intégrer). S'il effectue une find opération sur une std :: map avec des milliers d'entrées, ou en fait en utilisant l'allocation dynamique (donc certains nouveaux , souvent en utilisant malloc ), il n'est généralement pas significatif.

En pratique, vous devez profiler votre candidature depuis YMMV.


3 commentaires

Compte tenu de l'impression initiale que j'ai eue que std :: function est difficilement évitable à utiliser en conjonction avec lambdas, et que j'estime que la plupart des gens considèrent que "les allocations dynamiques peuvent se produire" comme "pas bon marché", cela ne me semble pas si vague . Fondamentalement, la "preuve" que j'espérais du "oui, c'est vraiment bon marché" devait être montrée que l'utilisation de lambdas sans std :: function est à la fois posisble et pratique - j'espérais que ce sentiment ressortirait dans mon message.


@sktpin: Si votre question est vraiment de savoir si vous pouvez utiliser des lambdas sans les capturer dans une std :: function (ou une opération similaire basée sur l'allocation), alors vous devriez peut-être ajustez votre titre et le texte de votre question en conséquence, plutôt que d'utiliser des questions sur ce qui est «bon marché», etc.


La motivation derrière la question est que je veux savoir si les lambdas peuvent être pratiquement utilisées «à bon marché». Quelque chose qui semblait faire obstacle à cela était std :: function, d'où le focus. Ma compréhension de cela peut être erronée cependant, et il peut aussi y avoir d'autres aspects qui me manquent. Donc, poser des questions sur std :: function ne donnerait peut-être pas une image complète qui est réellement nécessaire pour prendre des décisions pratiques. Pourquoi la réflexion sur le SO est-elle telle que seules les questions au niveau subatomique sont considérées comme bonnes? Il semble presque que vous puissiez essentiellement répondre à votre question vous-même.