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

Python Discussion :

multiprocessing et QProcess


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Par défaut multiprocessing et QProcess
    Bonsoir,

    bon je vais être honnête, j'ai pas tout piger à ce que je vais vous demander :p

    je veux permettre le multiprocess sur une liste de travaille très longue...

    j'ai plusieurs milliers d'images que je transforme en texte automatiquement via la commande :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    for File in self.SubImgFiles:
     process.start('tesseract -l {0} "{1}" "{1}"'.format(self.Lang, File))
    je voudrais donc accelerer le traitement en séparant le travail en plusieurs parties pour le partager.

    je me dis qu'il faudrait créer X listes de fichiers et les donner aux différents processeurs.

    j'ai vu multiprocessing qui semble le permettre mais les exemples que j'ai trouvé ne sont pas forcement très parlant.

    Avez vous une idée de la façon de s'y prendre ?


    J'ai posté ici car je ne pense pas que le QProcess soit déterminant dans la façon de s'y prendre.


    Merci et bonne soirée à vous.

  2. #2
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 715
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 715
    Par défaut
    Salut,

    Je ne vois pas trop l'intérêt de multiprocessing: il n'y a pas de code/données Python à partager juste la commande/programme externe à lancer avec différents paramètres.
    Dans tous les cas, on va avoir à lancer N process en parallèle (avec un N qui dépend du nombre de CPU disponibles) et leur soumettre le fichier suivant à traiter.
    Peut être regarder du côté de concurrent.futures qui fait déjà une partie du boulot au dessus des Threads ou de subprocess (au choix).

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Membre chevronné
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Par défaut
    Dans tous les cas, on va avoir à lancer N process en parallèle (avec un N qui dépend du nombre de CPU disponibles) et leur soumettre le fichier suivant à traiter.
    C'est tout à fait l'idée que j'en avais

    Je vais regarder ce que tu me conseilles, j'espere juste que ça lancera pas toutes les commandes sur le meme processeur.

    je reviendrai vers vous pour vous dire ce qu'il en est.

    Merci à toi.

    EDIT : C'est parfait !
    je suis passé de 770s à 215s !
    Franchement pratique !

    voici ma version
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    from concurrent.futures import ThreadPoolExecutor # Permet de le multi calcul
    from psutil import cpu_count # Indique le nombre de cpu
     
     
        def TesseractConvert(self, File):
            ### Création du QProcess avec unification des 2 sorties (normale + erreur)
            process = QProcess()
     
            ### Reconnaissance de l'image suivante
            process.start('tesseract -l {0} "{1}" "{1}"'.format(self.Lang, File))
            process.waitForFinished()
     
            # Décompte du nombre de fichier traité avec création du pourcentage
            self.FilesDone += 1
            self.Pourcentage = int((self.FilesDone * 100) / self.TotalSubtitles)
            print(str(self.Pourcentage))
     
     
    with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
        for File in self.SubImgFiles:
             executor.submit(self.TesseractConvert, File)

  4. #4
    Membre chevronné
    Homme Profil pro
    Inscrit en
    Novembre 2013
    Messages
    563
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Novembre 2013
    Messages : 563
    Par défaut
    Bonjour,

    je me permets de relancer vite fait ce topic,

    en effet mes threads modifient tous une QProgressBar, ce que qt n'aime pas

    J'ai testé différents trucs mais qui n'ont pas amélioré le systeme...

    Je pense qu'il faut jouer avec les pyqtSlot et pyqtSignal mais ça me parait un peu lourdingue de programmer des centaines voire des milliers de signaux (1 pour chaque thread de mon multiproces)...

    Avez vous une idée pour simplifier le truc ?

    Le code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from concurrent.futures import ThreadPoolExecutor # Permet de le multi calcul
     
    def TesseractConvert(File):
        Reply = LittleProcess('tesseract -l {0} --tessdata-dir "{2}" "{1}" "{1}"'.format(GlobalVar["TesseractLanguage"], File, GlobalVar["TesseractFolder"]))
     
        GlobalVar["FilesDone"] += 1
     
        GlobalVar["ProgressBar"].setValue(GlobalVar["FilesDone"])
     
        ### Si c'était le dernier fichier, on cache la fenetre
        if GlobalVar["FilesDone"] == GlobalVar["TotalSubtitles"]:
            GlobalVar["ProgressDialog"].accept()
     
    with ThreadPoolExecutor(max_workers=GlobalVar["NbCPU"]) as executor:
       for ImageFile in GlobalVar["SubImgFiles"]:
          executor.submit(TesseractConvert, ImageFile)
     
          GlobalVar["ProgressDialog"].exec()

  5. #5
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour hizoka,

    Il n'est pas facile de juger de la validité d'une solution quand on ne connait pas bien le problème à résoudre. Mais imaginons que tu ais plusieurs milliers de tâches indépendantes de durées différentes à traiter par du code Python, voilà la solution qui me plait le plus et que j'ai déjà utilisée:

    - On lance au début du programme et d'un seul coup autant de Process (du module multiprocessing) qu'il y a de CPU, avec 2 arguments: "qin" = Queue d'entrée et "qout" = Queue de sortie. Les 2 queues (du module multiprocessing) seront communes à tous les process ainsi qu'au programme principal et pourront donc être lues et écrites par n'importe lequel d'entre eux (le système de verrous est transparent).

    - le programme principal rentre dans la queue d'entrée "qin" la totalité des travaux à réaliser (p. ex. les fichiers à traiter)

    - Chaque process a un fonctionnement permanent! Au début de chaque boucle while, il attend qu'il y ait un travail en attente dans la queue d'entrée "qin" (travail=qin.get()), le prend, le traite et rentre son résultat dans la queue de sortie "qout" (qout.put(resultat)). Ceci fait, il revient en début de la boucle pour prendre le travail suivant s'il y en a encore.

    - le programme principal compte la longueur de la queue de sortie "qout" et sait que tout est terminé quand le nombre total de travaux réalisés est atteint. Lorsque c'est fait, il peut détruire les process qui sont devenus inutiles. A noter que le suivi par le programme principal de ce nombre de travaux réalisés peut alimenter une barre de progression. Pour ne pas figer la partie graphique, le comptage peut être fait par un timer (il y a d'autres solutions).

    A mon avis, le grand avantage de cette méthode est que ce n'est pas le programme principal qui alimente chaque process: c'est chaque process qui vient chercher du travail quand il a terminé le précédent. Ce qui fait que le process qui est tombé sur un travail de courte durée en prendra plus! Cela veut dire que les ressources des CPU sont complètement exploitées.

    A noter qu'avec ajout de l'instruction "freeze_support()", cette solution est compatible avec cx_freeze.

    Si cette solution t'intéresse, je peux te proposer du code.

  6. #6
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 715
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 715
    Par défaut
    Salut,

    Citation Envoyé par hizoka Voir le message
    en effet mes threads modifient tous une QProgressBar, ce que qt n'aime pas

    J'ai testé différents trucs mais qui n'ont pas amélioré le systeme...

    Je pense qu'il faut jouer avec les pyqtSlot et pyqtSignal mais ça me parait un peu lourdingue de programmer des centaines voire des milliers de signaux (1 pour chaque thread de mon multiproces)...
    D'après ce que j'en lis TesseractConvert(File) est un thread qui va créer un sous process. On va donc lancer autant de threads que de fichiers et créer de s/process... Ce qui effectivement distribue le contrôle sur beaucoup d'agents autonomes.
    Comment simplifier? En virant les agents et en le remplaçant par un "manager" En créant un "manager" qui aura la responsabilité de pousser threads et s/process et de mettre à jour la barre de progression à la fin de chaque s/process.
    Si on reste avec le module concurrent.futures, on peut appeler un callback "à la fin de..." et l'architecture du bazar ressemble à:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    from threading import Thread
    from concurrent.futures import ThreadPoolExecutor as Pool
    import time
     
    def launcher(count, on_update, on_done):
     
        left = count
        step = 100 / count
        def callback(future):
            nonlocal left
            left -= 1
            on_update(step)
            if left == 0:
                on_done()
     
        with Pool(max_workers=2) as pool:
            for z in range(count):
                future = pool.submit(subprocess.call, 'python -c "import time; time.sleep(0.5)"')
                future.add_done_callback(callback)
     
    if __name__ == '__main__':
     
        import tkinter as tk
        from tkinter.constants import *
        from tkinter import ttk
     
        root = tk.Tk()
     
        def updater(root, pb):
            def _do_update(x):
                root.after_idle(lambda: pb.step(x))
            return _do_update
     
        def on_done():
            def reset():
                pb.pack_forget()
                start_btn['state'] = NORMAL
            root.after_idle(reset)
     
        def do_launch():
            start_btn['state'] = DISABLED
            pb.pack()
            update = updater(root, pb) 
            p = Thread(target=launcher, args=(150, update, on_done))
            p.daemon = True
            p.start()
     
        start_btn = tk.Button(root, text='start', command=do_launch)
        start_btn.pack()
        pb = ttk.Progressbar(root, orient=HORIZONTAL, length=200, mode='determinate')
     
        tk.mainloop()
    note: tkinter ou Qt, c'est la même chose côté répartition des tâches et de toutes façons ce qui vous intéresse c'est la partie au dessus de "if __name__ == '__main__':"

    Citation Envoyé par tyrtamos Voir le message
    Il n'est pas facile de juger de la validité d'une solution quand on ne connait pas bien le problème à résoudre. Mais imaginons que tu ais plusieurs milliers de tâches indépendantes de durées différentes à traiter par du code Python, voilà la solution qui me plait le plus et que j'ai déjà utilisée:

    - On lance au début du programme et d'un seul coup autant de Process (du module multiprocessing) qu'il y a de CPU, avec 2 arguments: "qin" = Queue d'entrée et "qout" = Queue de sortie. Les 2 queues (du module multiprocessing) seront communes à tous les process ainsi qu'au programme principal et pourront donc être lues et écrites par n'importe lequel d'entre eux (le système de verrous est transparent).

    - le programme principal rentre dans la queue d'entrée "qin" la totalité des travaux à réaliser (p. ex. les fichiers à traiter)

    - Chaque process a un fonctionnement permanent! Au début de chaque boucle while, il attend qu'il y ait un travail en attente dans la queue d'entrée "qin" (travail=qin.get()), le prend, le traite et rentre son résultat dans la queue de sortie "qout" (qout.put(resultat)). Ceci fait, il revient en début de la boucle pour prendre le travail suivant s'il y en a encore.

    - le programme principal compte la longueur de la queue de sortie "qout" et sait que tout est terminé quand le nombre total de travaux réalisés est atteint. Lorsque c'est fait, il peut détruire les process qui sont devenus inutiles. A noter que le suivi par le programme principal de ce nombre de travaux réalisés peut alimenter une barre de progression. Pour ne pas figer la partie graphique, le comptage peut être fait par un timer (il y a d'autres solutions).

    A mon avis, le grand avantage de cette méthode est que ce n'est pas le programme principal qui alimente chaque process: c'est chaque process qui vient chercher du travail quand il a terminé le précédent. Ce qui fait que le process qui est tombé sur un travail de courte durée en prendra plus! Cela veut dire que les ressources des CPU sont complètement exploitées.

    A noter qu'avec ajout de l'instruction "freeze_support()", cette solution est compatible avec cx_freeze.

    Si cette solution t'intéresse, je peux te proposer du code.
    Très bonne idée, mais hizoka utilise déjà concurrent.futures qui fait déjà tout çà "en standard".
    *edit* J'ai remis ThreadPoolExecutor à la place de Process, car si l'idée est de lancer une commande externe, ce n'est pas utile. Qt dispose d'une bibliothèque semblable (QtConcurrent). Elle ne sait lancer que des Threads (ils n'ont pas de soucis de GIL en C++) mais je ne sais même pas si elle est dans PyQt.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  7. #7
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour wiztricks,

    Citation Envoyé par wiztricks Voir le message
    Très bonne idée, mais hizoka utilise déjà concurrent.futures qui fait déjà tout çà "en standard"
    Merci pour l'info! Je n'ai pas encore suffisamment compris ce que faisait "concurrent.futures" pour l'utiliser. Je vais creuser le sujet.

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

Discussions similaires

  1. MPTL: MultiProcessing Template Library
    Par alskaar dans le forum Langage
    Réponses: 16
    Dernier message: 29/04/2007, 16h44
  2. Réponses: 8
    Dernier message: 21/12/2006, 21h03
  3. Langage pour contrôle multiprocessing
    Par crocodile dans le forum Langages de programmation
    Réponses: 4
    Dernier message: 13/06/2006, 20h02
  4. lock de fichier - multiprocess
    Par hugo123 dans le forum Entrée/Sortie
    Réponses: 5
    Dernier message: 11/04/2006, 10h08
  5. Réponses: 16
    Dernier message: 30/01/2004, 11h05

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