[Java] gestion des objets

Résolu/Fermé
moussecp Messages postés 56 Date d'inscription lundi 30 juillet 2007 Statut Membre Dernière intervention 10 mai 2012 - 30 nov. 2011 à 02:53
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 - 9 déc. 2011 à 15:40
Bonjour,

Je travaille sur de traitement de signal audio et assez rapidement, j'arrive à des situations ou je sature la mémoire vive de mon ordinateur. Je cherches donc à optimiser l'utilisation de mes objets pour limiter les dépenses inutiles.

Considérez le cas suivant. J'ai un objet Signal qui possède plusieurs champs. Par exemple :
class Signal{
   private int freqEchantillonnage;
   private int nbrEchantillons; 
   private double dureeEnSecondes;
   priate double[] echantillons;

   public Signal(freqEchantillonnage, echantillons[], dureeEnSecondes){
       this.freqEchantillonnage = freqEchantillonnage;
       this.echantillons = echantillons;
       nbrEchantillons = echantillons.size();
       this.dureeEnSecondes = dureeEnSecondes;
   }

   public int getFreqEchantillonnage(){
      return freqEchantillonnage;
   }
   public double[] getEchantillons(){
      return echantillons;
   }
}


Soit maintenant une classe Traitement qui va traiter ce signal. Pour cette classe, j'ai besoin de connaitre la fréquence d'échantillonnage et les échantillons de mon objet Signal. Comment puis-je faire pour que mon code travaille sur les mêmes champs et ne copie pas bêtement ces valeurs ?

Est-ce que faire passer l'objet Signal en paramètre lors de la construction de la classe Traitement reviens à recréer un nouvel objet ou non ?

class Traitement{
   private int freqEchantillonnage;
   private double[] echantillons; 
   private Signal monSignal;

   public Traitement(Signal monSignal){
       this.monSignal = monSignal;       
       freqEchantillonnage = monSignal.getFreqEchantillonnage;
       echantillons = monSignal.getEchantillons;
   }

}


Par contre, il me semble assez clair que dans mon exemple ci-dessus, je recréé des valeurs freqEchantillonnage et echantillons et que je ferais mieux de les déclarer comme étant public dans ma classe Signal et si j'en ai besoin, de les lire ainsi : monSignal.freqEchantillonnage et monSignal.echantillons[].

Est-ce correcte ? Ou est-ce que je n'ai rien compris. :)

Merci à vous.




A voir également:

5 réponses

tarek_dotzero Messages postés 817 Date d'inscription jeudi 19 juillet 2007 Statut Membre Dernière intervention 12 avril 2022 120
30 nov. 2011 à 10:35
Bonjour,


En réalité, vous ne créez que les valeurs entières, les tableaux sont considérés comme des objets pour Java alors:

echantillons = monSignal.getEchantillons();


Ne fait que passer la référence et pas l'ensemble du tableau (normalement).

Je pense qu'il faut voir ailleurs: un programme se bloque quand il atteint les 80 Mo dans la RAM et je ne pense pas que quelques tableaux de
double
vont causer cela.

