Optimisation des Threads (comparaison de fragments d'ADN)

Résolu/Fermé
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019 - Modifié par mikis69 le 1/11/2016 à 16:55
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019 - 4 nov. 2016 à 00:07
Bonjour à tout le monde !

J'ai un petit soucis que je n'arrive pas à résoudre.. J'espère que vous pourrez m'aider !

Je vous explique mon projet :

J'ai une liste de 1617 fragments dans un fichier .fasta et je dois comparer chaque paire de fragment entre eux (de manière normale et inversée/complémentaire)

Exemple :

Fragment 1 : ACTGCA Fragment 1 inversé/complémentaire : TGCAGT
Fragment 2 : TTGGCCGG Fragment 2 inversé/complémentaire : CCGGCCAA
Fragment 3 : GCGCTATA Fragment 3 inversé/complémentaire : TATAGCGC
Fragment 4 : CCAA Fragment 4 inversé/complémentaire : TTGG

Je dois donc aligner :

- le fragment 1 avec le fragment 2
- le fragment 1 avec le fragment 3
- le fragment 1 avec le fragment 4
- le fragment 2 avec le fragment 3
- le fragment 2 avec le fragment 4
- le fragment 3 avec le fragment 4
- le fragment 1 inversé/complémentaire avec le fragment 2
- le fragment 1 inversé/complémentaire avec le fragment 3
- le fragment 1 inversé/complémentaire avec le fragment 4
- le fragment 2 inversé/complémentaire avec le fragment 3
- le fragment 2 inversé/complémentaire avec le fragment 4
- le fragment 3 inversé/complémentaire avec le fragment 4

=> Pour un fichier de taille n, je devrais donc faire (n-1!)*2 comparaisons (ici 3! * 2 = 12)

Le soucis :

Dans mon cas j'ai un fichier avec 1617 fragments.. Ce qui veut dire que j'ai un très grand nombre de comparaisons à faire !!
J'aimerais savoir quelle manière serait la plus rapide pour les comparer.. Faire du multi-threading ? Ne pas en faire ? Si oui, combien de threads pour 1617 fragments ?

J'ai déjà essayer de faire un thread pour chaque fragment (donc un thread fait deux comparaisons : une normale et une inversée/complémentaire) mais le programme a planté car trop de thread apparemment.. J'ai ensuite essayé de faire un thread pour deux fragments (donc un thread fait 4 comparaisons) mais même résultat, le programme a planté.

Ici je suis arrivé à faire 1 thread pour 4 fragments (donc un thread fait 8 comparaisons) ce qui ne donne plus d'erreurs mais qui prend trop de temps (+ de 3h pour faire toutes les comparaisons)..

Code :

// La méthode pour aligner (pas besoin de savoir ce qu'est l'objet alignement je pense
public void alignement(Fragment f) {
  alignements.add(new Alignement(nucleobases, f.getNucleobases()));
  alignementsInverses.add(new Alignement(inverses, f.getNucleobases()));
}


// Dans mon controlleur j'utilise cette méthode
Collection c = model.getCollection();

ExecutorService execute = Executors.newFixedThreadPool((int)Math.ceil((double)c.getFragments().size()/4));
List<Future<Integer>> lists = new ArrayList<>();
  
int i = 0;
int tour = c.getFragments().size()%4 == 0 ? c.getFragments().size()/4 : c.getFragments().size() / 4 + 1;
int cpt = 0;
while(cpt < tour) {
 if(c.getFragments().size() % 4 == 0 || cpt != tour - 1) {
  lists.add(execute.submit(new AlignementCallable(c.getFragments(), i, i + 2, false)));
 } else {
  lists.add(execute.submit(new AlignementCallable(c.getFragments(), i, i + c.getFragments().size() % 4, true)));   
 }
 i += 2;
 cpt++;
}


Remarque :

Le premier thread va aligner par exemple sur une liste de 10 fragments :
- Le fragment 1 avec tous les autres; (2, 3, 4, 5, 6, 7, 8, 9, 10)
- Le fragment 2 avec tous les autres; (3, 4, 5, 6, 7, 8, 9, 10)
- Le fragment 10 avec tous les autres; (aucun en fait puisque le 10 est le dernier)
- Le fragment 9 avec tous les autres; (10)

Le deuxième :
- Le fragment 3 avec tous les autres;
- Le fragment 4 avec tous les autres;
- Le fragment 8 avec tous les autres;
- Le fragment 7 avec tous les autres;

Le troisième :
- Le fragment 5 avec tous les autres;
- Le fragment 6 avec tous les autres;

// La méthode call de AlignementCallable
 @Override
 public Integer call() throws Exception {
  if(!milieu) {
   for(int i = debut;  i < debut + 2; i++) {
    Fragment f = fragments.get(i);
    for(int j = i + 1; j < fragments.size(); j++) {
     f.alignement(fragments.get(j));
    }
    
    f = fragments.get(fragments.size() - i - 1);
    for(int j = fragments.size() - i; j < fragments.size(); j++) {
     f.alignement(fragments.get(j));
    }
   }
  } else {
   for(int i = debut; i < fin; i++) {
    Fragment f = fragments.get(i);
    for(int j = i + 1; j < fragments.size(); j++) {
     f.alignement(fragments.get(j));
    }
   }
  }
  
  return 1;
 }


Remarque : J'ai décidé d'implémenté l'interface Callable car c'est le seul moyen que j'ai trouvé pour que mon controlleur sache lorsque tous les threads ont été terminés (avec ce morceau de code)

// pour vérifier que les threads sont tous finis
try {
 int sum = 0;
 for(Future<Integer> f : lists) {
  sum += f.get();
 }
   
 if(sum == tour) { // Voir ici 
         System.out.println("On a fini de faire l'alignement semi global de toutes les paires de fragments");
 }
} catch (InterruptedException | ExecutionException e) {
 e.printStackTrace();
}
  
 execute.shutdown();


Merci pour votre aide.. Si vous avez des idées pour que le programme compare les 1617 fragments plus vite n'hésitez pas !! :)

2 réponses

KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
1 nov. 2016 à 17:35
Bonjour,

Je ne suis pas sûr d'avoir compris en quoi consistait la comparaison de deux fragments d'ADN, mais en tout cas (n-1)! c'est énorme (et faux) ici tu as n fragments d'ADN à comparer à (n-1) autres fragments, soit n(n-1) comparaisons, c'est déjà beaucoup plus accessible, d'autant qu'en réalité tu en as un peu moins puisque quand tu compares A et B tu n'as pas besoin de comparer B et A ensuite.

Bref, threads ou pas threads, thread bien sûr, mais ça ne sert à rien de faire 1000 threads, parce que ta machine a peut-être 4 ou 5 processeurs maximum, donc ça ne sert à rien d'aller au delà d'une dizaine de threads en tout, parce que sinon ils vont passer leur temps à donner la main à quelqu'un d'autre sans jamais avoir de processeur pour eux.

De plus, Java 8 permet déjà de faire ce genre d'opérations tout seul, tu n'as pas à gérer le nombre de threads, ni le Callable pour attendre le résultat final, tu mets tout ça dans un parallelStream et c'est "magique"
En pratique : Aggregate Operations et notamment la rubrique Parallelism
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019
1 nov. 2016 à 19:11
En fait, à partir de ces fragments, je dois les aligner entre eux (ce qui donne un score d'alignement) et ensuite je dois encore faire d'autres étapes pour finir mon projet !

Maintenant que tu le dis, je pense que je me suis trompé c'est plutôt n(n-1) oui .. Car le fragment 1 va générer 1616 comparaisons * 2 (puisqu'il faut tenir compte du fragment inversé/complémentaire)
Et le fragment 2 en générera 1615 * 2.. Donc oui du n(n-1) désolé !

J'avais lu sur internet que nos ordinateurs pouvaient supporter des milliers de threads pourtant.. Donc une dizaine de thread d'accord ! Je vais me renseigner sur les objet que tu m'as donné et je vais les essayer !

Un grand merci pour ta réponse :)
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019
1 nov. 2016 à 20:28
Donc l'idée serait de faire un parallelStream de la collection de fragments ?

