IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

"Cercle" de fonctions récursives


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre très actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2014
    Messages
    183
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2014
    Messages : 183
    Par défaut "Cercle" de fonctions récursives
    Bonjour,
    dans un projet je me retrouve dans un cas où plusieurs fonctions pourraient s’appeler mutuellement et former un cercle :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    void fonction1(params){
      ...
      fonction2();
    }
    void fonction2(){
      ...
      userCallback(); //appelle userFunction()
    }
    void userFunction(){
      ...
      fonction1();
    }
    Evidemment le problème avec ce genre de code, c'est qu'il génère une sorte d'arbre :
    fonction1{
    fonction2{
    fonction3{
    fonction1{
    fonction2{
    ...
    et puis au bout d'un moment stack overflow alors j'ai tout de suite oublié l'idée.
    J'avais commencé à gérer ça avec des threads mais comme ce serais beaucoup plus simple et plus efficace de le faire avec la première méthode, je suis quand même allé chercher au cas où... je suis tombé sur une certaine "tail optimization", il semblerait que certains compilateurs transforment le "call" en "jump" quand l'appel est la dernière instruction de la fonction, ce qui corrige le problème.
    Oui je sais c'est pas ce qu'il y a de plus propre comme code mais c'est ce qu'il y a de plus rapide et contourner cette méthode rendrais probablement le code 10x plus complexe.

    Mais la question est (ou plutôt les questions) : Est-ce que ça marche dans mon cas ?
    dans tous les exemple que j'ai trouvé, les fonctions n'étaient pas des "void", la dernière ligne était donc toujours quelque chose comme "return fonction(p);" dans mon cas les fonctions sont de type void, est-ce que appeler la fonction suffit ou je dois écrire quelque chose comme "return fonction();"
    J'utilise visual studio, est-ce que le compilateur réalise l'optimisation ? si oui et-ce qu'il faut paramétrer quelque chose avant j'avais vu qu'en général l'optimisation de ne fait qu'en release, si c'est le cas peut-on la forcer ?

    Merci de votre aide.

  2. #2
    Membre Expert
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Par défaut
    Toute récursion a besoin d'une condition d'arrêt. Soit ton algorithme est incorrect, soit c'est son implémentation qui l'est. Identifie cette condition et détermine pourquoi elle n'est jamais vérifiée à l'exécution.

  3. #3
    Membre très actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2014
    Messages
    183
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2014
    Messages : 183
    Par défaut
    Il y a une condition d'arrêt, en quelques sortes, quand l'utilisateur le décide en fait ce qui pourrait se faire au bout de 1 million de tour de boucle...
    Mais c'est possible dans le cas ou le compilateur réalise cette "tail optimization", le code compilé seras a peu près équivalent a un goto plutôt qu'un appel de fonction donc pas de problème de stack overflow

  4. #4
    Membre Expert
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Par défaut
    Je comprends ce que tu veux faire : tu te dis que tu peux peut-être t'éviter une refactorisation en profondeur en jouant sur cette rustine.

    Pour bénéficier de la TCO il faut :
    • que le code de chacune des x fonctions soit éligible ;
    • que l'implémentation prenne en charge cette optimisation (ce n'est pas garanti par la norme en C++, on perd donc déjà en portabilité).

    Je ne connais ni ton cas d'utilisation, ni ton code (peut-être peux-tu nous en dire plus ? Et il faudra de toute manière y jeter un œil si tu veux avoir un avis technique) mais quand je lis ça :

    Citation Envoyé par RedSkidy Voir le message
    Il y a une condition d'arrêt, en quelques sortes, quand l'utilisateur le décide en fait ce qui pourrait se faire au bout de 1 million de tour de boucle...
    ..je tique, cela ressemble à un choix désespéré pour répondre à un défaut de conception. Peut-être faut-il remettre à plat une partie de cette dernière ?

  5. #5
    Membre très actif
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2014
    Messages
    183
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2014
    Messages : 183
    Par défaut
    Bon j'avoue ne pas avoir tout compris je suis assez nouveau dans le milieu de la programmation j'ai que 2 ans d’expérience si on peut appeler ça de l’expérience étant donné que j'ai appris tout seul sur internet donc j'ai encore un peu du mal avec le vocabulaire technique

    En fait je suis entrain de faire (ou en tout cas d'essayer) une bibliothèque qui gère des réseaux de neurones (un équivalent de tflearn mais en c++) qui fait les calculs sur GPU en utilisant OpenCL.
    J'essaye de la concevoir pour qu'elle soit modulaire, facile d'utilisation et rapide.
    J'ai une classe NetManager qui contiens différents objets nécessaires a OpenCL (context, platform, command_queue...) et un tableau de réseaux de neurones.
    Ces réseaux de neurone contiennent eux même un tableau de "AbstractLayer" qui représente les couches du NN.
    Pour qu'OpenCL fasse un calcul, il faut lui envoyer une commande qu'il exécuteras dès qu'il pourra. Quand l'execution est terminé il appelle une fonction callback.
    Les commandes envoyés ne sont pas forcément exécutées dans l'ordre mais les couches elles doivent être "utilisées" dans l'ordre.
    alors j'ai une fonction appelons la "coordinate()" (pas trouvé de nom encore) qui va :
    appeler la fonction "start()" de la couche 1, cette couche 1 va envoyer une commande a OpenCL et lui donner "coordinate" come callback
    coordinate seras donc re-appelée a la fin du calcul et va ensuite appeler la fonction start() de la couche 2 etc...
    a la fin, elle appelleras une autre fonction callback définie par l'utilisateur cette fois, pour lui dire que son réseau de neurone a terminé.
    Et l'utilisateur va probablement lui donner de nouvelles instructions, ce qui re-appelleras la fonction coordinate...

    Peut être qu'une rupture se fait au niveau de OpenCL qui doit surement appeler la fonction callback depuis un autre thread mais je ne sais pas si c'est une bonne idée que presque tout le code soit exécute par un thread sur lequel je n'ai aucun contrôle et je ne sais pas si OpenCL continue de fonctionner pendant l'appel de la fonction callback

    Ma solution alternative était d'avoir un thread qui vérifie constamment l'état de tous les réseaux de neurones et leur redonne une tache si nécessaire, une sorte de fonction coordinate générale (pour tous les réseaux) exécutée dans un thread. La boucle resteras bloquée tant qu'une fonction "update()" n'auras pas été appelée. cette fonction est utilisée comme callback pour OpenCL. Le blocage sert a éviter d'avoir une boucle infinie qui utilise 100% du processeur

  6. #6
    Membre Expert
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Par défaut
    Je peux avoir compris de travers mais de ce que tu présentes, les tâches doivent être exécutées de manière totalement séquentielle. Pourtant tu as actuellement une call stack de la forme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    start(0)
      callback(0)
        start(1)
          callback(1)
            start(2)
              callback(2)
    ...
                start(n)
                  callback(n)
    Ma question est : pourquoi ? Pourquoi empiler tous ces contextes alors que la tâche n+1 ne peut démarrer que lorsque la tâche n est terminée ?

    Le callback peut très bien se borner à mettre le résultat dans une structure commune et lever un évènement pour signaler la fin de la tâche correspondante pour obtenir quelque chose du style :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    run()
      start(0)
        callback(0)
      start(1)
        callback(1)
      start(2)
        callback(2)
    ...
      start(n)
        callback(n)
    Tu n'es même pas forcé d'utiliser plusieurs threads (hors ceux sur lesquels les callbacks sont invoqués, s'il ne s'agit pas du main thread).

+ Répondre à la discussion
Cette discussion est résolue.

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo