<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet title="XSL formatting" type="text/xsl" href="http://www.bonz.org/tech/feed/rss2/xslt" ?><rss version="2.0"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
  <title>ZenTech - timeit</title>
  <link>http://www.bonz.org/tech/</link>
  <description>Om Mani Padme Hum</description>
  <language>fr</language>
  <pubDate>Mon, 10 Nov 2008 00:41:06 +0100</pubDate>
  <copyright></copyright>
  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  <generator>Dotclear</generator>
  
    
  <item>
    <title>Qui est le plus fort entre l'éléphant et l'hippopotame</title>
    <link>http://www.bonz.org/tech/post/2008/04/01/Qui-est-le-plus-fort-entre-lelephant-et-lhippopotame</link>
    <guid isPermaLink="false">urn:md5:680d04d6d6bc409ad11e407ba1a7f22f</guid>
    <pubDate>Wed, 02 Apr 2008 15:31:00 +0200</pubDate>
    <dc:creator>hr</dc:creator>
        <category>code</category>
        <category>performance</category><category>python</category><category>regexp</category><category>timeit</category>    
    <description>&lt;p&gt;Je me pose toujours des questions de performance lorsque je développe. Il y a souvent plusieurs façons de rêgler un problème et certaines plus consommatrices de ressources que d'autres. Dans le cas qui m'intéresse, je me questionne sur la performance des expressions régulières en regard d'autres méthodes de traitement de chaîne. Je dois effectuer un traitement sur un très grand nombre de lignes de log au fur et à mesure de leur émission en &lt;code&gt;syslog&lt;/code&gt;.&lt;/p&gt;    &lt;p&gt;La plupart du temps avec python, avoir une idée innovante est modérée par l'existence d'un module qui couvre déjà le sujet. Dans ce cas, le module &lt;code&gt;&lt;a href=&quot;http://docs.python.org/lib/module-timeit.html&quot; hreflang=&quot;en&quot;&gt;timeit&lt;/a&gt;&lt;/code&gt; va me permettre de tester la rapidité d'exécution de différents bouts de code.&lt;/p&gt;


&lt;p&gt;Le format d'une ligne de log syslog est connu et décrit par la &lt;a href=&quot;http://www.ietf.org/rfc/rfc3164.txt&quot; hreflang=&quot;en&quot;&gt;RFC3164&lt;/a&gt;, ce format me permet différentes approches pour en extraire différents champs. Dans mon cas, je vais extraire le champ appelé &lt;code&gt;HEADER&lt;/code&gt; de différentes façons. Un ligne de log syslog doit être de la forme&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
&amp;lt;     &amp;gt;TIMESTAMP HOSTNAME TAG CONTENT
| PRI |      HEADER      |    MSG
&lt;/pre&gt;

&lt;p&gt;Dans le code python ci-dessous, les trois fonctions testées sont&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;split()&lt;/code&gt; qui récupère le champ header en effectuant un split sur le caractère espace et en réunissant les champs résultants.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;match()&lt;/code&gt; effectue une sélection par expression régulière.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;match_full()&lt;/code&gt; effectue une capture par expression régulière et occurence nommée.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il est important de bien faire attention au contexte et à la portée des variables et fonctions (&quot;scope&quot; et &quot;namespace&quot;) lorsqu'on utilise &lt;code&gt;timeit&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;
import timeit

def split():
    str = '&amp;lt;22&amp;gt;Mar 27 16:13:35 192.168.1.1 mx-daemon[2754]: m2RFDTbw002754: from=&amp;lt;j.doe@nowhere.com&amp;gt;, size=3356, class=0, nrcpts=1, msgid=&amp;lt;000701c8902d$04cb7356$879fa6b4@epwlhnj&amp;gt;, proto=ESMTP, daemon=MX, relay=[192.168.1.2]'
    ' '.join(str.split()[0:4]).split('&amp;gt;')[1]

def match():
    import re
    pattern = r'^&amp;lt;(\d+)&amp;gt;(\w{3} (?: |\d)\d \d\d:\d\d:\d\d [\w:.]+) (\w+.+)'
    _pattern = re.compile(pattern)
    str = '&amp;lt;22&amp;gt;Mar 27 16:13:35 192.168.1.1 mx-daemon[2754]: m2RFDTbw002754: from=&amp;lt;j.doe@nowhere.com&amp;gt;, size=3356, class=0, nrcpts=1, msgid=&amp;lt;000701c8902d$04cb7356$879fa6b4@epwlhnj&amp;gt;, proto=ESMTP, daemon=MX, relay=[192.168.1.2]'
    _pattern.search(str).group(2)

def match_full():
    import re
    pattern_full = r'^&amp;lt;(?P&amp;lt;pri&amp;gt;\d+)&amp;gt;(?P&amp;lt;header&amp;gt;\w{3} (?: |\d)\d \d\d:\d\d:\d\d [\w:.]+) (?P&amp;lt;msg&amp;gt;\w+.+)'
    _pattern_full = re.compile(pattern_full)
    str = '&amp;lt;22&amp;gt;Mar 27 16:13:35 192.168.1.1 mx-daemon[2754]: m2RFDTbw002754: from=&amp;lt;j.doe@nowhere.com&amp;gt;, size=3356, class=0, nrcpts=1, msgid=&amp;lt;000701c8902d$04cb7356$879fa6b4@epwlhnj&amp;gt;, proto=ESMTP, daemon=MX, relay=[192.168.1.2]'
    _pattern_full.search(str).groupdict()['header']

print &amp;quot;split:&amp;quot;
print timeit.Timer(&amp;quot;split()&amp;quot;, &amp;quot;from __main__ import split&amp;quot;).timeit()
print &amp;quot;match:&amp;quot;
print timeit.Timer(&amp;quot;match()&amp;quot;, &amp;quot;from __main__ import match&amp;quot;).timeit()
print &amp;quot;match_full:&amp;quot;
print timeit.Timer(&amp;quot;match_full()&amp;quot;, &amp;quot;from __main__ import match_full&amp;quot;).timeit()
&lt;/pre&gt;

&lt;p&gt;Ce qui donne après exécution&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
$ python stress.py
split:
6.14724898338
match:
14.80702281
match_full:
19.0696430206
&lt;/pre&gt;


&lt;p&gt;En y réfléchissant bien, les fonctions testées ne sont pas vraiment optimisées et ne sont pas donc comparables en vitesse d'exécution. Pour vraiment jouer sur un pied d'égalité, je procède aux modifications suivantes&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Import du module re en début de script, ça fonctionne finalement, j'avais peur que les problèmes de contexte de &lt;code&gt;timeit&lt;/code&gt; ne posent problème.&lt;/li&gt;
&lt;li&gt;La compilation des expressions régulières est faite une fois pour toute et pas à chaque lancement des fonctions &lt;code&gt;cmatch()&lt;/code&gt; et &lt;code&gt;cmatch_full()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Introduction de deux nouvelles fonctions effectuant une capture par expression régulière sans compilation de l'expression.&lt;/li&gt;
&lt;li&gt;Utilisation de la méthode &lt;code&gt;repeat()&lt;/code&gt; plutôt que &lt;code&gt;timeit()&lt;/code&gt; pour essayer d'obtenir un résultat moins influencé par le fonctionnement du système.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;
import re, timeit

str='&amp;lt;22&amp;gt;Mar 27 16:13:35 192.168.1.1 mx-daemon[2754]: m2RFDTbw002754: from=&amp;lt;j.doe@nowhere.com&amp;gt;, size=3356, class=0, nrcpts=1, msgid=&amp;lt;000701c8902d$04cb7356$879fa6b4@epwlhnj&amp;gt;, proto=ESMTP, daemon=MX, relay=[192.168.1.2]'