c.getFragments().parallelStream()...........

Mais on utilise cela pour faire des opérations "simples" non ? Genre trouver le max, le min, filtrer des listes.. Mais ici j'aimerais faire pour chaque fragment de la liste :

for(int j = i + 1; j < fragments.size(); j++) { // Sans savoir si je pourrais récupérer l'indice i
f.alignement(fragments.get(j));
}

Ou bien il existe un objet ParallelStream ? Car je ne vois pas trop sinon comment utiliser ces "objets"..
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019
Modifié par mikis69 le 1/11/2016 à 23:02
c.getFragments().parallelStream().forEach(f -> { 
  for(int j = c.getFragments().indexOf(f) + 1; j < c.getFragments().size(); j++) {
   f.alignement(c.getFragments().get(j));
  }
  });


Avec ce code, tout fonctionne bien.. Dès demain, je poste le temps qu'il faut pour aligner les 1617 fragments !

Merci KX ;)
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
2 nov. 2016 à 07:35
J'avais lu sur internet que nos ordinateurs pouvaient supporter des milliers de threads pourtant
Oui, mais ils ne vont pas tous travailler en même temps, il leur faut du temps processeur, donc si tu fais 1000 processus il y en au moins 990 qui vont dormir sans rien faire pendant que les autres s'exécutent, et ça coûte (un peu) cher de changer de threads donc il vaut mieux éviter.

Remarque : à voir si tu ne peux pas paralléliser la boucle for avec un deuxième stream parce que à vue de nez ça pourrait se faire.
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019 > KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024
2 nov. 2016 à 09:20
Ah oui peut être que je peux récupérer une sous liste et ainsi faire un for each.. Je te tiens au courant !

