Précédent   Forum du club des développeurs et IT Pro > Autres langages > Python & Zope > Général Python
Général Python Forum d'entraide sur les fondamentaux du langage Python, syntaxe, POO, bibliothèque standard, ...
Partagez cette discussion sur d'autres réseaux sociaux : Viadeo Twitter Google Facebook Digg Delicious MySpace Yahoo
Réponse
 
Outils de la discussion
Publicité
'
Vieux 17/01/2013, 21h13   #1
memento80
Nouveau Membre du Club
 
Avatar de memento80
 
Homme
Mon boulot ?? En ce moment, j'me l'demande...
Inscription : novembre 2004
Messages : 90
Détails du profil
Informations personnelles :
Sexe : Homme
Âge : 33
Localisation : France, Yvelines (Île de France)

Informations professionnelles :
Activité : Mon boulot ?? En ce moment, j'me l'demande...
Secteur : Industrie

Informations forums :
Inscription : novembre 2004
Messages : 90
Points : 33
Points : 33
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) :

Citation:
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 :
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")
memento80 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 18/01/2013, 07h53   #2
Sekigo
Membre du Club
 
Serge
Inscription : février 2012
Messages : 28
Détails du profil
Informations personnelles :
Nom : Serge
Localisation : France

Informations forums :
Inscription : février 2012
Messages : 28
Points : 41
Points : 41
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 :
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 :
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.
Sekigo est déconnecté   Envoyer un message privé Réponse avec citation 10
Vieux 18/01/2013, 22h02   #3
memento80
Nouveau Membre du Club
 
Avatar de memento80
 
Homme
Mon boulot ?? En ce moment, j'me l'demande...
Inscription : novembre 2004
Messages : 90
Détails du profil
Informations personnelles :
Sexe : Homme
Âge : 33
Localisation : France, Yvelines (Île de France)

Informations professionnelles :
Activité : Mon boulot ?? En ce moment, j'me l'demande...
Secteur : Industrie

Informations forums :
Inscription : novembre 2004
Messages : 90
Points : 33
Points : 33
Tout d'abord merci pour ta contribution.
Ces tests complémentaires sont fort intéressants.

Citation:
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 :
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 :
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...
Citation:
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.

Citation:
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 :
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")
memento80 est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 19/01/2013, 10h25   #4
Sve@r
Expert Confirmé Sénior
 
Avatar de Sve@r
 
Homme Frédéric
Ingénieur développement logiciels
Inscription : février 2006
Messages : 3 533
Détails du profil
Informations personnelles :
Nom : Homme Frédéric
Âge : 45
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 : 3 533
Points : 7 066
Points : 7 066
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 :
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 :
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")
__________________
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Tout ce qu'un individu reçoit sans rien faire pour l'obtenir, un autre individu a dû travailler pour le produire sans en tirer profit.
Tout Pouvoir ne peut distribuer aux uns que ce qu'il a préalablement confisqué à d'autres car on n'accroît pas les biens en les divisant.
Quand la moitié d'un peuple croit qu'il ne sert à rien de faire des efforts car l'autre moitié les fera pour elle, et quand cette dernière moitié se dit qu'il ne sert à rien d'en faire car ils bénéficieront à d'autres, cela s'appelle le déclin et la fin d'une nation.
Dr. Adrian Rogers, 1931
Sve@r est déconnecté   Envoyer un message privé Réponse avec citation 10
Vieux 21/01/2013, 19h04   #5
memento80
Nouveau Membre du Club
 
Avatar de memento80
 
Homme
Mon boulot ?? En ce moment, j'me l'demande...
Inscription : novembre 2004
Messages : 90
Détails du profil
Informations personnelles :
Sexe : Homme
Âge : 33
Localisation : France, Yvelines (Île de France)

Informations professionnelles :
Activité : Mon boulot ?? En ce moment, j'me l'demande...
Secteur : Industrie

Informations forums :
Inscription : novembre 2004
Messages : 90
Points : 33
Points : 33
Merci à vous deux pour vos lumières.
memento80 est déconnecté   Envoyer un message privé Réponse avec citation 00
Réponse Cette discussion est résolue.
Outils de la discussion

Navigation rapide


Fuseau horaire GMT +2. Il est actuellement 08h37.


 
 
 
 
Partenaires

Hébergement Web