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 :

Programmation Objet "lente" ?


Sujet :

Python

  1. #1
    Membre habitué Avatar de memento80
    Homme Profil pro
    Boulot : ne rentre pas dans une case
    Inscrit en
    Novembre 2004
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Boulot : ne rentre pas dans une case
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2004
    Messages : 163
    Points : 125
    Points
    125
    Par défaut Programmation Objet "lente" ?
    Bonjour,

    Je me suis auto formé à Python mais pas encore vraiment à la programmation objet.
    J'ai développé un programme sans vraiment exploiter la programmation objet. Entre autre, j'ai usé et abusé des listes dans ce programme.
    En abordant la programmation objet, je me suis dit que de nombreux attributs dans mon programme pouvaient être regroupé dans ces fameux objets. Par contre, je me demandais si "ça valait le coup" que je fasse ce gros lifting.

    Pour essayer de répondre à ma question, j'ai fait un petit test de performance suivant :
    - 1 - Pour simuler la méthode de mon programme actuel, j'ai fait une boucle qui allouait 100 000 000 de fois (oui, c'est beaucoup) une valeur à un élément de liste.
    - 2 - J'ai fait la même boucle mais cette fois-ci, je suis passé par un objet et je modifie "directement" une des propriété de cet objet (mais parait que c'est philosophiquement pas bien pour la Programmation objet d'après ce que j'ai lu)
    - 3 - Encore la même boucle mais en passant par une méthode pour modifier une des propriété de l'objet (parait que c'est mieux...).

    J'ai obtenu les résultats suivants (les valeurs sont en secondes) :

    1 - Temps pour l'action de modifier une valeur : 28
    2 - Temps pour l'action de modifier un objet (directement) : 33
    3 - Temps pour l'action de modifier un objet (par méthode) : 60
    Appuyez sur une touche pour continuer...
    Donc, c'est surement par ignorance que je pose cette question probablement caricaturale, mais je voulais avoir vos avis d'experts Python à ce sujet : En Python, la Programmation Objet est-elle, dans un certain sens, plus lente que la programmation "non objet" (euh... procédurale ?) ?

    Merci à vous.

    PS : Voici le code utilisé pour mon test
    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    import random
    import os
    import time
     
    tsDeb = round(time.time()) # Timestamp actuel
     
    # Déclaration des variables et objets
    numero = 1
    nbtest = 100000000
    statut = [0]
    HUMAIN = [0]
     
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.id = id
            self.nom = nom
            self.statut = statut
     
    HUMAIN[0] = Humain(1, "TOTO", 100)
     
    while numero<nbtest:
        statut[0] = 99
        numero +=1
     
    tsFin = round(time.time()) # Timestamp actuel
    tpsTs = tsFin - tsDeb
     
    print("1 - Temps pour l'action de modifier une valeur : {0}".format(tpsTs))
     
    #########################################################################
     
    tsDeb = round(time.time()) # Timestamp actuel
     
    # Déclaration des variables et objets
    numero = 1
    nbtest = 100000000
    statut = [0]
    HUMAIN = [0]
     
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.id = id
            self.nom = nom
            self.statut = statut
     
    HUMAIN[0] = Humain(1, "TOTO", 100)
     
    while numero<nbtest:
        HUMAIN[0].statut = 99
        numero +=1
     
    tsFin = round(time.time()) # Timestamp actuel
    tpsTs = tsFin - tsDeb
     
    print("2 - Temps pour l'action de modifier un objet (directement) : {0}".format(tpsTs))
     
    ######################################################################
     
    tsDeb = round(time.time()) # Timestamp actuel
     
    # Déclaration des variables et objets
    numero = 1
    nbtest = 100000000
    statut = [0]
    HUMAIN = [0]
     
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.id = id
            self.nom = nom
            self.statut = statut
     
        def modifStatut(self, newStatut): # Permet de modifier le statut de Humain
            self.statut = newStatut
     
    HUMAIN[0] = Humain(1, "TOTO", 100)
     
    while numero<nbtest:
        HUMAIN[0].modifStatut(99)
        numero +=1
     
    tsFin = round(time.time()) # Timestamp actuel
    tpsTs = tsFin - tsDeb
     
    print("3 - Temps pour l'action de modifier un objet (par méthode) : {0}".format(tpsTs))
     
    os.system("pause")

  2. #2
    Membre régulier
    Profil pro
    Inscrit en
    Février 2012
    Messages
    48
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2012
    Messages : 48
    Points : 105
    Points
    105
    Par défaut
    J'ai modifié ton code pour faciliter les benchs, mais le fond reste le même.
    J'ai aussi modifié le nombre de tour de boucle, parce que je suis sur un netbook qui est très lent.
    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
     
    import time
     
    NBTEST = 10000000
     
    def bench(method):
        def timed(*args, **kw):
            ts = time.time()
            result = method(*args, **kw)
            te = time.time()
            print('%s\t\t%fs' % (method.__name__, te-ts))
            return result
        return timed
     
     
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.id = id
            self.nom = nom
            self.statut = statut
     
        def modifStatut(self, newStatut): # Permet de modifier le statut de Humain
            self.statut = newStatut
     
     
    @bench
    def test1():
        numero = 1
        statut = [0]
        while numero<NBTEST:
            statut[0] = 99
            numero +=1
     
    @bench
    def test1_optim():
        statut = [0]
        stat = statut[0]
        for i in xrange(1, NBTEST):
            stat = 99
     
     
    @bench
    def test2():
        numero = 1
        HUMAIN = [0]
        HUMAIN[0] = Humain(1, "TOTO", 100)
     
        while numero<NBTEST:
            HUMAIN[0].statut = 99
            numero +=1
     
    @bench
    def test2_optim():
        l_humain = [Humain(1, 'TOTO', 100)]
        humain = l_humain[0]
     
        for i in xrange(1, NBTEST):
            humain.statut = 99
     
     
    @bench
    def test3():
        numero = 1
        HUMAIN = [0]
        HUMAIN[0] = Humain(1, "TOTO", 100)
     
        while numero<NBTEST:
            HUMAIN[0].modifStatut(99)
            numero +=1
     
    @bench
    def test3_optim():
        l_humain = [Humain(1, 'TOTO', 100)]
        humain = l_humain[0]
        func = humain.modifStatut
     
        for i in xrange(1, NBTEST):
            func(99)
     
     
    if __name__ == '__main__':
        test1()
        test1_optim()
        test2()
        test2_optim()
        test3()
        test3_optim()
    Le résultat :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    test1		        5.710586s
    test1_optim		1.578327s
    test2		        6.496632s
    test2_optim		3.816492s
    test3		        16.409655s
    test3_optim		8.612386s
    Alors, les fonctions _optim n'ont plus vraiment le même comportement. Mais le résultat est le même. Il faut éviter au maximum les appels durant une longue boucle.
    Prendre un index dans une liste, c'est assez long. Idem pour reconstruire la chaîne à chaque tour de boucle (c'est assez visible dans la partie 1). Mieux vaut aller prendre la référence de l'index de la liste avant d'entamer la boucle.
    Un des goulets d'étranglement dans ton code est aussi l'utilisation de while. Mieux vaut utiliser for (pour les générateurs), et tu y gagnes en plus en lisibilité.

    La partie 3 restera toujours plus lent que les autres, car les appels de fonctions sont assez gourmand. Une solution peut-être de faire un lien vers cette fonction, ce qui évite de faire appel à "l'accesseur" (me semble que c'est le nom) de la méthode au sein de l'instance.

    Maintenant, ça reste du benchmark pour du benchmark, et je pense que ton code ne doit pas être suffisamment bien construit de base pour éviter au maximum les appels qui réduisent les performances. En tout cas, dans ce code, je ne vois pas l’intérêt de parcourir une liste.

    Edit: J'avais oublié l'essentiel. Le fait d'appeler directement la variable d'instance n'est pas "sale philosophiquement parlant". Bien au contraire. Dans d'autres langages, faut que tu passes par ces procédures, que je juge lourdingue dans des cas simple d'assignation comme présenté ici. En python, tu peux t'en passer, et le code en sera d'autant plus lisible (modulo le fait que tu nommes tes variables correctement pour qu'on puisse comprendre à leurs intitulés leurs fonctions). Souvent, on passe par ces accesseurs pour éviter de trifouiller aux variables privés d'instance et foutre le dawa. En python, on est entre adulte responsable, et on précède le nom de sa variable par un underscore pour dire "c'est privé, tu n'y touches pas de l'extérieur" sans avoir besoin de mettre une mécanique chiante à écrire.
    Et puis, si en Python, on ne pouvait pas faire appel directement aux variables d'instances, de classes, etc... on pourrait mettre à la poubelle les indispensables property, ce qui serait franchement moche.

    Et "philosophiquement" parlant, appeler myinstance.myvar ou myinstance.set_myvar, c'est exactement la même chose en python : appeler des références vers des objets. Par conséquent, interdire d'appeler des variables reviendraient à interdire de faire appel à des fonctions, puisque c'est la même chose. Et tu en conviendras, ce serait un peu difficile pour construire un programme de cette manière.

  3. #3
    Membre habitué Avatar de memento80
    Homme Profil pro
    Boulot : ne rentre pas dans une case
    Inscrit en
    Novembre 2004
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Boulot : ne rentre pas dans une case
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2004
    Messages : 163
    Points : 125
    Points
    125
    Par défaut
    Tout d'abord merci pour ta contribution.
    Ces tests complémentaires sont fort intéressants.

    Un des goulets d'étranglement dans ton code est aussi l'utilisation de while. Mieux vaut utiliser for (pour les générateurs), et tu y gagnes en plus en lisibilité.
    Effectivement. Il y a des trucs tout bête auquel on ne pense pas tout de suite mais ne serait-ce que de remplacer tous mes while par des for, tel que tu le préconises, et hop !... on divise tout par 2 ou presque.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    1 - Temps pour l'action de modifier une valeur : 11
    2 - Temps pour l'action de modifier un objet (directement) : 17
    3 - Temps pour l'action de modifier un objet (par méthode) : 42
    Appuyez sur une touche pour continuer...
    Par contre, que je modifie une valeur d'une variable ou un élément de liste dans la boucle me donne quasiment les mêmes temps que ci-dessus.

    Là où je gagne sur la partie 3, c'est effectivement en ajoutant un "lien" vers la fonction..
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    1 - Temps pour l'action de modifier une valeur : 12
    2 - Temps pour l'action de modifier un objet (directement) : 15
    3 - Temps pour l'action de modifier un objet (par méthode) : 28
    Appuyez sur une touche pour continuer...
    Maintenant, ça reste du benchmark pour du benchmark, et je pense que ton code ne doit pas être suffisamment bien construit de base pour éviter au maximum les appels qui réduisent les performances. En tout cas, dans ce code, je ne vois pas l’intérêt de parcourir une liste.
    Oui bien sur, présenté tel quel ça n'a pas grand intérêt, du moins fonctionnellement.
    En fait, je me posais cette question car dans le programme un peu plus étoffé que j'ai développé jusqu'à présent, en conservant cet exemple, je passe d'un objet "humain" à l'autre pour en modifier éventuellement les attributs.. mais pour ne pas modifier directement ceux-ci, je récupérais chaque attributs en début de programme, je les insérais dans des listes et je travaillais directement sur ces listes.

    Je ne sais pas si c'est très clair mais en gros, par exemple, j'avais tous les objets "humains" en début de programme avec des attributs "age", "statut", etc..
    En début de programme, je récupérais chaque attribut que je mettais dans une liste adéquat (listAge, listStatut) et après je travaillais sur la liste via l'indice commun de chacun (listAge[0], listStatut[0] --> Attribut de mon premier "Humain", listAge[1], listStatut[1] --> Attribut de mon deuxième"Humain", etc...).
    Donc quand je modifiais un attribut, je modifiais en fait la liste et pas l'objet.

    Edit: J'avais oublié l'essentiel. Le fait d'appeler directement la variable d'instance n'est pas "sale philosophiquement parlant". Bien au contraire. Dans d'autres langages, faut que tu passes par ces procédures, que je juge lourdingue dans des cas simple d'assignation comme présenté ici. En python, tu peux t'en passer, et le code en sera d'autant plus lisible (modulo le fait que tu nommes tes variables correctement pour qu'on puisse comprendre à leurs intitulés leurs fonctions). Souvent, on passe par ces accesseurs pour éviter de trifouiller aux variables privés d'instance et foutre le dawa. En python, on est entre adulte responsable, et on précède le nom de sa variable par un underscore pour dire "c'est privé, tu n'y touches pas de l'extérieur" sans avoir besoin de mettre une mécanique chiante à écrire.
    Donc pour ce que je comprends, si je fais "HUMAIN[0].statut = 99" dans mon programme pour modifier la valeur d'un attribut d'un objet, ce n'est pas forcément "mauvais" ? ... du moins, pour Python car ce n'est pas à envisager pour un autre langage de programmation.
    Et donc finalement, par rapport à mon exemple ci-dessus, je pourrais modifier directement les attributs de mes "Humains" sans m'embêter à mettre tout ça dans des listes ? Hormis que je perdrais très légèrement en performance d'après les tests que je viens de faire..

    NB : Mon code "crado" modifié avec ces optimisations.
    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    import random
    import os
    import time
     
    tsDeb = round(time.time()) # Timestamp actuel
     
    # Déclaration des variables et objets
    numero = 1
    nbtest = 100000000
    statut = [0]
    HUMAIN = [0]
     
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.id = id
            self.nom = nom
            self.statut = statut
     
    HUMAIN[0] = Humain(1, "TOTO", 100)
    statutTraite = statut[0]
     
    for i in range(numero, nbtest):
        statutTraite = 99
     
    tsFin = round(time.time()) # Timestamp actuel
    tpsTs = tsFin - tsDeb
     
    print("1 - Temps pour l'action de modifier une valeur : {0}".format(tpsTs))
     
    #########################################################################
     
    tsDeb = round(time.time()) # Timestamp actuel
     
    # Déclaration des variables et objets
    numero = 1
    nbtest = 100000000
    statut = [0]
    HUMAIN = [0]
     
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.id = id
            self.nom = nom
            self.statut = statut
     
    HUMAIN[0] = Humain(1, "TOTO", 100)
     
    humainTraite = HUMAIN[0]
    for i in range(numero, nbtest):
        humainTraite.statut = 99
     
    tsFin = round(time.time()) # Timestamp actuel
    tpsTs = tsFin - tsDeb
     
    print("2 - Temps pour l'action de modifier un objet (directement) : {0}".format(tpsTs))
     
    ######################################################################
     
    tsDeb = round(time.time()) # Timestamp actuel
     
    # Déclaration des variables et objets
    numero = 1
    nbtest = 100000000
    statut = [0]
    HUMAIN = [0]
     
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.id = id
            self.nom = nom
            self.statut = statut
     
        def modifStatut(self, newStatut): # Permet de modifier le statut de Humain
            self.statut = newStatut
     
    HUMAIN[0] = Humain(1, "TOTO", 100)
    funcModif = HUMAIN[0].modifStatut
     
    for i in range(numero, nbtest):
        funcModif(99)
     
    tsFin = round(time.time()) # Timestamp actuel
    tpsTs = tsFin - tsDeb
     
    print("3 - Temps pour l'action de modifier un objet (par méthode) : {0}".format(tpsTs))
     
    os.system("pause")

  4. #4
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 684
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 684
    Points : 30 973
    Points
    30 973
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par memento80 Voir le message
    Donc pour ce que je comprends, si je fais "HUMAIN[0].statut = 99" dans mon programme pour modifier la valeur d'un attribut d'un objet, ce n'est pas forcément "mauvais" ? ... du moins, pour Python car ce n'est pas à envisager pour un autre langage de programmation.
    Salut
    Tu peux tout à fait modifier tes attributs de façon directe même dans d'autres langages comme C++ à condition que ces attributs soient publics.
    Le danger de cette façon de faire arrive quand on travaille sur des gros projets à plusieurs et que tu n'es alors pas maitre du code de tes collègues qui peuvent eux aussi modifier tes attributs directement.
    Le cas arrive aussi quand la modif se fait à partir d'une valeur saisie par un utilisateur extérieur.
    Alors que si tu crées un point d'entrée unique pour modifier tes attributs, tu peux alors contrôler la façon dont ils sont modifiés.
    Accessoirement, tu peux rendre des attributs privés en Python en les faisant précéder de __
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.__id = id
            self.__nom = nom
            self.__statut = statut
    Ainsi plus personne ne peut avoir accès aux attributs id, nom et statut depuis l'extérieur. Te faut alors créer des getters et des setters pour récupérer et modifier ces valeurs
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Humain: # Definition de la classe Humain
        def __init__(self, id, nom, statut): # Constructeur de Humain
            self.__id = id
            self.__nom = nom
            self.__statut = statut
        def get(self, token): # Récupérateur d'attributs
           if token="id": return self.__id
           if token="nom": return self.__nom
           if token="statut": return self.__statut
           raise IOError
     
    a=Humain(1, "toto", "vivant")
    print a.get("nom"), a.get("statut")
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  5. #5
    Membre habitué Avatar de memento80
    Homme Profil pro
    Boulot : ne rentre pas dans une case
    Inscrit en
    Novembre 2004
    Messages
    163
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Boulot : ne rentre pas dans une case
    Secteur : Industrie

    Informations forums :
    Inscription : Novembre 2004
    Messages : 163
    Points : 125
    Points
    125
    Par défaut
    Merci à vous deux pour vos lumières.

+ 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