Merci pour ces bonnes remarques !
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019
2 nov. 2016 à 13:12
Après de nombreuses tentatives, je vois que cela fonctionne sur des fichiers de 170 fragments mais sur le fichier à 1617 fragments ça plante :

Exception in thread "main" java.lang.OutOfMemoryError
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

Est-ce une fuite mémoire ou le garbage collector de java qui fait mal son travail ? :(
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019
Modifié par mikis69 le 2/11/2016 à 16:48
Pour être un peu plus précis car je viens de comprendre d'où cette erreur provient :

public void alignementSemiGlobal() {
  m = new int[f1.size() + 1][f2.size() + 1];
  Position maxPosition = calcul();

  if(maxPosition.getX() == f1.size()) { // Si le maximum est sur la ligne
   alignementLigne(f1.size(), maxPosition.getY());
  } else { // Sinon le maximum est sur la colonne
   alignementColonne(maxPosition.getX(), f2.size());
  }
  
  m = null;
 }


Pour chaque alignement, je dois créer un tableau à deux dimensions de int (pratiquement toujours 500 x 500) afin de calculer la matrice (qui sert à aligner les deux fragments). Mais pour que le GC delete la delete à chaque fois, je la remet à null lorsque j'en ai plus besoin..
Et même avec ça, j'obtiens toujours l'erreur après 1h d'execution..
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
2 nov. 2016 à 17:02
Dans ton code où est ce que tu te sers de m ? Parce que le mettre à null ne sert pas à grand chose si l'objet derrière est toujours utilisé (par un autre thread par exemple).
De plus si ton programme est voué à durer plusieurs heures il faudrait peut être enregistrer tes résultats au fur et à mesure (dans un fichier ou une base de données) plutôt que tout mettre en mémoire.
Remarque : tu peux utiliser des outils du JDK comme jVisualVM pour suivre l'évolution de ton programme en cours d'exécution (nombre de threads, mémoire alloués, avancement du GC, etc)
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019 > KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024
2 nov. 2016 à 17:15
m est juste utilisé dans les méthodes alignementLigne et alignementColonne..
Ce tableau a deux dimensions représentent une matrice en fait et n'est utiliser que dans un thread (fin chaque alignement créera sa propre matrice mais n'en aura plus besoin une fois l'alignement fait) c'est pour cela que je le met à null à la fin (j'ai lu que ca pouvait aider le garbage collector à le jeter pour libérer de la mémoire).

Le soucis c'est que j'aurais besoin de ces données et je ne pense pas que les stocker dans un fichier puisse m'aider car ça risque de compliquer énormément le code..
J'ai déjà une liste de 1617 fragments et chaque fragment possède deux listes :

private List<Alignement> alignements;
private List<Alignement> alignementsInverses;


l'objet 1 de la liste des 1617 fragments aura :

- une liste d'alignements de 1616 alignements (vu qu'il est comparé au 1616 autres)
- une liste d'alignements inverses de 1616 aussi..

l'objet 2 en aura 1615 * 2

Tu penses que java ne peut pas stocker autant de données dans sa mémoire ? C'est la première fois que je suis confronté à un projet qui manipule autant de données.. (et je ne peux pas utiliser de base de données)..
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
2 nov. 2016 à 17:28
Ce n'est pas Java le problème, en 64 bits la consommation mémoire qu'il peut théoriquement utiliser est gigantesque, mais c'est ton système d'exploitation qui va limiter la vraie mémoire utilisable.
Remarque : à partir de 6/8 Go le temps nécessaire pour faire un GC va induire des arrêts de services du programme sur des durées perceptibles (même si dans ton cas ça ne va pas changer grand chose)

Soit tu utilises une configuration mémoire par défaut insuffisante et il faut l'augmenter en précisant l'option XmX.
Soit tu es déjà effectivement au max et il faut voir si tu as une vraie fuite mémoire, avec jvisualvm tu auras une idée de la consommation réelle de ton programme et tu verras peut être une aberration.
En complément tu peux faire des hprof de temps en temps pour identifier si les objets qui consomment la mémoire sont bien ceux auxquels tu penses.
0
mikis69 Messages postés 168 Date d'inscription mardi 26 novembre 2013 Statut Membre Dernière intervention 11 février 2019 > KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024
2 nov. 2016 à 17:40
J'avais lu que l'on pouvait augmenter la mémoire mais que ça ne règle pas le problème. Et si je le fais sur ma machine, lorsque je vais rendre mon projet et que mon professeur l'executera, il va tomber sur une erreur n'est ce pas ? (vu que lui n'aura pas augmenter la taille de sa mémoire)

Je suis entrain de l'exécuter avec Java Monitor et la Used Heap Memory ne dépasse jamais 200 M
Le nombre de threads est constant : 50
Et le CPU est généralement très bas..

Je ne vois vraiment pas à quel moment il peut planter..
0