web-dev-qa-db-fra.com

Comprendre la surcharge des fonctions lambda en C ++ 11

Cela a déjà été abordé dans Pourquoi C++ lambda est plus lent que la fonction ordinaire lorsqu'il est appelé plusieurs fois? et C++ 0x Lambda overhead Mais je pense que mon exemple est un peu différent du discussion dans le premier et contredit le résultat dans le second.

Lors de la recherche d'un goulot d'étranglement dans mon code, j'ai trouvé une fonction de modèle récursif qui traite une liste d'arguments variadiques avec une fonction de processeur donnée, comme copier la valeur dans un tampon.

template <typename T>
void ProcessArguments(std::function<void(const T &)> process)
{}

template <typename T, typename HEAD, typename ... TAIL>
void ProcessArguments(std::function<void(const T &)> process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

J'ai comparé le temps d'exécution d'un programme qui utilise ce code avec une fonction lambda ainsi qu'une fonction globale qui copie les arguments dans un tampon global à l'aide d'un pointeur mobile:

int buffer[10];
int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>([&p](const int &v) { *p++ = v; }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }
}

compilé avec g ++ 4.6 et -O3 mesure avec le temps de l'outil prend plus de 6 secondes sur ma machine tout en

int buffer[10];
int *p = buffer;
void CopyIntoBuffer(const int &value)
{
  *p++ = value;
}

int main(int argc, char **argv)
{
  int *p = buffer;

  for (unsigned long int i = 0; i < 10E6; ++i)
  {
    p = buffer;
    ProcessArguments<int>(CopyIntoBuffer, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  }

  return 0;
}

prend environ 1,4 seconde.

Je ne comprends pas ce qui se passe dans les coulisses qui explique le temps supplémentaire et je me demande si je peux changer quelque chose pour utiliser les fonctions lambda sans payer avec l'exécution.

28
mcbulba

Le problème ici est votre utilisation de std :: function. Vous l'envoyez par copie et copiez donc son contenu (et faites cela de manière récursive en déroulant les paramètres).

Maintenant, pour que le pointeur fonctionne, le contenu est simplement un pointeur pour fonctionner. Pour lambda, le contenu est au moins un pointeur vers la fonction + référence que vous avez capturée. C'est deux fois plus à copier. De plus, en raison de l'effacement du type de std :: function, la copie de toutes les données sera probablement plus lente (non alignée).

Il y a plusieurs options ici, et la meilleure serait probablement de passer non pas std :: function, mais plutôt un modèle. Les avantages sont que votre appel de méthode est plus susceptible d'être aligné, aucun effacement de type ne se produit par std :: function, aucune copie ne se produit, tout est donc très bon. Comme ça:

template <typename TFunc>
void ProcessArguments(const TFunc& process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(const TFunc& process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

La deuxième option fait de même, mais en envoyant le process par copie. Maintenant, la copie a lieu, mais elle est toujours parfaitement alignée.

Ce qui est tout aussi important, c'est que process 'body peut également être aligné, en particulier pour lamda. Selon la complexité de la copie de l'objet lambda et sa taille, le passage par copie peut ou non être plus rapide que le passage par référence. Cela peut être plus rapide car le compilateur peut avoir plus de mal à raisonner sur la référence que la copie locale.

template <typename TFunc>
void ProcessArguments(TFunc process)
{}

template <typename TFunc, typename HEAD, typename ... TAIL>
void ProcessArguments(TFunc process, const HEAD &head, const TAIL &... tail)
{
  process(head);
  ProcessArguments(process, tail...);
}

La troisième option est, bien, essayez de passer std :: function <> par référence. De cette façon, vous évitez au moins la copie, mais les appels ne seront pas intégrés.

Voici quelques résultats de performance (en utilisant le compilateur C++ 11 d'idéones). Notez que, comme prévu, le corps lambda en ligne vous offre les meilleures performances:

Original function:
0.483035s

Original lambda:
1.94531s


Function via template copy:
0.094748

### Lambda via template copy:
0.0264867s


Function via template reference:
0.0892594s

### Lambda via template reference:
0.0264201s


Function via std::function reference:
0.0891776s

Lambda via std::function reference:
0.09s
41
Artem Tokmakov