1
votes

Alternative à un grand groupe d'énoncés if?

J'ai un ensemble d'instructions If (beaucoup d'entre elles, environ 40), et elles vérifient chacune une entrée pour une carte intégrée. Pour une raison quelconque, parcourir le groupe d'instructions est vraiment lent.

uint32_t INPUT_ARRAY[40];
#define INPUT_X INPUT_ARRAY[0] // input 1 corresponds to the first array slot and so on, easy to call up a specific input.

void main(){
    while(1) // infinite loop for embedded program
    { 
        Read_Inputs(); // input read function

        Write_Outputs(); // output write function

        Logic_Test(); // This is to test out the inputs and outputs on our hardware test rig
    }
}

inline void Logic_Test(void){
    if ( INPUT_1 != 0){
       output_3 |= some_bit // this logic could change
       output_10 |= another_bit 
    }
    if ( INPUT_2 != 0){
       output_x |= some_bit 
    }
    if ( INPUT_3 != 0){
       output_x |= some_bit 
    }
    if ( INPUT_4 != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_X != 0){
       output_x |= some_bit // this logic could change
    }
    if ( INPUT_40 != 0){
       output_x |= some_bit 
    }
}

Le code est structuré comme ci-dessus et de 1 à 40. Ce ne sont cependant pas des instructions if-else-if. J'utiliserais un interrupteur, mais d'après ma connaissance, un interrupteur ne couvre qu'un seul cas à la fois, et plusieurs entrées pourraient être activées. Existe-t-il un moyen plus rapide de couvrir toutes les entrées sans utiliser autant de ifs?

Informations supplémentaires: Nous utilisons la série STM32F4 (F429ZI) cadencée à 180 Mhz.


19 commentaires

Que diriez-vous d'afficher environ 5+ if () pour avoir une meilleure idée du code? Un exemple reproductible minimal


Mis à jour pour mieux refléter la structure existante.


Sans une relation / un modèle entre les divers output_x | = some_bit // cette logique pourrait changer lignes de code, il suffit de parcourir les 40.


"le groupe d'instructions est vraiment lent." Quelle est la définition de vraiment lent: microsecondes, millisecondes, secondes? Quel type de processeur utilisez-vous? Quelle est la vitesse d'horloge? Quelle est la durée d'accès aux E / S mappées en mémoire?


Avez-vous réellement vérifié que le problème provient des instructions if . Ou la majeure partie du temps est-elle consacrée aux fonctions Read_Inputs et Write_Outputs ?


Nous avons vérifié que le problème provenait de la section logique. Il faut environ 2,62 microsecondes par instruction if pour s'exécuter.


Sur quel processeur, à quelle vitesse d'horloge?


Ce qui n'est pas clair ici, c'est uint32_t INPUT_ARRAY [40]; au lieu d'un seul uint64_t INPUT_ARRAY; utilisant 1 bit par entrée. Pourquoi utiliser uin32_t pour un test booléen? Pour être clair, avec beaucoup plus d'informations, ce sera beaucoup de questions de va-et-vient. Publier un exemple minimal reproductible


La série STM32F4 (F429ZI) à 180 Mhz


@chux Nous allions utiliser un uint8_t. Le registre de données d'entrée est uint32_t, donc il s'est terminé de cette façon, même s'il n'a pas besoin d'espace supplémentaire. En outre, le MCU est 32 bits.


2.62 usec @ 180 instructions / usec = 470 instructions par instruction if . Le code que vous avez affiché n'explique pas comment vous utilisez 470 instructions par instruction if .


C'est ce qui nous trouble. Cela ne devrait pas être possible. Nous avons commenté toutes les instructions if sauf une et il est ressorti à 614 ns.


Définissez-vous réellement le même bit pour chaque cas? Si oui, pourquoi n'utilisez-vous pas au moins else if ?


Je regarderais le code d'assemblage pour voir comment les instructions if ont été implémentées. Il y a une chance extérieure que le compilateur fasse une multiplication réelle pour calculer l'adresse dans le tableau. Cela ralentirait certainement les choses.


Les macros INPUT_X n'ont aucun avantage en termes de performances par rapport à INPUT_ARRAY [X]


@ user3386109: C'est probablement pire encore que ça. Un Cortex-M atteint 1.25MIPS / MHz, donc 225 instructions / usec.


Il me semble que vous posez la mauvaise question. (Un problème X-Y). Votre question devrait plutôt être pourquoi ce code prend-il 2,62us pour s'exécuter? parce qu'il ne devrait pas - aucune optimisation de votre code ne résoudra un problème de performances aussi important, et le problème ne vient pas de ce code . Êtes-vous certain que la PLL fonctionne à 180 MHz? Savez-vous que vous n'êtes pas fréquemment dans le contexte d'interruption et que vous n'exécutez pas toujours le code en question pendant que vous mesurez les performances. Comment mesurez-vous également la performance? Cela mérite des explications au cas où votre méthode de mesure serait défectueuse.


Je peux sentir un projet de démonstration fonctionnant sur l'oscillateur RC interne. La configuration de l'horloge est où? Btw le matériel GPIO pourrait ne pas être en mesure de basculer plus rapidement que quelque part autour de 30 - 100ns, vérifiez les caractéristiques analogiques.


Nous avons donc découvert plusieurs choses. La première chose que nous avons trouvée était que la broche que nous surveillions pour tester la vitesse de notre code était sur un bus à vitesse inférieure (la moitié de la vitesse du processeur) et donnait de mauvais résultats. La deuxième chose que nous avons constatée était que nos boucles temporelles temporaires étaient compensées par les déclarations 40 if. Assez pour ralentir nos boucles d'entrée, de sortie et de bascule LED. Merci à tous pour l'aide. J'ai appris quelques choses.


3 Réponses :


2
votes

Utilisez une boucle. Vous pouvez utiliser for loop, while loop ou do while loop selon vos besoins. Une boucle for est plus courante.

  for(i=0;i<40;i++) \\40_is_your_array_length_by_your_code
   {
     if(INPUT_ARRAY[i] != 0){
       output_x |= some_bit
      }
   }

Après avoir ajouté d'autres éléments de la structure de code actuelle: -

int i; 
for(i=1;i<=Number_Of_times_following_code_need_to_run;i++)
{
   \\Your_desired_code_here
}


5 commentaires

A part: Loop est une bonne idée. Plus idiomatique pour commencer à 0 et limiter avec i


Dans mon code, je commence toujours ma boucle à 0, jamais utilisé 1 au démarrage. Mais je pensais que commencer par 1 serait plus significatif pour @ConductedForce


Malheureusement, une boucle de cette façon ne fonctionnerait pas bien. La boucle logique actuelle consiste simplement à tester le matériel avec lequel nous travaillons. J'aurais probablement dû le mentionner.


@JonathanLeffler: Peut-être parce qu'il n'y a pas de relation arithmétique entre i et some_bit ou même output_x où j'imagine que x est un espace réservé tel qu'il est dans < code> MACRO_X . De plus pour i = 0 les opérations diffèrent. L'explication de ConducedForce sur les raisons pour lesquelles la boucle ne fonctionne pas n'est cependant pas claire. Trop de code est élidé de «esquissé» dans la question originale.


En général, l'utilisation d'une boucle ajoutera simplement une surcharge. Cela ralentirait donc le code, pas l'accélérer. Une boucle peut aider si le cache de code est surchargé. Mais la conversion du code en boucle suppose que l'instruction output_x | = some_bit peut en fait être paramétrée pour être utilisée dans une boucle.



0
votes

Utilisez un masque 64 bits au lieu de uint32_t INPUT_ARRAY [40]; .

Le code utilise certainement un tableau indexé pour faciliter la définition / la suppression. Utilisez plutôt un masque de bits pour améliorer les performances du test , qui se produit certainement beaucoup plus souvent que set / clear.


0 commentaires

3
votes

Si les branches ont un impact sur les performances, il peut être possible de supprimer les branches. Par exemple, si vous définissez un seul bit, au lieu de dire:

output_x |= (((INPUT_1 | INPUT_2 | INPUT_3 | ... ) != 0) << 15);

, vous pouvez faire sans condition:

output_x |= ((INPUT_n != 0) << 15);

Et si vous définissez le même bit pour chaque entrée, il pourrait être réduit en:

if (INPUT_n != 0) {
   output_x |= (1 << 15);
}


2 commentaires

Tout d'abord output_x | = ((INPUT_n! = 0) << 15); ne change pas le bit output_x. il ne peut que le définir mais pas le mettre à zéro. Deuxièmement, le! = Est également conditionnel


@P__J__ Tout d'abord, l ' effacement des bits n'a jamais fait partie du code d'origine. Si vous souhaitez effacer un peu, alors & = ~ some_bit pourrait être utilisé à la place. Deuxièmement, il ne s’agit pas de supprimer les conditions , mais de supprimer les branches . De plus, si INPUT_n est déjà un booléen correct, alors ! = peut disparaître.