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

C# Discussion :

Trop de "using(var foo = new Foo())" ?


Sujet :

C#

  1. #1
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut Trop de "using(var foo = new Foo())" ?
    Hello,

    Tout d'abord, mes connaissances en C# sont assez limitées.

    J'ai un petit utilitaire pour faire un atlas à partir d'une font. Je l'ai codé en C++ (avec la SFML), ça marche pas trop mal.

    Créer une GUI en C++ c'est plutôt chiant, du coup j'ai recoder ça en C# (copier / coller et corriger les erreurs de syntaxe ), jusque là pas de problèmes.

    Mais rapidement je me suis heurté à de gros problèmes de gestion de mémoire en C# (l’ironie...).
    Basiquement j'ai quelque chose du genre
    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
    public class Atlas {
       public static Image Pack(string fontName, uint fontSize, string charToDraw, uint atlasSize=4096) {
          // Image est SFML.Graphics.Image
          // utilisation de SFML.Graphics.RenderTexture pour créer l'atlas
          // return null si atlas impossible à créer (les caractères ne rentre pas dans la taille demandée)
          // return l'image sinon
       }
     
       public class PackResult {
          public Image img;
          public uint fontSize;
       }
     
       public static PackResult PackMaxSize(string fontName, string charToDraw, uint maxFontSize=1000, uint atlasSize=4096) {
           // recherche dichotomique de la taille maximale possible
           // 10 appels maximum à Pack pour maxFontSize=1024 par exemple
       }
    }
    Clairement le code n'est pas optimal (pas besoin d'essayer de créer tous les atlas avec toutes les allocations de RenderTexture que ça représente).

    Si j'essaie de créer plusieurs atlas, SFML fini par throw une exception : je remplis la VRAM parce que le GC ne juge pas nécessaire de faire le ménage.

    A priori la solution est d'utiliser un using(var foo = new Foo()) { } pour chaque ressource dont je dois contrôler la durée de vie (donc strict minimum toutes les classes SFML qui allouent quelque chose en VRAM).

    Çà marche, mais j'ai tellement de using(var foo = new Foo()) { } que ça en devient ridicule : un new = un using(...) { }.
    Ya une solution à ce genre de problème ? Peut être avec une classe contenant les variables locales d'une fonction ?

    Par exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private class PackLocalVars {
       public Font font;
       public Text text;
       public Atlas atlas;
       public RenderTexture rTex;
    }
     
    public static Image Pack(string fontName, uint fontSize, string charToDraw, uint atlasSize=4096) {
       using (var locals = new PackLocalVars()) {
          locals.rTex = new RenderTexture(...);
          // ...
          return locals.rTex.Texture.copyToImage();
       } // locals.rTex.Dispose() sera bien appelé ici ? Il faut que PackLocalVars implémente IDisposable ?
    }
    Mais... ça me semble être une solution assez mauvaise (créer une classe pour chaque fonction...), et surtout ça semble être une façon de faire qui veut trop se caler sur la façon de "penser C++".

    Bref, une bonne solution existe à ce problème ?

    Question bonus, pourquoi ça ne marche pas ? -> La conso RAM / VRam explose et j'ai la même exception de lancé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
    public static PackResult PackMaxSize(string fontName, string charToDraw, uint maxFontSize=1000, uint atlasSize=4096) {
       var ret = PackResult();
       while(...) {
            System.GC.Collect();
            uint fontSize = ...;
            Image img = Pack(...);
     
            if(img != null) {
               ret.img = img;
               ret.fontSize = fontSize;
            }
       }
       return ret;
    }

  2. #2
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    Salut

    si tu alloues des ressources SFML, il y a des chances que ce soient des ressources allouées en C++ et donc, le GC va pas trop s'en sortir.

    Par contre, comme tu récupères une copie de ton image, à mon avis, il te faut faire un Dispose() pour nettoyer les "handle d'image" à un moment donné.

    Ou bien, il faut à un moment donné faire une fonction de conversion pour travailler avec des Bitmap C# ou des images.

    Ne connaissant pas bien SFML, je ne pourrais pas trop aller plus loin.
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  3. #3
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    Après, je ne sais pas si tu es au courant, mais il existe un binding C# pour SFML.

    http://www.sfml-dev.org/download/sfml.net/


    Peut-être que l'utilisation de ce binding (wrapper) apporterait une meilleure gestion de la mémoire et donc une résolution de ton problème.
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  4. #4
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par theMonz31 Voir le message
    Après, je ne sais pas si tu es au courant, mais il existe un binding C# pour SFML.

    http://www.sfml-dev.org/download/sfml.net/


    Peut-être que l'utilisation de ce binding (wrapper) apporterait une meilleure gestion de la mémoire et donc une résolution de ton problème.
    Je l'utilise actuellement.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var img0 = new SFML.Graphics.Image(42, 42);
    fonctionQuiPeutThrow(); // si ça throw, img0.Dispose() ne sera pas appelé au moment voulu, et va trainer en mémoire
    img0.Dispose();
     
    using(var img1 = new SFML.Graphics.Image(42, 42)) {
       fonctionQuiPeutThrow(); // même si ça throw, pas de problèmes ?
    }
     
    var img2 = new SFML.Graphics.Image(42, 42);
    fonctionQuiPeutThrow(); // si ça throw, img2 va trainer en mémoire quelques temps.
    img2 = null;
    System.GC.Collect();
    Le problème de Dispose() c'est qu'on est à la merci des exceptions.
    Ma question est de savoir s'il existe un moyen simple / propre de garantir la libération de différentes ressources à un moment donné sans avoir des millions de using imbriqués.

    Appeler Dispose() / System.GC.Collect() manuellement me fait peur, c'est assez tendu de garantir qu'il sera appelé au moment voulu même en cas d'exception sans avoir un try / catch à chaque ligne.

    Dans le cas d'une ressource stockée dans la RAM ça pose pas de problèmes particulier : laisser le GC faire son boulot est suffisant la plupart du temps.
    Mais dans le cas de ressource stockées en VRAM c'est différent, le GC n'a pas de moyen de savoir quand la VRAM est pleine et qu'il doit faire le ménage.

  5. #5
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    le problème avec les librairies qui sont des wrappers sur des librairies C++ est qu'il faut "souvent" gérer la mémoire... donc, en dehors de Dispose() ou de GC.collect, ya des chances que tu n'es guères d'autres solutions...

    Maintenant, je pense que ce type de problématique doit avoir été traitées ou posées sur les forum de SFML ? non ?
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  6. #6
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Çà marche, mais j'ai tellement de using(var foo = new Foo()) { } que ça en devient ridicule : un new = un using(...) { }.
    Ya une solution à ce genre de problème ? Peut être avec une classe contenant les variables locales d'une fonction ?
    Non, aucune solution à part utiliser les using. Pas de RAII en C#.

    Le mieux que je puisse te proposer c'est d'encapsuler la logique commune dans une fonction qui prendra un lambda en argument, ou fera appel à une méthode virtuelle que tu surchargeras à chaque fois.

    Ou alors tu pourrais créer une structure de données représentant les opérations à traiter et laisser un moteur la concrétiser.

    Question bonus, pourquoi ça ne marche pas ? -> La conso RAM / VRam explose et j'ai la même exception de lancée.
    Utilise GC.WaitForPendingFinalizers : les finaliseurs peuvent être exécutés sur n'importe quel thread après la passe du GC.

    Note cependant que c'est extrêmement inefficace : non seulement une passe complète du GC est une opération lourde faîte pour ne jamais être réalisée sauf en cas de nécessité, et en plus ce faisant tu forces le GC à promouvoir des objets vers la génération supérieure : autrement dit tu réalises des copies de mémoire inutiles et tu crées des objets éphémères qui ne seront pas libérés sans nouvelle passe du GC.


    Citation Envoyé par Iradrille Voir le message
    [code]using(var img1 = new SFML.Graphics.Image(42, 42)) {
    fonctionQuiPeutThrow(); // même si ça throw, pas de problèmes ?
    }
    Dispose sera toujours appelé même en cas d'exception, comme un bloc finally.

    Les seuls cas où ce n'est pas vrai sont les arrêts anormaux du processus (coupure courant, Windows qui s'impatiente, stack overflow). Pour le stack overflow en particulier, parfois Dispose sera exécuté, parfois non : pas de garantie dans ce cas-là.

  7. #7
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par theMonz31 Voir le message
    le problème avec les librairies qui sont des wrappers sur des librairies C++ est qu'il faut "souvent" gérer la mémoire... donc, en dehors de Dispose() ou de GC.collect, ya des chances que tu n'es guères d'autres solutions...
    C'est ce que je voulais savoir, merci.

    Citation Envoyé par theMonz31 Voir le message
    Maintenant, je pense que ce type de problématique doit avoir été traitées ou posées sur les forum de SFML ? non ?
    J'aurais le même problème avec n'importe quelle lib (je parle de la SFML parce que je l'utilise actuellement, mais la problématique est la même pour n'importe quelle lib C/C++).

  8. #8
    Expert confirmé

    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Septembre 2006
    Messages
    3 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Chef de projet NTIC
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Septembre 2006
    Messages : 3 580
    Points : 5 195
    Points
    5 195
    Par défaut
    tu auras la même problématique pour les librairies C++ qui allouent des ressources (type handle entre autre)... il faut les libérer manuellement si on veut être propre.

    C'est une des seules failles de .Net pour les fuites mémoires.
    The Monz, Toulouse
    Expertise dans la logistique et le développement pour
    plateforme .Net (Windows, Windows CE, Android)

  9. #9
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par DonQuiche Voir le message
    Le mieux que je puisse te proposer c'est d'encapsuler la logique commune dans une fonction qui prendra un lambda en argument, ou fera appel à une méthode virtuelle que tu surchargeras à chaque fois.
    Hum, je suis pas sur de bien comprendre, quelque chose comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    delegate void delType(ref Image img);
    void ExecOnImg(delType d) {
        using (var i=new Image()) {
            d(ref i);
        }
    }
    Citation Envoyé par DonQuiche Voir le message
    Ou alors tu pourrais créer une structure de données représentant les opérations à traiter et laisser un moteur la concrétiser.
    Çà me semble bien trop compliquer pour un code de quelques centaines de lignes, mais je note l'idée pour plus tard (si jamais je bosse sur un plus gros projet en C#).

    Citation Envoyé par DonQuiche Voir le message
    Utilise GC.WaitForPendingFinalizers : les finaliseurs peuvent être exécutés sur n'importe quel thread après la passe du GC.

    Note cependant que c'est extrêmement inefficace : non seulement une passe complète du GC est une opération lourde faîte pour ne jamais être réalisée sauf en cas de nécessité, et en plus ce faisant tu forces le GC à promouvoir des objets vers la génération supérieure : autrement dit tu réalises des copies de mémoire inutiles et tu crées des objets éphémères qui ne seront pas libérés sans nouvelle passe du GC.
    Bon à savoir, donc ça "marchait pas" parce que la mémoire était allouée plus rapidement que le GC n'arrivait à nettoyer le bordel depuis un autre thread ?

    Citation Envoyé par theMonz31 Voir le message
    tu auras la même problématique pour les librairies C++ qui allouent des ressources (type handle entre autre)... il faut les libérer manuellement si on veut être propre.
    Oui libérer les ressources n'est pas un problème, la question était de savoir comment faire : pas de savoir quelle fonction appeler, mais comment garantir qu'elle sera appelée quand je veux.

  10. #10
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Hum, je suis pas sur de bien comprendre, quelque chose comme ça:
    Oui...

    Bon à savoir, donc ça "marchait pas" parce que la mémoire était allouée plus rapidement que le GC n'arrivait à nettoyer le bordel depuis un autre thread ?
    ...et encore oui. ^^

  11. #11
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Bonjour,

    Pour avoir tenté un jour de me lancer dans un dev sur Android, qui dispose du GC de Java 1.4 (donc autant dire de la merde), j'ai pu retenir une astuce (valable dans n'importe quel langage, avec ou sans GC) permettant d'améliorer considérablement la pression mémoire...

    Plutôt que de recréer de nouvelles instances et les détruire à tout bout de champ, essayez de passer par des variables d'un scope plus global, et essayez de les écraser (ou même réutiliser si c'est possible, sans même les écraser).

    Du coup :

    Au lieu de faire :
    Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    for (int i = 0; i < 10; i++)
    {
       var foo = new Foo(i);
       foo.Bar();
    }

    On pourra tenter de faire :
    Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    var foo;
    for (int i = 0; i < 10; i++)
    {
       foo = new Foo(i);
       foo.Bar();
    }

    Voir même :
    Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    var foo = new Foo();
    for (int i = 0; i < 10; i++)
    {
       foo.Bar(i);
    }

    Ceci permet de n'avoir qu'une seule instance de Foo() en mémoire. Et dans le dernière exemple, même les ressources non managée sont ne sont pas dupliquées, réduisant ainsi les risques de fuite mémoire et le boulot du GC.
    On ne jouit bien que de ce qu’on partage.

  12. #12
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Il n'y a aucune différence entre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for (int i = 0; i < 10; i++) {
       var foo = new Foo(i);
       foo.Bar();
    }
    // Et 
    Foo foo;
    for (int i = 0; i < 10; i++) {
       foo = new Foo(i);
       foo.Bar();
    }
    ?

    Dans les 2 cas il y aura 10 objets alloués, non ? La seule différence c'est que dans le 2eme cas on garde une ref sur le dernier objet, l’empêchant temporairement d'être libéré ? On peut forcer la libération avec un using dans le 1er cas, mais pas le 2eme
    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
    for (int i = 0; i < 10; i++) {
       using(var foo = new Foo(i)) {
          foo.Bar();
       } // foo libéré
    }
     
    // la seule façon de les libérer c'est de jongler avec Dispose et la gestion d'exceptions
    Foo foo = null;
    for (int i = 0; i < 10; i++) {
       try {
          foo = new Foo(i);
          foo.Bar();
       }
       finally {
          if(foo != null) { // si foo = new Foo(i) throw, foo est null et Dispose ne peut pas (et ne doit pas) être appelée.
             foo.Dispose();
          }
          foo = null;
       }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    var foo = new Foo();
    for (int i = 0; i < 10; i++)
    {
       foo.Bar(i);
    }
    Peut être une solution, mais c'est au cas par cas.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    using(var foo = new Foo()) {
       for (int i = 0; i < 10; i++)
       {
          foo.reInit(i); // reInit contient les mêmes instructions que Dispose et que le ctor dans le pire des cas
          // et n'effectue aucunes désallocations / réallocations dans le meilleur des cas, c'est au cas par cas,
          // ça peut être une approche intéressante.
          foo.Bar();
       }
    }

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

Discussions similaires

  1. Réponses: 2
    Dernier message: 10/11/2012, 16h39
  2. [C++0x] Initializer lists et «foo f{};» vs «new foo{};»
    Par Florian Goo dans le forum Langage
    Réponses: 5
    Dernier message: 06/10/2008, 18h26
  3. [Prototype] var laRequete = new Ajax.Request(url,o_options);
    Par mkaelkael dans le forum Bibliothèques & Frameworks
    Réponses: 0
    Dernier message: 09/05/2008, 14h17

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