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

Java Discussion :

[Thread]Version multitache d'un programme moins rapide que la version monotache ?!


Sujet :

Java

  1. #1
    Membre averti Avatar de Chatbour
    Profil pro
    Étudiant
    Inscrit en
    Septembre 2006
    Messages
    431
    Détails du profil
    Informations personnelles :
    Localisation : Tunisie

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Septembre 2006
    Messages : 431
    Points : 305
    Points
    305
    Par défaut [Thread]Version multitache d'un programme moins rapide que la version monotache ?!
    Bonjour à tous et à toutes

    j'ai codé un programme qui fragmente un fichier en un nombre donné (par l'utilisateur) de fragments..

    l'algorithme utilisé pour fragmenter le fichier est très simpliste : lecture du fichier source octet par octet et écriture dans un fragment après un autre..

    voici le code de la classe Fragmenteur :
    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
    package fragdefrag;
     
    /*
     *  Classe qui fragmente un fichier donné en un nombre spécifié de fragments
     */
     
    import java.io.*;
     
    public class Fragmenteur {
        /* Constructeur */
        public Fragmenteur(File fichier, int nbrFragments) throws FileNotFoundException, IOException {
            /* lecture binaire bufférisée depuis le fichier */
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fichier));
     
            /* calculer la taille de chaque fragment */
            long tailleFragment = fichier.length() / nbrFragments;
     
            int a;
            int compteurFragments = 1;
     
            /* on crée d'abord les nbrFragments - 1 premiers fragments */
            while(compteurFragments <= nbrFragments - 1) {
                /* écriture binaire bufférisée dans chaque fragment */
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fichier.getAbsolutePath() + "_part_" + compteurFragments));
                for(int i=0; i<tailleFragment; i++) {
                    if((a = bis.read()) != -1) {
                        bos.write(a);
                    }
                }
                bos.close();
     
                compteurFragments++;
            }
     
            /* compléter le dernier fragement */
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fichier.getAbsolutePath() + "_part_"+ compteurFragments));
            while((a = bis.read()) != -1) {
                bos.write(a);
            }
            bos.close();
            bis.close();
     
            genererLog(fichier, nbrFragments);
        }
     
        /* méthode privée qui génére un fichier journal et y stocke le nombre de fragments */
        private void genererLog(File fichier, int nbrFragments) throws FileNotFoundException, IOException {
            BufferedWriter bw = new BufferedWriter(new FileWriter(fichier.getAbsolutePath() + ".log"));
            bw.write(nbrFragments + "");
            bw.close();
        }
    }
    comme vous voyez, c'est très évident..

    Essayant d'améliorer le programme, j'ai pensé que si chaque fragment était "rempli" par un thread, ça deviendrait peut être plus rapide..
    voici le code de la classe Fragmenteur modifiée :
    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
    package frag;
     
    /*
     *  Classe qui fragmente un fichier donné en un nombre spécifié de fragments
     */
     
    import java.io.*;
    import java.util.concurrent.CountDownLatch;
     
    public class Fragmenteur {
        /* Constructeur */
        public Fragmenteur(File file, int nbFragments) throws FileNotFoundException, IOException, InterruptedException {
     
            /* lecture binaire bufférisée depuis le fichier */
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            /* calculer la taille de chaque fragment */
            long length = file.length() / nbFragments;
            int position;
            CountDownLatch countdown = new CountDownLatch(nbFragments - 1);
     
            /* on crée d'abord les nbrFragments - 1 premiers fragments */
            for (int i=1 ; i<=nbFragments ; i++) {
                /* écriture binaire bufférisée dans chaque fragment */
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "_part_" + i));
     
                // Chaque fois on calcule la position que le nouveau thread doit se placer pour lire
                position = i * (int)length; 
                Thread fragmentThread = new FragmentThread(bis, bos, position, length, countdown);
                fragmentThread.start();            
            }
     
            /* compléter le dernier fragement */
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "_part_"+ nbFragments));
            int b;
            while((b = bis.read()) != -1) {
                bos.write(b);
            }
            bos.close();
     
            /* il faut attendre que les autres thread terminenet avant de fermer le flux de lecture */
            countdown.await();
     
            bis.close();
     
            log(file, nbFragments);
        }
     
        /* méthode privée qui génére un fichier journal et y stocke le nombre de fragments */
        private void log(File file, int nbrFragments) throws FileNotFoundException, IOException {
            BufferedWriter bw = new BufferedWriter(new FileWriter(file.getAbsolutePath() + ".log"));
            bw.write(nbrFragments + "");
            bw.close();
        }
    }
    et le code de la classe FragmentThread :
    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
    package frag;
     
    import java.io.*;
    import java.util.concurrent.CountDownLatch;
     
    public class FragmentThread extends Thread {
        private BufferedInputStream bis;
        private BufferedOutputStream bos;
        private int startPosition;
        private long length;
        private CountDownLatch countdown;
     
        /* Constructeur */
        public FragmentThread(BufferedInputStream bis, BufferedOutputStream bos, int startPosition, long length, CountDownLatch countdown) {
            this.bis = bis;
            this.bos = bos;
            this.startPosition = startPosition;
            this.length = length;
            this.countdown = countdown;
        }
     
        @Override
        public void run() {
            try {
                int b;
                bis.skip(startPosition);
                for (int i=0; i<length; i++) {
                    if ((b = bis.read()) != -1) {
                        bos.write(b);
                    }
                }
     
                bos.close();
                countdown.countDown();
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    Mauvaise surprise : le temps de Fragmentation a triplé

    J'arrive pas à déterminer la cause.. merci pour vos éclaircissements..

  2. #2
    Modérateur
    Avatar de dinobogan
    Homme Profil pro
    ingénieur
    Inscrit en
    Juin 2007
    Messages
    4 073
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France

    Informations professionnelles :
    Activité : ingénieur
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 4 073
    Points : 7 163
    Points
    7 163
    Par défaut
    Tout d'abord, tes fragments ne sont plus séquentiels mais mélangés.
    Ensuite, en multi-Thread, il faut vérifier les endroits limitant les flux. Clairement, ici, c'est ton fichier source qui se trouve évidemment sur un seul disque dur et tes fichiers résultats écrits sur ce même disque. Les lectures sur un disque, comme les écritures, ne sont pas parallélisables.
    Enfin, il ne faut pas faire trop de Thread car le processeur a une capacité limité. En règle générale, plus on augmente le nombre de Thread, meilleurs sont les performances. En augmentant encore un peu, les perfs ne s'améliorent pas, puis ensuite si on augmente encore les Thread, les perfs s'effondrent.

    Ce n'est pas une bonne méthode de faire un Thread par fragment. Tu dois toi-même spécifier un nombre de Thread. Imagine qu'un utilisateur souhaite 1000 fragments sur un fichier : les performances vont être catastrophiques.
    Enfin, tu peux jouer sur la taille des buffers de lecture et d'écriture. Fais tes tests en les augmentant un peu.
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java
    Que la force de la puissance soit avec le courage de ta sagesse.

  3. #3
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Salut,

    Citation Envoyé par Chatbour Voir le message
    Mauvaise surprise : le temps de Fragmentation a triplé
    Normal : la concurrence n'est pas LA réponse absolu pour améliorer des performances ! Ce n'est pas parce qu'on est plusieurs à faire quelque chose que ce sera forcément fait plus vite...

    C'est vrai si les tâches sont bien distinctes, mais cela ne l'es pas si on doit se partager les mêmes outils...

    En devenant multithread, chaque accès au BufferedInputStream est synchronisé et les thread passent leurs temps à obtenir/libérer le lock associer au flux (la synchronisation "coûte" cher). En plus cela est amplifié par le fait que tu lisent les bytes un à un (chaque appel abouti à une vérification du lock).


    Citation Envoyé par dinobogan Voir le message
    Tout d'abord, tes fragments ne sont plus séquentiels mais mélangés.
    +1
    Comme les threads sont exécutés en parallèle, un thread peut lire un caractères correspondant à un autre... ce qui donne du grand n'importe quoi au final !
    Par exemple le premier thread fait le seek() et lit quelques caractères, le second fait de même, puis le premier reprend la main et continue à lire mais à l'emplacement du second thread (puisqu'il partage le même flux)...



    En fait pour améliorer un traitement par une exécution en parallèle, il faut que les différentes tâches soient bien distinctes et travailles sur leurs propres données sans les partager (pour éviter de trop nombreux lock/unlock). De plus cela permet d'améliorer les perfs seulement sur une architectures multicode ou multiprocess...



    Sinon j'aurais quelques remarques quand à ton code :
    • Pourquoi utiliser un constructeur pour faire un traitement ? Je verrais plutôt une méthode static !
    • Attention à la libération des flux, qui doivent s'exécuter dans un bloc finally ( Comment libérer proprement les ressources (ou comment utiliser proprement les bloc try/finally) ?)
    • Pour simplifier cela, il y a une règle simple : celui qui ouvre un flux le referme
    • Lorsque tu exécutes tes traitements dans des threads, tu n'as aucun moyen pour remonter l'exception (à part la trace). Donc ton programme pourrait continuer normalement malgré un plantage
    • Tu effectues les lectures bytes par bytes... Même si les Buffered*Stream sont bufférisé, tu multiplies inutilement les traitements. Une copie par bloc serait plus rapide. Tu pourrais même le faire directement sur les File*Stream (en gros cela consiste à gérer soit même la bufferisation).






    Bref dans ton cas j'aurais plutôt eu quelque chose de ce style :
    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
    	/**
             * Découpe le fichier 'file' en plusieurs fragments...
             */
    	public static void fragment(File file, int fragmentCount)
    			throws IOException {
    		// On récupère le fichier absolu une fois pour toute
    		file = file.getAbsoluteFile();
    		// On ouvre le fichier en lecture :
    		final FileInputStream input = new FileInputStream(file);
    		try {
    			/* calculer la taille de chaque fragment */
    			long fragmentLenght = file.length() / fragmentCount;
    			// On gère le cas où le fichier serait plus petit
    			// que le nombre de fragment :
    			if (fragmentLenght == 0) {
    				// => On force 1 seul fragment
    				fragmentLenght = file.length();
    				fragmentCount = 1;
    			}
     
    			for (int current = 1; current <= fragmentCount; current++) {
    				// On ouvre le fichier de destination :
    				final FileOutputStream output = new FileOutputStream(file + "_part_" + current);
    				try {
    					// On copie les données d'un buffer à l'autre :
    					copy(input, output, fragmentLenght);
    					// Dans le cas du dernier fragment, 
    					// on copie jusqu'à la fin du fichier :
    					if (current == fragmentCount) {
    						copy(input, output, file.length());
    					}
    				} finally {
    					output.close();
    				}
    			}
    		} finally {
    			input.close();
    		}
    	}
     
    	/**
             * Copie de 'in' vers 'out' un maximum de 'length' bytes.
             */
    	private static void copy(final InputStream in, final OutputStream out,
    			final long lenght) throws IOException {
    		// Le buffer de copie :
    		final byte[] buf = new byte[8192];
    		// Le nombre de données à recopier :
    		long toCopy = lenght;
    		// Le nombre de bytes lu
    		int len;
     
    		// On lit dans le fichier tant qu'il reste des données à lire
    		// et tant que le fichier comporte encore des données
    		while (toCopy > 0
    				&& (len = in.read(buf, 0, min(toCopy, buf.length))) >= 0) {
    			// On copie ce qui a été lu :
    			out.write(buf, 0, len);
    			// Et on met à jour les compteurs
    			toCopy -= len;
    		}
    	}
     
    	/**
             * Retourne la valeur minimale entre un type 'long' et un type 'int'
             */
    	private static int min(long longValue, int intValue) {
    		// Si la valeur 'long' est plus grand que la valeur d'un Integer
    		if (longValue > Integer.MAX_VALUE) {
    			// On renvoit la valeur 'int' forcément plus petite
    			return intValue;
    		}
    		// Sinon on renvoi le min en castant en (int)
    		// (on ne peut pas caster directement,
    		// car le long pourrait se retrouver en négatif)
    		return Math.min((int) longValue, intValue);
    	}

    Avec cela je passe d'environ 5s avec ton code à 2.5s...


    Mais la meilleure solution est bel et bien d'utiliser toutes la puissance de l'API, et en particulier des FileChannel qui permettent un accès plus direct aux ressources matériels/externes, et de bien meilleures performances, ce qui donnerait :
    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
    	/**
             * Découpe le fichier 'file' en plusieurs fragments,
             * en utilisant les channels
             */
    	public static void fragmentWithChannel(File file, int fragmentCount)
    			throws IOException {
    		// On récupère le fichier absolu une fois pour toute
    		file = file.getAbsoluteFile();
    		// On ouvre le fichier en lecture :
    		final FileChannel input = new FileInputStream(file).getChannel();
    		try {
    			/* calculer la taille de chaque fragment */
    			long fragmentLenght = file.length() / fragmentCount;
    			// On gère le cas où le fichier serait plus petit
    			// que le nombre de fragment :
    			if (fragmentLenght == 0) {
    				// => On force 1 seul fragment
    				fragmentLenght = file.length();
    				fragmentCount = 1;
    			}
     
    			for (int current = 1; current <= fragmentCount; current++) {
    				// On ouvre le fichier de destination :
    				final FileChannel output = new FileOutputStream(file + "_part_"
    						+ current).getChannel();
    				try {
    					// On copie les données d'un buffer à l'autre :
    					output.transferFrom(input, 0, fragmentLenght);
    					// Dans le cas du dernier fragment, 
    					// on copie jusqu'à la fin du fichier :
    					if (current == fragmentCount) {
    						output.transferFrom(input, 0, input.size());
    					}
    				} finally {
    					output.close();
    				}
    			}
     
    		} finally {
    			input.close();
    		}
    	}
    Là je passe sous la barre des 1 secondes...


    a++

  4. #4
    Membre averti Avatar de Chatbour
    Profil pro
    Étudiant
    Inscrit en
    Septembre 2006
    Messages
    431
    Détails du profil
    Informations personnelles :
    Localisation : Tunisie

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Septembre 2006
    Messages : 431
    Points : 305
    Points
    305
    Par défaut
    merci beaucoup les amis

    merci énormément AdiGuba c'est la nième fois que tu me sauves

  5. #5
    Membre averti Avatar de Chatbour
    Profil pro
    Étudiant
    Inscrit en
    Septembre 2006
    Messages
    431
    Détails du profil
    Informations personnelles :
    Localisation : Tunisie

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Septembre 2006
    Messages : 431
    Points : 305
    Points
    305
    Par défaut
    Re,

    AdiGuba, j'ai essayé les FileChannel.. c'est vrai que le temps de fragmentation s'est réduit mais en dépit de la mémoire, car j'ai remarqué que toute la taille spécifiée (fragmentLength) se charge dans la mémoire avant d'être copiée dans le fragment..

    j'ai opté donc pour l'utilisation d'un buffer (byte[]) de la taille d'un mégaoctet..

    Merci encore

Discussions similaires

  1. Réponses: 10
    Dernier message: 30/06/2011, 15h57
  2. Réponses: 2
    Dernier message: 16/12/2009, 18h14
  3. programmer moins lourd pour le pross
    Par Isses dans le forum Flash
    Réponses: 2
    Dernier message: 08/08/2006, 11h35
  4. Déconnexion moins rapide
    Par nicoaix dans le forum Mode d'emploi & aide aux nouveaux
    Réponses: 14
    Dernier message: 07/07/2006, 12h20
  5. Empecher deux versions d'un meme programme
    Par yakotey dans le forum C++
    Réponses: 5
    Dernier message: 05/07/2005, 21h45

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