Bon Courage.
0
Char Snipeur Messages postés 9696 Date d'inscription vendredi 23 avril 2004 Statut Contributeur Dernière intervention 3 octobre 2023 1 297
30 nov. 2011 à 12:19
80Mo seulement ? c'est vite atteint si le tableau stock le signal sonore (en CD, 80min <->700Mb, donc tu ne peux chargé un signal de plus de 10 minutes !).
Je ne connais pas assez java pour me prononcer sur la duplication du double[]. En revanche, si ton but est d'éviter la duplication, pourquoi faire une variable echantillons dans la classe Traitement étant donné que tu stocke la variable Signal qui contient echantillons.
ça manque un peu de détails tout ça...
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
30 nov. 2011 à 12:28
Saturer la mémoire vive de ton ordinateur est impossible, même si tu n'as que 504 Mo de RAM, Java travaille sur une machine virtuelle, et c'est la mémoire de la machine virtuelle qui va saturer pas celle de l'ordinateur.
Donc soit c'est un problème de code, mais dans ce cas ce n'est pas avec les morceaux de code que tu nous a donné mais ailleurs (peut-être un StackOverflowError qui est dû à une boucle infinie), soit c'est un problème de configuration de ta JVM qui n'a pas assez de mémoire vive autorisée, tu peux regarder par exemple la valeur maximale allouée en faisant :
System.out.println((double)Runtime.getRuntime().maxMemory()/Math.pow(2,20));
Si effectivement la quantité de mémoire allouée n'est pas suffisante, mais que tu veux l'allouer manuellement tu peux le faire avec l'option -Xmx256m (ici pour lui attribuer 256 Mo)
0
moussecp Messages postés 56 Date d'inscription lundi 30 juillet 2007 Statut Membre Dernière intervention 10 mai 2012
30 nov. 2011 à 17:52
En effet, mon programme est bien plus complexe que ce que je propose dans mes exemples. L'idée de ceux-ci était plus de comprendre comment les passages de valeurs fonctionnaient.

Ceci-dit, si je considère travailler avec des objets Echantillons avec entre 5 et 10 attributs. Que je travail avec un signal audio de 15 minutes (soit 900s) échantilloné à 44,1 kHz, ça me fait 39 690 000 échantillons, donc 39 690 000 objets Echantillons.

Mon promoteur maintient que si je travail comme ça, la mémoire virtuelle va saturer. Le but de mon projet est de pouvoir travailler avec des signaux à durée arbitraire. Donc je suis obligé de travailler avec des trames de signaux.

