Optimisation étrange dans un bout de code

a marqué ce sujet comme résolu.

Salut,

Je viens tout juste de récupérer un projet C++ que je dois faire évoluer.

Mon problème réside certaines obscures optimisations d'une partie en C de l'équipe précédente et notamment une qui me laisse perplexe.

J'ai la fonction suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void send( bool appelBidon )
{
     long a;
     long b;
     char c[100];
     if ( !appelBidon )
     {
         /* Enorme bloc d'instructions qui consiste à créer un message binaire de 100 octets
            et l'émettre sur une socket */
     }
}

Les variables a et b contiennent des durées basées sur l'heure système. La variable c contient le message à émettre.

Cette fonction est appelée comme suit :

1
2
3
4
5
6
while ( 1 )
{
     send( true );
     [...]
     send( false );
}

En gros, j'appelle une première fois ma fonction en exécutant uniquement les déclarations des variables locales. Puis, je l'appelle réellement un peu plus loin dans ma boucle.

Dans la documentation, j'ai la remarque suivante sur ce bout de code : Premier appel bidon pour optimisation. send() prend du temps et cela permet de mettre en cache.

J'ai beau essayé de comprendre, je ne vois absolument pas où il peut bien y avoir une optimisation, là-dedans (surtout que dans tous les cas, nous sommes cadencés à 200ms). J'ai bien pensé à la mise en cache des variables locales, mais ça ne tient pas la route…

Je ne me suis pas fait prié pour supprimer tout ça (le projet y survivra sans problème). Mais je voudrais bien comprendre ce qu'ils ont tenté de faire et, peut-être, suis-je passé à côté de quelque chose. Alors, est-ce qu'il s'agit vraiment d'une optimisation ou simplement d'une aberration ?

Il n'est pas rare que le premier appel d'une fonction coute plus que les autres – je connaissais plus avec des allocations dynamiques. Mais ce qui est fait me parait bien ridicule. Si un appel isolé à f() coûte x + y + epsilon ms, et qu'un appel f(true) coute x + epsilon ms, et f(false) coute x + epsilon ms, … Comment dire ? Ce qui est fait est ridicule.

Lu'!

Pour être sûr, il faudrait voir le contenu du bloc "if". Dans l'idée, il est fort possible que ce bloc contiennent des déréférencements pour accéder à des données "lointaine". Alors, oui le bloc n'est pas exécuté, mais pour gagner du temps, le processeur peut commencer à fetcher les données avant que la décision du if soit prise "au cas où" (grâce au pipeline d'exécution). Puis une fois l'évaluation effectuée, laisser tomber l'exécution du bloc.

Bon, après cette technique est doublement stupide. Premièrement, il existe une instruction pour prefetcher des données, donc pas besoin d'utiliser ce genre de technique obscure. Mais surtout, deuxièmement, le compilateur est (très) fort pour placer les prefetching au bon endroit (si bien que typiquement, dans le noyau linux, les instructions de pre-fetch ont été dégagées car elles perturbaient le travail du compilateur et baissaient les perfs (!)).

L'interprétation de Ksass`Peuk n'est pas la bonne. Le branchement suit un pattern Pris, non-pris, pris, non-pris, etc; qui est parfaitement prédit par l'unité de prédiction de branchements : pas d'execution du code du if dans le dummy call, et donc pas de prefetching.

A mon avis, cela permet de précharger le code de send() en mémoire cache d'instruction.

Sans le code optimisé, la fonction send doit être chargée en cache d'instruction à la fin de la fonction, et on n'a rien à faire en parallèle du cache miss. Avec le code optimisé, on peut exécuter le code qui suit le cache miss, qui est indépendant du code de la fonction, en même temps que le cache miss de send : il suffit que le cache soit non-bloquant et que le processeur soit à exécution dans le désordre. Et comme cela, lors de l'appel à send, cache hit.

Quand on sait que les compilateurs la plupart des compilateurs ne gèrent pas les instructions de prefetch, sauf avec certaines options de compilation, et qu'ils sont particulièrement moisis pour gérer le prefetch, et notamment celui des instructions, je comprends que certains en arrivent à faire ça à la main dans des situations désespérées. Enfin presque…

+2 -0

Il est possible que gcc optimise en décomposant ta fonction send en 2 fonctions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void send_true()
{
     long a;
     long b;
     char c[100];

/* Enorme bloc d'instructions qui consiste à créer un message binaire de 100 octets
            et l'émettre sur une socket */
}

void send_false()
{
     long a;
     long b;
     char c[100];
}

while ( 1 )
{
     send_true();
     [...]
    send_false();
}

Merci à tous. Ça me conforte dans l'idée que cette "optimisation" n'a rien à faire dans le code…

En fait, ce qui me gêne le plus est que, pour moi, s'ils avaient vraiment voulu faire du prefetch, ils auraient fait l'appel à send(false) juste avant la boucle. Il n'y a que quelques lignes entre les deux appels qui consistent à afficher quelques logs et d'autres opérations inutiles (sans doute des reliquats d'une plus ancienne version).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte