{"id":3845,"date":"2017-12-11T12:56:26","date_gmt":"2017-12-11T17:56:26","guid":{"rendered":"http:\/\/bioinfo.iric.ca\/?p=3845"},"modified":"2017-12-11T12:56:26","modified_gmt":"2017-12-11T17:56:26","slug":"un-exemple-utilisant-multiprocessing-et-plus","status":"publish","type":"post","link":"https:\/\/bioinfo.iric.ca\/fr\/un-exemple-utilisant-multiprocessing-et-plus\/","title":{"rendered":"Un exemple utilisant multiprocessing et plus"},"content":{"rendered":"<p>R\u00e9cemment, j&rsquo;ai eu \u00e0 chercher une structure chimique donn\u00e9e dans une liste de structures. En utilisant les librairies python de chimie informatique <a href=\"https:\/\/openbabel.org\/docs\/dev\/UseTheLibrary\/Python_Pybel.html\">pybel<\/a> et <a href=\"http:\/\/www.rdkit.org\/\">rdkit<\/a>, je suis facilement arriv\u00e9e \u00e0 faire cette recherche, mais celle-ci prenait beaucup trop de temps \u00e0 mon go\u00fbt. En me demandant comment l&rsquo;acc\u00e9l\u00e9rer, je me suis souvenue de l&rsquo;article de blog de Jean-Philippe intitul\u00e9 <a href=\"https:\/\/bioinfo.iric.ca\/fr\/faites-travailler-vos-cpus\/\">\u00ab\u00a0Faites travailler vos CPUs !\u00a0\u00bb<\/a>. J&rsquo;ai donc d\u00e9cid\u00e9 de suivre ses instructions et de faire travailler mes CPUs!<\/p>\n<p><strong> But <\/strong><\/p>\n<p>Trouver une mol\u00e9cule (une structure chimique donn\u00e9e) dans une liste de mol\u00e9cules.<\/p>\n<p><strong> Impl\u00e9mentation s\u00e9quentielle <\/strong><\/p>\n<p>Notez que pour le pr\u00e9sent projet, ma liste de mol\u00e9cules contient des objets pybel alors que ma requ\u00eate est un objet rdkit. Sans cette contrainte, je n&rsquo;aurais pas n\u00e9cessairement utilis\u00e9 les deux librairies: mon code aurait \u00e9t\u00e9 \u00e9crit en choisissant l&rsquo;une ou l&rsquo;autre.<\/p>\n<pre><code class=\"python\">\r\ndef exactSearch (q, start, end) : \r\n   ''' Chercher une mol\u00e9cule dans une liste de mol\u00e9cules'''\r\n   ''' Retourne le nombre de fois que la mol\u00e9cule a \u00e9t\u00e9 trouv\u00e9e'''\r\n\r\n\r\n    matches = []\r\n    for mpybel in molecules[start, end]:  ## mol\u00e9cules pybel \r\n        fp1 = mpybel.calcfp()  ## pybel\r\n        smi2 = Chem.MolToSmiles(q, isomericSmiles=True)  # mol\u00e9cule rdkit\r\n        mpybel2 = pybel.readstring(\"smi\", smi2 ) # convertit la mol\u00e9cule rdkit en mol\u00e9cule pybel\r\n        if mpybel.formula == mpybel2.formula : \r\n            fp2 = mpybel2.calcfp()\r\n            tanimoto = fp1 | fp2\r\n\r\n            if tanimoto == 1:          \r\n               matches.append(mpybel)               \r\n    return len(matches)\r\n\r\nn_matches = exactSearch (query, 0, len(molecules))\r\n<\/code><\/pre>\n<p>Ma fonction prend 29.8 secondes pour trouver les trois occurrences de ma requ\u00eate parmi les 25791 mol\u00e9cules de la liste. Je suis patiente, mais pas assez pour attendre 30 secondes chaque fois que je dois chercher une mol\u00e9cule!<\/p>\n<p><strong> Impl\u00e9mentation avec multiprocessing <\/strong><\/p>\n<p>La librairie python multiprocessing permet d&rsquo;utiliser plus d&rsquo;un CPU pour faire le travail. Pour ce faire, je n&rsquo;ai que deux modifications \u00e0 faire \u00e0 mon code : (1) je dois cr\u00e9er une fonction \u00ab\u00a0wrapper\u00a0\u00bb et (2) d\u00e9finir les t\u00e2ches \u00e0 ex\u00e9cuter en parall\u00e8le.<\/p>\n<pre><code class=\"python\">\r\nimport multiprocessing\r\ndef exactSearch_wrapper (args) : \r\n    return exactSearch(*args)\r\n\r\ndef exactSearch (q, start, end) : \r\n   ... # same function as above\r\n\r\np = multiprocessing.Pool (processes=4)\r\ntasks = [(query, i, i+1000]) for i in range(0, len(molecules), 1000)]\r\nsearches = p.map_async(exactSearch_wrapper, tasks)\r\np.close()\r\np.join()\r\n<\/code><\/pre>\n<p>Ce code trouve les trois occurrences de ma requ\u00eate parmi ma liste de mol\u00e9cules. Toutefois, comme j&rsquo;ai divis\u00e9 la recherche en plusieurs recherches dans des listes de 1000 mol\u00e9cules et que j&rsquo;utilise 4 CPUs, la recherche prend maintenant 7.8 secondes. J&rsquo;ai pu diviser ma t\u00e2che principale en sous-t\u00e2ches puisque les comparaisons de ma requ\u00eate aux mol\u00e9cules de la liste sont des op\u00e9rations ind\u00e9pendantes qui peuvent se faire en parall\u00e8le.<\/p>\n<p><strong> Derni\u00e8res remarques <\/strong><\/p>\n<p>D&rsquo;autres options m&rsquo;auraient permis d&rsquo;acc\u00e9l\u00e9rer ma recherche. Cependant, mon but ici \u00e9tait de pr\u00e9senter un exemple simple (esp\u00e9rons!) d&rsquo;utilisation de la librairie multiprocessing.<\/p>\n<p>Parmi ces autres options, il y a l&rsquo;utilisation d&rsquo;un \u00ab\u00a0profileur\u00a0\u00bb de code (\u00ab\u00a0code profiler\u00a0\u00bb) pour identifier quelles op\u00e9rations sont co\u00fbteuses en terme de temps et voir s&rsquo;il est possible d&rsquo;apporter des modifications dans le but d&rsquo;optimiser le code.<br \/>\nVoici le r\u00e9sultat de profilage que j&rsquo;ai obtenu pour ma fonction exactSearch :<\/p>\n<pre>Timer unit: 1e-06 s\r\n\r\nTotal time: 28.8646 s\r\nFile: \r\nFunction: exactSearch at line 1\r\n\r\nLine #      Hits         Time  Per Hit   % Time  Line Contents\r\n==============================================================\r\n     1                                           def exactSearch0 (q, mols) : \r\n     2                                              \r\n     3         1            1      1.0      0.0      matches = []\r\n     4                                           \r\n     5     25792        16267      0.6      0.1      for mpybel in mols:  ## pybel molecules\r\n     6     25791     13097301    507.8     45.4          fp1 = mpybel.calcfp()  ## pybel\r\n     7                                                   \r\n     8                                               \r\n     9     25791      1043002     40.4      3.6          smi2 = Chem.MolToSmiles(q, isomericSmiles=True)\r\n    10     25791     14247872    552.4     49.4          mpybel2 = pybel.readstring(\"smi\", smi2 )\r\n    11                                           \r\n    12     25791       459966     17.8      1.6          if mpybel.formula == mpybel2.formula : \r\n    13                                                       \r\n    14                                           \r\n    15         3          151     50.3      0.0              fp2 = mpybel2.calcfp()\r\n    16         3           13      4.3      0.0              tanimoto = fp1 | fp2\r\n    17                                           \r\n    18         3            3      1.0      0.0              if tanimoto == 1: \r\n    19         3            2      0.7      0.0                  matches.append(mpybel)\r\n    20                                                           \r\n    21         1           67     67.0      0.0      print len(matches)   \r\n    22         1            1      1.0      0.0      return matches\r\n\r\n<\/pre>\n<p>Je peux tout de suite voir que les op\u00e9rations co\u00fbteuses sont la g\u00e9n\u00e9ration du <em>fingerprint<\/em> fp1 pour chacune des mol\u00e9cules de la liste ainsi que la conversion de la mol\u00e9cule de la requ\u00eate. Ces deux op\u00e9rations sont pr\u00e9sentement effectu\u00e9es 25791 fois et prennent environ 94% des 30 secondes n\u00e9cessaires pour l&rsquo;ex\u00e9cution. La seconde op\u00e9ration n&rsquo;a clairement pas sa place dans la boucle puisque je n&rsquo;ai besoin de la faire qu&rsquo;une seule fois. Pour ce qui est de la g\u00e9n\u00e9ration de fp1, comme je fais une recherche exacte, je n&rsquo;en ai besoin que si les formules chimiques des mol\u00e9cules compar\u00e9es sont identiques. Si elles ne le sont pas, je sais que ce sont deux mol\u00e9cules diff\u00e9rentes. Je n&rsquo;ai donc pas besoin de calculer fp1 pour les 25791 mol\u00e9cules. L&rsquo;optimisation du code de ma fonction, suite \u00e0 cette inspection, r\u00e9duit son temps d&rsquo;ex\u00e9cution de 29.8 secondes \u00e0 0.137 secondes. Dans ce cas-ci, il n&rsquo;y a aucun gain \u00e0 utiliser plus d&rsquo;un CPU: \u00e7a pourrait m\u00eame prendre plus de temps!<\/p>\n<p>0.137 secondes, c&rsquo;est beaucoup plus raisonnable!!<\/p>\n<p>Voici le code optimis\u00e9 de ma fonction de recherche :<\/p>\n<pre><code class=\"python\">\r\ndef exactSearch (q, start, end) : \r\n    matches = []\r\n    smi2 = Chem.MolToSmiles(q, isomericSmiles=True)\r\n    mpybel2 = pybel.readstring(\"smi\", smi2 )\r\n    for mpybel in molecules[start:end]: \r\n        \r\n        if mpybel.formula == mpybel2.formula : \r\n            fp1 = mpybel.calcfp()  \r\n            fp2 = mpybel2.calcfp()\r\n            tanimoto = fp1 | fp2\r\n\r\n            if tanimoto == 1: \r\n                matches.append(mpybel)\r\n                \r\n    return len(matches) \r\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>R\u00e9cemment, j&rsquo;ai eu \u00e0 chercher une structure chimique donn\u00e9e dans une liste de structures. En utilisant les librairies python de chimie informatique pybel et rdkit, je suis facilement arriv\u00e9e \u00e0 faire cette recherche, mais celle-ci prenait beaucup trop de temps \u00e0 mon go\u00fbt. En me demandant comment l&rsquo;acc\u00e9l\u00e9rer, je me suis souvenue de l&rsquo;article de blog de Jean-Philippe intitul\u00e9 \u00ab\u00a0Faites travailler vos CPUs !\u00a0\u00bb. J&rsquo;ai donc d\u00e9cid\u00e9 de suivre ses instructions et de faire travailler mes CPUs! But Trouver une <a href=\"https:\/\/bioinfo.iric.ca\/fr\/un-exemple-utilisant-multiprocessing-et-plus\/\"> [&#8230;]<\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[41,160,32],"tags":[],"class_list":["post-3845","post","type-post","status-publish","format-standard","hentry","category-bioinformatique","category-informatique-fr","category-performance-fr-2"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts\/3845","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/comments?post=3845"}],"version-history":[{"count":9,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts\/3845\/revisions"}],"predecessor-version":[{"id":3864,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/posts\/3845\/revisions\/3864"}],"wp:attachment":[{"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/media?parent=3845"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/categories?post=3845"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bioinfo.iric.ca\/fr\/wp-json\/wp\/v2\/tags?post=3845"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}