2010-11-16 25 views
3

Après avoir essayé d'importer la bibliothèque d'exécution Java de base rt.jar avec language-java-classfile, j'ai découvert qu'elle utilisait d'énormes quantités de mémoire.Forçage du traitement séquentiel dans Data.Binary.Get de Haskell

J'ai réduit le programme démontrant le problème à 100 lignes et l'ai téléchargé à hpaste. Sans forcer l'évaluation de stream à la ligne # 94, je n'ai aucune chance de l'exécuter parce qu'elle me dévore toute la mémoire. Forcing stream avant de passer à getClass finitions, mais utilise encore d'énormes quantités de mémoire:

34,302,587,664 bytes allocated in the heap 
    32,583,990,728 bytes copied during GC 
    139,810,024 bytes maximum residency (398 sample(s)) 
     29,142,240 bytes maximum slop 
      281 MB total memory in use (4 MB lost due to fragmentation) 

    Generation 0: 64992 collections,  0 parallel, 38.07s, 37.94s elapsed 
    Generation 1: 398 collections,  0 parallel, 25.87s, 27.78s elapsed 

    INIT time 0.01s ( 0.00s elapsed) 
    MUT time 37.22s (36.85s elapsed) 
    GC time 63.94s (65.72s elapsed) 
    RP time 0.00s ( 0.00s elapsed) 
    PROF time 13.00s (13.18s elapsed) 
    EXIT time 0.00s ( 0.00s elapsed) 
    Total time 114.17s (115.76s elapsed) 

    %GC time  56.0% (56.8% elapsed) 

    Alloc rate 921,369,531 bytes per MUT second 

    Productivity 32.6% of total user, 32.2% of total elapsed 

Je pensais que le problème était le ConstTable s restant autour, donc j'ai essayé de forcer cls en ligne # 94 ainsi. Mais cela ne fait que la consommation de mémoire et le temps d'exécution pire:

34,300,700,520 bytes allocated in the heap 
    23,579,794,624 bytes copied during GC 
    487,798,904 bytes maximum residency (423 sample(s)) 
     36,312,104 bytes maximum slop 
      554 MB total memory in use (10 MB lost due to fragmentation) 

    Generation 0: 64983 collections,  0 parallel, 71.19s, 71.48s elapsed 
    Generation 1: 423 collections,  0 parallel, 344.74s, 353.01s elapsed 

    INIT time 0.01s ( 0.00s elapsed) 
    MUT time 40.60s (42.38s elapsed) 
    GC time 415.93s (424.49s elapsed) 
    RP time 0.00s ( 0.00s elapsed) 
    PROF time 56.53s (57.71s elapsed) 
    EXIT time 0.00s ( 0.00s elapsed) 
    Total time 513.07s (524.58s elapsed) 

    %GC time  81.1% (80.9% elapsed) 

    Alloc rate 844,636,801 bytes per MUT second 

    Productivity 7.9% of total user, 7.7% of total elapsed 

Donc, ma question est fondamentalement, comment puis-je forcer le traitement séquentiel des fichiers concernés, de sorte qu'après chacun est traité, seul le résultat de la chaîne (cls) reste en mémoire?

+0

Que diriez-vous certains profils de tas et postez ce graphique dans votre question. –

+0

J'ai ajouté la sortie de profilage de tas pour les deux versions à hpaste. – Cactus

+0

Avez-vous essayé d'utiliser le paquet 'cereal' à la place? Juste une pensée, mais strictes bytestrings aident souvent si vous savez que le fichier est assez petit. –

Répondre

2

Edit 2: Je viens de réaliser votre code fait ceci:

stream <- BL.pack <$> fileContents [] classfile 

Ne fais pas ça. Les fonctions pack sont notoirement lentes. Vous devrez trouver une solution qui n'implique pas l'utilisation de pack pour créer un ByteString.

Je laisse le reste de ma réponse parce que je pense toujours qu'elle s'applique, mais c'est certainement le plus gros problème.

Malheureusement, je ne peux pas tester cela parce que je ne reconnais pas toutes vos importations.

Si vous voulez seulement que le résultat cls reste en mémoire, pourquoi ne pas le forcer au lieu de forcer le flux? Modifier la ligne 94 à

cls `seq` return cls 

Il peut être nécessaire d'utiliser deepseq au lieu de simplement seq, bien que je soupçonne cette plaine seq sera suffisant ici.

Cependant, je pense qu'il y a une meilleure solution, et c'est d'utiliser mapM_ au lieu de mapM. Je pense que c'est généralement un meilleur style (et presque toujours une meilleure performance) pour créer une fonction qui fait ce qu'elle est censée faire avec chaque résultat plutôt que de retourner une liste. Ici, vous pouvez changer votre fonction principale:

main = do 
    withArchive [CheckConsFlag] jarPath $ do 
    classfiles <- filter isClassfile <$> fileNames [] 
    forM_ classfiles $ \classfile -> do 
     stream <- BL.pack <$> fileContents [] classfile 
     let cls = runGet getClass stream 
     lift $ print cls 

Maintenant, le print est soulevé dans la fonction passée à forM_ pour chaque ClassFile. La valeur cls est utilisée en interne et n'a jamais été renvoyée, elle est donc évaluée à la fois et rapidement GC'd à chaque itération de forM_. L'utilisation de ce style dans une application plus grande peut nécessiter un refactoring ou même une refonte, mais les résultats peuvent en valoir la peine. Editer: Si vous avez du mal à reconcevoir votre code, vous pouvez utiliser iteratees et éviter complètement ce problème.

+0

Mais dans le vrai programme, mon résultat serait la liste des cls. En outre, la deuxième sortie est du flux 'seq' cls' seq' renvoie cls (désolé si je n'étais pas assez clair) – Cactus

+0

@Cactus, désolé mais je viens d'ajouter une note au début de ma réponse, vérifiez cela. –

+0

Aussi, je sais que votre résultat est la liste des cls, mais je pense que vous feriez mieux de créer une fonction qui a fait quelque chose avec chaque cls plutôt que d'en créer une liste. Mais je ne pense pas que ce soit le problème le plus important. –

0

Merci pour les suggestions.

Je pense que pour mon problème concret, la solution sera de traiter les fichiers .jar en petits morceaux - heureusement, les classes internes sont toujours dans le même répertoire dans le fichier .jar que leur classe externe, donc pas besoin pour traiter tous les 50 Mo en une seule fois.

La seule chose que je n'arrive pas à comprendre est s'il est possible d'utiliser libzip via des énumérateurs, ou cela aurait-il besoin d'une nouvelle implémentation de libzip?

1

Votre idée de forcer l'évaluation de cls dans la ligne 94 était correcte. Mais je suppose que vous avez l'approche de le faire n'a pas été un succès. Voir ce paste pour ma version, qui fonctionne dans ca. 40 Mo au lieu de 220 Mo. La clé est de forcer la réduction à la forme normale de cls, ce qui est fait par les cls rnf

Et cela doit arriver avant l'appel pour revenir. Par conséquent:

RNF cls `seq` retour cls

Sinon, vous pouvez utiliser Control.Exception.evaluate: $ RNF évaluer cls retour cls