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

Discussion :

Code propre et fonctions courtes.

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    Avatar de sylvain1984
    Homme Profil pro
    Retraité, développeur amateur
    Inscrit en
    Juillet 2023
    Messages
    75
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 57
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : Retraité, développeur amateur

    Informations forums :
    Inscription : Juillet 2023
    Messages : 75
    Par défaut Code propre et fonctions courtes.
    Bonjour,

    Développeur du dimanche, je suis maintenant à la retraite et je creuse certains concepts.
    Aussi je suis en train de lire "Coder proprement" de Robert C. Martin.
    Bouquin évidemment très intéressant mais que j'ai mis en pause le temps d'en ingurgiter un autre (UML 2 pour les développeurs) afin de disposer de quelques outils pour bien le comprendre.

    J'aurais une question concernant un principe avancé par l'auteur : la rédaction de fonctions les plus courtes possibles.
    Suivant ce principe, et considérant les fonctions données en exemple, une fonction se doit d'appeler un petit nombre d'autres fonctions et de réaliser une seule tâche. Parfait.
    Mais du côté de la traduction en langage machine, cela ne risque-t-il pas de générer un code qui fera sans arrêt des sauts mémoires vers d'autres portions de code ? Quel est le coût de ce principe s'il y a une sauvegarde du contexte processeur à chaque saut ?

    Sylvain

  2. #2
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 515
    Par défaut
    Bonjour,

    Citation Envoyé par sylvain1984 Voir le message
    Mais du côté de la traduction en langage machine, cela ne risque-t-il pas de générer un code qui fera sans arrêt des sauts mémoires vers d'autres portions de code ? Quel est le coût de ce principe s'il y a une sauvegarde du contexte processeur à chaque saut ?
    Pour un langage compilé, en mode debug, oui. Par contre, en mode release, si le compilateur décide de faire de l'inlining, alors le code machine généré peut être le même avec ou sans le découpage du code en sous-fonctions. Il n'y aura alors pas plus de sauts.

    Voici un exemple concret en Rust :

    Code Rust : 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
    #[derive(Copy, Clone)]
    pub struct Point {
        x: f64,
        y: f64,
    }
     
    pub struct Triangle {
        a: Point,
        b: Point,
        c: Point,
    }
     
    impl Triangle {
        pub fn perimeter(&self) -> f64 {
            let ab = ((self.b.x - self.a.x).powi(2) + (self.b.y - self.a.y).powi(2)).sqrt();
            let bc = ((self.c.x - self.b.x).powi(2) + (self.c.y - self.b.y).powi(2)).sqrt();
            let ca = ((self.a.x - self.c.x).powi(2) + (self.a.y - self.c.y).powi(2)).sqrt();
            ab + bc + ca
        }
    }

    Dans Rust Playground, copions-collons ce code et sélectionnons "Release" à la place de "Debug" et "ASM" à la place de "Run". En cliquant sur "SHOW ASSEMBLY", cela donne :

    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
    playground::Triangle::perimeter:
    	movupd	(%rdi), %xmm0
    	movupd	32(%rdi), %xmm1
    	movsd	16(%rdi), %xmm2
    	movapd	%xmm0, %xmm3
    	unpcklpd	%xmm2, %xmm3
    	unpcklpd	%xmm1, %xmm2
    	subpd	%xmm3, %xmm2
    	mulpd	%xmm2, %xmm2
    	movsd	24(%rdi), %xmm3
    	movapd	%xmm0, %xmm4
    	subpd	%xmm1, %xmm0
    	movsd	%xmm3, %xmm1
    	shufpd	$1, %xmm3, %xmm4
    	subpd	%xmm4, %xmm1
    	mulpd	%xmm1, %xmm1
    	addpd	%xmm2, %xmm1
    	sqrtpd	%xmm1, %xmm1
    	mulpd	%xmm0, %xmm0
    	movapd	%xmm0, %xmm2
    	unpckhpd	%xmm0, %xmm2
    	addsd	%xmm0, %xmm2
    	sqrtsd	%xmm2, %xmm2
    	movapd	%xmm1, %xmm0
    	unpckhpd	%xmm1, %xmm0
    	addsd	%xmm1, %xmm0
    	addsd	%xmm2, %xmm0
    	retq
    À présent, modifions le code pour faire appel à une sous-fonction distance qui n'est pas publique :

    Code Rust : 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
    #[derive(Copy, Clone)]
    pub struct Point {
        x: f64,
        y: f64,
    }
     
    pub struct Triangle {
        a: Point,
        b: Point,
        c: Point,
    }
     
    impl Triangle {
        pub fn perimeter(&self) -> f64 {
            let ab = distance(self.a, self.b);
            let bc = distance(self.b, self.c);
            let ca = distance(self.c, self.a);
            ab + bc + ca
        }
    }
     
    fn distance(a: Point, b: Point) -> f64 {
        ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt()
    }

    Le code assembleur généré est exactement le même !

    D'ailleurs, si on met les deux versions à la fois :

    Code Rust : 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
    #[derive(Copy, Clone)]
    pub struct Point {
        x: f64,
        y: f64,
    }
     
    pub struct Triangle {
        a: Point,
        b: Point,
        c: Point,
    }
     
    impl Triangle {
        pub fn perimeter_v1(&self) -> f64 {
            let ab = ((self.b.x - self.a.x).powi(2) + (self.b.y - self.a.y).powi(2)).sqrt();
            let bc = ((self.c.x - self.b.x).powi(2) + (self.c.y - self.b.y).powi(2)).sqrt();
            let ca = ((self.a.x - self.c.x).powi(2) + (self.a.y - self.c.y).powi(2)).sqrt();
            ab + bc + ca
        }
        pub fn perimeter_v2(&self) -> f64 {
            let ab = distance(self.a, self.b);
            let bc = distance(self.b, self.c);
            let ca = distance(self.c, self.a);
            ab + bc + ca
        }
    }
     
    fn distance(a: Point, b: Point) -> f64 {
        ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt()
    }

    Le compilateur est assez intelligent pour remarquer que perimeter_v1 et perimeter_v2 font exactement la même chose et génère une seule séquence en assembleur pour les deux à la fois :

    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
    playground::Triangle::perimeter_v1:
    	movupd	(%rdi), %xmm0
    	movupd	32(%rdi), %xmm1
    	movsd	16(%rdi), %xmm2
    	movapd	%xmm0, %xmm3
    	unpcklpd	%xmm2, %xmm3
    	unpcklpd	%xmm1, %xmm2
    	subpd	%xmm3, %xmm2
    	mulpd	%xmm2, %xmm2
    	movsd	24(%rdi), %xmm3
    	movapd	%xmm0, %xmm4
    	subpd	%xmm1, %xmm0
    	movsd	%xmm3, %xmm1
    	shufpd	$1, %xmm3, %xmm4
    	subpd	%xmm4, %xmm1
    	mulpd	%xmm1, %xmm1
    	addpd	%xmm2, %xmm1
    	sqrtpd	%xmm1, %xmm1
    	mulpd	%xmm0, %xmm0
    	movapd	%xmm0, %xmm2
    	unpckhpd	%xmm0, %xmm2
    	addsd	%xmm0, %xmm2
    	sqrtsd	%xmm2, %xmm2
    	movapd	%xmm1, %xmm0
    	unpckhpd	%xmm1, %xmm0
    	addsd	%xmm1, %xmm0
    	addsd	%xmm2, %xmm0
    	retq
    .set playground::Triangle::perimeter_v2, playground::Triangle::perimeter_v1
    Par contre, si on remplace "Release" par "Debug", l'assembleur généré fait apparaître la fonction distance et aussi les méthodes powi et sqrt :

    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
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    std::f64::<impl f64>::powi:
    	subq	$24, %rsp
    	movsd	%xmm0, (%rsp)
    	movl	%edi, 12(%rsp)
    	movq	__powidf2@GOTPCREL(%rip), %rax
    	callq	*%rax
    	movsd	%xmm0, 16(%rsp)
    	movsd	16(%rsp), %xmm0
    	addq	$24, %rsp
    	retq
    
    std::f64::<impl f64>::sqrt:
    	movsd	%xmm0, -16(%rsp)
    	sqrtsd	%xmm0, %xmm0
    	movsd	%xmm0, -8(%rsp)
    	movsd	-8(%rsp), %xmm0
    	retq
    
    playground::Triangle::perimeter_v1:
    	subq	$88, %rsp
    	movq	%rdi, 24(%rsp)
    	movq	%rdi, 56(%rsp)
    	movsd	16(%rdi), %xmm0
    	subsd	(%rdi), %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movq	24(%rsp), %rdi
    	movsd	%xmm0, 8(%rsp)
    	movsd	24(%rdi), %xmm0
    	subsd	8(%rdi), %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movaps	%xmm0, %xmm1
    	movsd	8(%rsp), %xmm0
    	addsd	%xmm1, %xmm0
    	callq	std::f64::<impl f64>::sqrt
    	movq	24(%rsp), %rdi
    	movsd	%xmm0, 48(%rsp)
    	movsd	%xmm0, 64(%rsp)
    	movsd	32(%rdi), %xmm0
    	subsd	16(%rdi), %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movq	24(%rsp), %rdi
    	movsd	%xmm0, 16(%rsp)
    	movsd	40(%rdi), %xmm0
    	subsd	24(%rdi), %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movaps	%xmm0, %xmm1
    	movsd	16(%rsp), %xmm0
    	addsd	%xmm1, %xmm0
    	callq	std::f64::<impl f64>::sqrt
    	movq	24(%rsp), %rdi
    	movsd	%xmm0, 40(%rsp)
    	movsd	%xmm0, 72(%rsp)
    	movsd	(%rdi), %xmm0
    	subsd	32(%rdi), %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movq	24(%rsp), %rdi
    	movsd	%xmm0, 32(%rsp)
    	movsd	8(%rdi), %xmm0
    	subsd	40(%rdi), %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movaps	%xmm0, %xmm1
    	movsd	32(%rsp), %xmm0
    	addsd	%xmm1, %xmm0
    	callq	std::f64::<impl f64>::sqrt
    	movsd	40(%rsp), %xmm2
    	movaps	%xmm0, %xmm1
    	movsd	48(%rsp), %xmm0
    	movsd	%xmm1, 80(%rsp)
    	addsd	%xmm2, %xmm0
    	addsd	%xmm1, %xmm0
    	addq	$88, %rsp
    	retq
    
    playground::Triangle::perimeter_v2:
    	subq	$56, %rsp
    	movq	%rdi, (%rsp)
    	movq	%rdi, 24(%rsp)
    	movsd	(%rdi), %xmm0
    	movsd	8(%rdi), %xmm1
    	movsd	16(%rdi), %xmm2
    	movsd	24(%rdi), %xmm3
    	callq	playground::distance
    	movq	(%rsp), %rdi
    	movsd	%xmm0, 16(%rsp)
    	movsd	%xmm0, 32(%rsp)
    	movsd	16(%rdi), %xmm0
    	movsd	24(%rdi), %xmm1
    	movsd	32(%rdi), %xmm2
    	movsd	40(%rdi), %xmm3
    	callq	playground::distance
    	movq	(%rsp), %rdi
    	movsd	%xmm0, 8(%rsp)
    	movsd	%xmm0, 40(%rsp)
    	movsd	32(%rdi), %xmm0
    	movsd	40(%rdi), %xmm1
    	movsd	(%rdi), %xmm2
    	movsd	8(%rdi), %xmm3
    	callq	playground::distance
    	movsd	8(%rsp), %xmm2
    	movaps	%xmm0, %xmm1
    	movsd	16(%rsp), %xmm0
    	movsd	%xmm1, 48(%rsp)
    	addsd	%xmm2, %xmm0
    	addsd	%xmm1, %xmm0
    	addq	$56, %rsp
    	retq
    
    playground::distance:
    	subq	$72, %rsp
    	movsd	%xmm2, 8(%rsp)
    	movaps	%xmm1, %xmm2
    	movaps	%xmm0, %xmm1
    	movsd	8(%rsp), %xmm0
    	movsd	%xmm2, 16(%rsp)
    	movsd	%xmm3, 24(%rsp)
    	movsd	%xmm1, 40(%rsp)
    	movsd	%xmm2, 48(%rsp)
    	movsd	%xmm0, 56(%rsp)
    	movsd	%xmm3, 64(%rsp)
    	subsd	%xmm1, %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movsd	16(%rsp), %xmm1
    	movaps	%xmm0, %xmm2
    	movsd	24(%rsp), %xmm0
    	movsd	%xmm2, 32(%rsp)
    	subsd	%xmm1, %xmm0
    	movl	$2, %edi
    	callq	std::f64::<impl f64>::powi
    	movaps	%xmm0, %xmm1
    	movsd	32(%rsp), %xmm0
    	addsd	%xmm1, %xmm0
    	callq	std::f64::<impl f64>::sqrt
    	addq	$72, %rsp
    	retq

  3. #3
    Membre éclairé
    Avatar de sylvain1984
    Homme Profil pro
    Retraité, développeur amateur
    Inscrit en
    Juillet 2023
    Messages
    75
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 57
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : Retraité, développeur amateur

    Informations forums :
    Inscription : Juillet 2023
    Messages : 75
    Par défaut
    Bonjour,

    Merci pour ta réponse très documentée.
    Toutefois, il s'agit là de "petites" fonctions de bas niveau. Qu'en sera-t-il à de plus hauts niveaux d'abstraction ? Quand les fonctions appelées vont elles-même appeler d'autres fonctions imbriquées ? Car Martin explique bien également qu'une fonction ne devrait traiter que d'aspects d'un même niveau d'abstraction...

    Sylvain

  4. #4
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 515
    Par défaut
    L'inlining peut se faire à n'importe quel niveau d'abstraction. Quand une fonction A appelle une fonction B qui appelle une fonction C, le compilateur peut décider de faire comme si la fonction A appelait directement la fonction C, y compris quand la fonction C est une fonction de haut niveau.

    À part ça, concernant Robert Cecil Martin, il me semble que "Coder proprement" est la traduction française du livre "Clean Code" qui utilise des exemples en Java. Dans le cas de Java, il faut faire attention que les méthodes à la fois publiques et non statiques peuvent par défaut être redéfinies dans une classe dérivée, sauf si on utilise le mot-clef final. Cela peut alors empêcher l'inlining.

    En effet, si une méthode A appelle une méthode B mais que la méthode B est redéfinie dans une classe dérivée, alors la méthode A n'appellera pas toujours la même méthode B. Alors, l'inlining de la méthode B dans A ne pourra pas avoir lieu.

    Par contre, si la méthode B est privée ou statique ou est déclarée avec le mot-clef final, alors il n'y aura pas ce problème.

  5. #5
    Membre éclairé
    Avatar de sylvain1984
    Homme Profil pro
    Retraité, développeur amateur
    Inscrit en
    Juillet 2023
    Messages
    75
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 57
    Localisation : France, Charente (Poitou Charente)

    Informations professionnelles :
    Activité : Retraité, développeur amateur

    Informations forums :
    Inscription : Juillet 2023
    Messages : 75
    Par défaut
    Bien compris, merci !

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

Discussions similaires

  1. [Tableaux] demande de code pour une fonction.php
    Par carmen256 dans le forum Langage
    Réponses: 4
    Dernier message: 21/01/2006, 17h22
  2. Réponses: 8
    Dernier message: 16/11/2005, 14h11
  3. optimiser le code d'une fonction
    Par yanis97 dans le forum MS SQL Server
    Réponses: 1
    Dernier message: 15/07/2005, 08h41
  4. Réponses: 2
    Dernier message: 05/07/2005, 14h46
  5. Code propre ? :)
    Par Orus dans le forum C++
    Réponses: 24
    Dernier message: 09/12/2004, 18h16

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