Donc si j'ai bien compris, si je passe un vecteur d'un million d'éléments d'une classe à l'autre, je ne fais que passer les références donc il n'y a pas de problème de gaspillage de mémoire.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
30 nov. 2011 à 18:45
Je n'y connais rien en traitement de signal (mais alors rien du tout) mais est-ce que t'es vraiment obligé d'avoir tous tes échantillons en mémoire en même temps ? Est-ce que tu ne peux pas faire du traitement seconde par seconde par exemple ? Quitte à devoir créer tes objets échantillons, les détruire puis les recréer à nouveau, mais ne pas tous les avoir en même temps en mémoire.
0
Char Snipeur Messages postés 9696 Date d'inscription vendredi 23 avril 2004 Statut Contributeur Dernière intervention 3 octobre 2023 1 297
1 déc. 2011 à 08:47
Si Java est aussi limité que ça, change de langage. Tu peux aussi optimisé tes données. Ton échantillon a un codage, il faut adapter la taille de ta variable à ce codage. Un CD audio est codé sur 16 bits, un double est codé sur 64 bits. L'idéal pour représenter un signal 16 bit est d'utiliser une tableau de short non signé.
La technique de KX de sauver sur le disque, en plus d'introduire un complexité supplémentaire, source de bug dans ton code, le fera aussi énormément ralentir.Avec un PC de 2Go de RAM, tu peux stocker au moins 2CD entiers.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
1 déc. 2011 à 13:30
Je ne pense pas que le problème soit vraiment Java, sa configuration ou son utilisation bien plus.
Moi ce que je trouve bizarre c'est de tout charger en mémoire, un peu comme si VLC était obligé de charger les 8 Go Mo d'un DVDen mémoire pour le lire, ce n'est évidemment pas le cas. Pourquoi ne pas lire les données au fur et à mesure depuis le support (quitte à y revenir au besoin) sans avoir à stocker 40 millions d'objets dans un tableau (évidemment avec autant d'éléments il y aurait intérêt à utiliser des structures de données bien plus efficaces que des tableaux mais c'est un autre problème)
0
Char Snipeur Messages postés 9696 Date d'inscription vendredi 23 avril 2004 Statut Contributeur Dernière intervention 3 octobre 2023 1 297
1 déc. 2011 à 14:07
Le problème, c'est qu'en traitement du signal, tu fais des opérations sur l'ensemble. Par exemple, si tu veux monter le volume il faut multiplier toutes les valeurs par gain. Encore, ça c'est facile à faire par morceau. Mais si tu veux faire un filtre, pour diminuer le bruit par exemple, chaque nouvelle valeur dépendra de ses voisines, si bien qu'il faut faire un chargement glissant. Et ça c'est nettement plus difficile à faire. C'est pour ça, tant que c'est possible, autant tout mettre en mémoire.
Pour continuer la comparaison, VLC, on lui demande de traiter une certaine quantité de données en un certain temps (en général assez large) du coup il est possible de faire des opérations "inutile" (comme la gestion de la mémoire). Lorsque tu traites un signal, le but c'est d'aller le plus vite possible, il faut que ça soit rapide pour l'utilisateur.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
1 déc. 2011 à 14:48
Dans ce cas, il va falloir faire ce que j'ai indiqué plus haut : augmenter la taille de la mémoire de la machine virtuelle, donc -Xmx pour la taille maximale, et comme on sait qu'on va tout utiliser, autant également configurer -Xms pour la taille initiale. Les possibilités réelles vont évidemment dépendre de la configuration de la machine, mais la RAM part très vite. Pour avoir un ordre d'idée voici ce que me donne un programme qui remplit un tableau avec 40 millions d'objets (évidemment les valeurs que j'obtient ici vont varier d'une machine à l'autre)

private static Runtime rt = Runtime.getRuntime();
private static void mem() {
System.out.println((double)(rt.totalMemory()-rt.freeMemory())/Math.pow(2,20));
}

public static void main(String...args)
{		
	//  n = 40 000 000
	int n = (int) 4E7;
	
	mem(); //   0.3 Mo
	
	// Un tableau avec que des null
	Object tab[] = new Object[n];
	
	mem(); // 152.9 Mo
	
	// Un tableau avec un objet à chaque case
	for (int i=0; i<n; i++)
		tab[i]=new Object();
	
	mem(); // 764.5 Mo

Remarque : j'ai pris des Object, mais plus chacun des objets aura de données unitaire, plus il faudra multiplier ces valeurs, il va donc falloir drastiquement contrôler la quantité de données de chaque objet.
De plus si on copie un tableau, le tableau copié aura les même références, donc on ne copiera pas les 611.6 Mo (764.5-152.9) de données, en revanche la mémoire de ce deuxième tableau est quand même de 152.6 Mo (152.9-0.3) qui se rajoute aux valeurs précédentes. Il faut donc s'assurer de ne pas copier le tableau, mais de toujours travailler sur sa référence.
0
Char Snipeur Messages postés 9696 Date d'inscription vendredi 23 avril 2004 Statut Contributeur Dernière intervention 3 octobre 2023 1 297
1 déc. 2011 à 16:12
Oui, mais est-ce vraiment la peine de faire un tableau d'objets ?
Un tableau d'entrées prend juste la taille de ses données. J'avoue ne pas très bien comprendre les problème d'échantillons de moussecp.
Sa classe Signal est très bien, je remplacerais juste le tableau de double par un tableau de float ou de "unsigned short" pour optimiser la taille, il faut utiliser un type primitif et non un tableau d'objet.
Mais -Xmx devrait faire l'affaire.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
1 déc. 2011 à 16:50
"Un tableau d'entrées prend juste la taille de ses données" : pas si c'est des objets.
Avec le système de références en Java, les tableaux ne manipulent pas directement les objets mais des références. Pour faire une analogie en C ce serait comme manipuler des tableaux de pointeurs (152.6Mo = 40 millions * 4octets, puisque une référence c'est - comme pour les pointeurs - un entier).

En revanche, comme tu l'as dit, on peut bien sûr faire un tableau de types primitifs pour optimiser l'espace, dans ce cas les données sont en mémoire dans un coin, et les objets qui les manipulent sont créés et détruit au fur et à mesure sans de trop grosses consommations de ressources. Malheureusement les types primitifs en Java sont très peu nombreux (pas de unsigned par exemple) et on ne peut pas les combiner sans créer des objets. Du coup si on voulait faire des tableaux de "structures" en Java sans faire d'objets, il faudrait faire un tableau par champ de données et créer virtuellement la "ième" structure en récupérant les cases i de chaque tableau de données.
Ce n'est pas très orienté objet, mais si on veut optimiser le programme on ne pourra pas faire mieux...
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
moussecp Messages postés 56 Date d'inscription lundi 30 juillet 2007 Statut Membre Dernière intervention 10 mai 2012
9 déc. 2011 à 14:43
Merci pour vos réponses. :)

A dire vrai, je n'ai pas le choix du langage. Mon promoteur m'impose Java et puis c'est tout. Donc je dois en tirer le maximum. Ceci-dit, je ne dois pas faire des calculs hyper complexes avec des matrices ou je ne sais encore... Donc ça va. De plus, je dois entre autre faire des affichages graphiques. Via Java et Netbeans, je m'en tire très bien.

Je n'ai en effet pas besoin de prendre toute ma chaine d'échantillons pour faire mes calculs et pour le moment je sauvegarde mes données dans un fichier CSV de manière très simple. L'idée et que par après, je vais aller récupérer ces données pour faire des affichages de graphs.

Cependant, comme l'a dit Char Snipeur, dès que je passe par une sauvegarde sur disque, j'y perds énormément en performances. Quelqu'un a une idée de comment est-ce que je pourrais optimiser ça ? :)

Voici mes deux classes me permettant d'écrire dans les fichiers csv.

package utils;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CsvWriter implements ConstantsAndTools {

    private File outputDir = new File(CSV_PATH);
    private File csvFile;
    private String fileName;
    private String headers;
    private FileWriter writer;
    private FileReader reader;

    public CsvWriter(String fileName, String headers) throws IOException {
        this.fileName = fileName;
        this.headers = headers;
        if (!outputDir.exists()) {
            outputDir.mkdir();
        }
        csvFile = new File(CSV_PATH + fileName + ".csv");
        if (!csvFile.exists()) {
//            csvFile.delete();
            csvFile.createNewFile();
        }
//        csvFile.createNewFile();

        writer = new FileWriter(csvFile, true);
        reader = new FileReader(csvFile);

    }

    public void writeNext(String data) throws IOException {
        if (reader.read() == -1) {
            writer.append(headers + "\n");
        }

        writer.append(data + "\n");
        writer.flush();
    }

    public void closeWriter() {
        try {
            writer.close();
        } catch (IOException ex) {
            Logger.getLogger(CsvWriter.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public String getFileName() {
        return fileName;
    }
}


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package csvoutput;

import java.io.IOException;
import utils.ConstantsAndTools;
import utils.CsvWriter;

import wavfilesmanagement.SoundFrame;

/**
 *
 * @author id825759
 */
public class WriteFrameDataToCSV implements ConstantsAndTools {

    private String separator = ";";
    private String headers = "FrameNumber;"
            + "DyspE;"
            + "WindowE;"
            + "OptimalShift;";
    private String fileName = CSV_FILE_NAME;

    public WriteFrameDataToCSV(SoundFrame sf) throws IOException {
        CsvWriter wicf = new CsvWriter(fileName, headers);
        fileName = wicf.getFileName();

        String rowString = generateRfdDetailsCSVFileRow(sf);
        wicf.writeNext(rowString);

        wicf.closeWriter();
    }

    private String replaceUndesirableCharacters(String favoriteName) {

        favoriteName = favoriteName.replace('/', '_');
        //favoriteName = favoriteName.replace('\', '_');
        favoriteName = favoriteName.replace(':', '-');
        favoriteName = favoriteName.replace('*', '_');
        favoriteName = favoriteName.replace('?', '_');
        favoriteName = favoriteName.replace('"', '_');
        favoriteName = favoriteName.replace('<', '_');
        favoriteName = favoriteName.replace('>', '_');
        favoriteName = favoriteName.replace('|', '_');
        favoriteName = favoriteName.replace(' ', '_');

        return favoriteName;
    }

    private String generateRfdDetailsCSVFileRow(SoundFrame soundF) {
        String aString;


        aString = soundF.getFrameNumber() + separator
                + soundF.getDyspE() + separator
                + soundF.getWindowE() + separator
                + soundF.getOptimalShift() + separator;
        return aString;
    }

    public String getFileName() {
        return fileName;
    }
}
0
Char Snipeur Messages postés 9696 Date d'inscription vendredi 23 avril 2004 Statut Contributeur Dernière intervention 3 octobre 2023 1 297
9 déc. 2011 à 15:12
faire du csv, donc du texte pour traiter des son, ça me semble une abération. Utilise plutôt des fichiers binaires.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
9 déc. 2011 à 15:40
1) à quoi ça te sers d'implémenter ConstantsAndTools ?
2) Pour ta classe CsvWriter, tu peux te passer de ton reader
3) Pour ta classe WriteFrameDataToCSV, tu ouvres le fichier tu écris une ligne et tu le refermes, c'est là que tu perds le plus de temps. Ouvre ton fichier une fois, écris tout ce que tu as à y mettre, et ferme le une fois (sinon ta classe CsvWriter ne sers pas à grand chose)
4) Plein de petites améliorations à apporter, j'ai pu en oublier :

class CsvWriter //implements ConstantsAndTools 
{
    private static final String CSV_PATH = null;

    private final static File outputDir = new File(CSV_PATH);
    {
        if (!outputDir.exists())
            outputDir.mkdir();
    }
    
    private final String fileName;    
    private final BufferedWriter writer;
       
    public CsvWriter(String fileName, String headers) throws IOException 
    {
        this.fileName = fileName;
        
        File csvFile = new File(outputDir,fileName+".csv");
        
        // true si le fichier n'existe pas encore
        boolean first = !csvFile.exists();
        
        // Créé le fichier s'il n'existe pas
        // Se positionne à la fin s'il existe
        // Utilise un buffer pour optimiser l'écriture
        writer = new BufferedWriter(new FileWriter(csvFile, true));
        
        // Si le fichier n'existait pas avant
        // On ajoute les en-têtes
        if (first)
            writeNext(headers);
    }
    
    public void writeNext(String data) throws IOException
    {
        writer.append(data);
        writer.newLine();
    }
    
    public void closeWriter()
    {
        try 
        {
            writer.close();
        }
        catch (IOException ex) 
        {
            Logger.getLogger(CsvWriter.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

public class WriteFrameDataToCSV //implements ConstantsAndTools 
{
    public final static String separator = ";";
    public static enum Header {FrameNumber, DyspE, WindowE, OptimalShift };
    
    private static final String headers =
            Header.FrameNumber+separator+
            Header.DyspE+separator+
            Header.WindowE+separator+
            Header.OptimalShift+separator;

    public WriteFrameDataToCSV(CsvWriter wicf, SoundFrame sf) throws IOException 
    {
        wicf.writeNext(generateRfdDetailsCSVFileRow(sf));
    }

    private static String replaceUndesirableCharacters(String favoriteName)
    {
        return favoriteName.replaceAll("[ /\\\\:\\*\\?\"<>|\\s]", "_");
    }

    private static String generateRfdDetailsCSVFileRow(SoundFrame soundF) 
    {        
        return new StringBuilder()
            .append(soundF.getFrameNumber())
                .append(separator)
            .append(soundF.getDyspE())
                .append(separator)
            .append(soundF.getWindowE())
                .append(separator)
            .append(soundF.getOptimalShift())
                .append(separator)
            .toString();
    }
}

NB. J'ai du rajouter un espace entre [ et / dans replaceUndesirableCharacters pour ne pas que [ ] soit interprété comme un hyperlien sur le forum, mais tu dois l'enlever...
0