split_init = &amp;quot;&amp;quot;&amp;quot;from __main__ import split&amp;quot;&amp;quot;&amp;quot;
match_init = &amp;quot;&amp;quot;&amp;quot;from __main__ import match&amp;quot;&amp;quot;&amp;quot;
match_full_init = &amp;quot;&amp;quot;&amp;quot;from __main__ import match_full&amp;quot;&amp;quot;&amp;quot;
cmatch_init = &amp;quot;&amp;quot;&amp;quot;from __main__ import cmatch&amp;quot;&amp;quot;&amp;quot;
cmatch_full_init = &amp;quot;&amp;quot;&amp;quot;from __main__ import cmatch_full&amp;quot;&amp;quot;&amp;quot;

pattern = r'^&amp;lt;(\d+)&amp;gt;(\w{3} (?: |\d)\d \d\d:\d\d:\d\d [\w:.]+) (\w+.+)'
pattern_full = r'^&amp;lt;(?P&amp;lt;pri&amp;gt;\d+)&amp;gt;(?P&amp;lt;header&amp;gt;\w{3} (?: |\d)\d \d\d:\d\d:\d\d [\w:.]+) (?P&amp;lt;msg&amp;gt;\w+.+)'
_pattern = re.compile(pattern)
_pattern_full = re.compile(pattern_full)

def split():
    ' '.join(str.split(' ')[0:4]).split('&amp;gt;')[1]

def match():
    re.search(pattern, str).group(2)

def match_full():
    re.search(pattern_full, str).groupdict()['header']

def cmatch():
    _pattern.search(str).group(2)

def cmatch_full():
    _pattern_full.search(str).groupdict()['header']

print &amp;quot;Testing %d occurences of each match, %d times&amp;quot; % (timeit.default_number, timeit.default_repeat)
split_result = timeit.Timer(&amp;quot;split()&amp;quot;, split_init).repeat()
print &amp;quot;split: %fs %s&amp;quot; % (min(split_result), split_result)
match_result = timeit.Timer(&amp;quot;match()&amp;quot;, match_init).repeat()
print &amp;quot;match: %fs %s&amp;quot; % (min(match_result), match_result)
match_full_result = timeit.Timer(&amp;quot;match_full()&amp;quot;, match_full_init).repeat()
print &amp;quot;match_full: %fs %s&amp;quot; % (min(match_full_result), match_full_result)
cmatch_result = timeit.Timer(&amp;quot;cmatch()&amp;quot;, cmatch_init).repeat()
print &amp;quot;cmatch: %fs %s&amp;quot; % (min(cmatch_result), cmatch_result)
cmatch_full_result = timeit.Timer(&amp;quot;cmatch_full()&amp;quot;, cmatch_full_init).repeat()
print &amp;quot;cmatch_full: %fs %s&amp;quot; % (min(cmatch_full_result), cmatch_full_result)
&lt;/pre&gt;


&lt;p&gt;Qui me donne les résultats suivants&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
$ python stress.py
Testing 1000000 occurences of each match, 3 times
split: 5.818318s [5.8183181285858154, 6.5981872081756592, 6.327794075012207]
match: 8.220056s [8.2262570858001709, 8.2361149787902832, 8.2200560569763184]
match_full: 12.399510s [12.40582799911499, 12.399509906768799, 12.501888036727905]
cmatch: 3.818037s [3.8180370330810547, 3.8276369571685791, 3.8310809135437012]
cmatch_full: 6.724747s [6.7247469425201416, 6.7392361164093018, 6.7564170360565186]
&lt;/pre&gt;

&lt;p&gt;Il est surprenant de constater la différence énorme entre les expressions régulières compilées et non compilées, l'avantage de compiler ses expressions dans le cas d'une utilisation de masse est évident. La rapidité de &lt;code&gt;split()&lt;/code&gt; est plutôt décevante par rapport à l'utilisation des expressions régullières.&lt;/p&gt;


&lt;p&gt;Ces tests sont intéressant et donnent des indications de performance mais ne remplacent pas des tests complet de &lt;a href=&quot;http://docs.python.org/lib/profile.html&quot; hreflang=&quot;en&quot;&gt;profilage&lt;/a&gt;.&lt;/p&gt;</description>
    
    
    
      </item>
    
</channel>
</rss>