2015-06-01 3 views
3

Как повысить производительность этого простого фрагмента кода на Python? Это не re.search лучший способ найти подходящую линию, так как она почти ~ 6x медленнее, чем Perl, или я делаю что-то неправильно?Улучшение производительности кода Python

#!/usr/bin/env python 

import re 
import time 
import sys 

i=0 
j=0 
time1=time.time() 
base_register =r'DramBaseAddress\d+' 
for line in open('rndcfg.cfg'): 
    i+=1 
    if(re.search(base_register, line)): 
     j+=1 
time2=time.time() 

print (i,j) 
print (time2-time1)  
print (sys.version) 

Этот код занимает около 0,96 секунд, чтобы завершить (в среднем 10 трасс)
Выход:

168197 2688 
0.8597519397735596 
3.3.2 (default, Sep 24 2013, 15:14:17) 
[GCC 4.1.1] 

в то время как следующий код Perl, делает это в 0,15 секунды.

#!/usr/bin/env perl 
use strict; 
use warnings; 

use Time::HiRes qw(time); 

my $i=0;my $j=0; 
my $time1=time; 
open(my $fp, 'rndcfg.cfg'); 
while(<$fp>) 
{ 
    $i++; 
    if(/DramBaseAddress\d+/) 
    { 
     $j++; 
    } 
} 
close($fp); 
my $time2=time; 

printf("%d,%d\n",$i,$j); 
printf("%f\n",$time2-$time1); 
printf("%s\n",$]); 


Выход:

168197,2688 
0.135579 
5.012001 

EDIT: Исправленная регулярное выражение - Какие ухудшили показатели незначительно

+1

Вы должны предварительно скомпилировать регулярное выражение, если вы используете его несколько раз, или в этом случае используйте 'in' вместо:' if base_register in line: '. –

+1

Если вы прочитали примечание к ['re.compile'] (https://docs.python.org/3/library/re.html#re.compile), вы увидите:« Скомпилированные версии последних шаблоны, переданные в re.compile(), и функции сопоставления на уровне модуля кэшируются, поэтому программы, которые используют только несколько регулярных выражений одновременно, не должны беспокоиться о компиляции регулярных выражений ». – Matthias

ответ

5

на самом деле, регулярное выражение является менее эффективным, чем строковые методы в Python. От https://docs.python.org/2/howto/regex.html#use-string-methods:

Строка имеет несколько методов для выполнения операций с фиксированной строки, и они, как правило, гораздо быстрее, потому что реализация один небольшим C цикл, который был оптимизирован для этой цели, а не из больших , более общий механизм регулярных выражений.

замена re.search на str.find даст вам лучшее время работы. в противном случае с помощью оператора in, который другие предложили, также будет оптимизирован.

за разницы в скорости между версией Python & Perl, я просто мел это до неотъемлемое качество каждого языка: text processing - python vs perl performance

+0

Как использовать 'str.find' для регулярного выражения? – Jean

+0

oh 'str.find' не является регулярным выражением; это простая подстрока. Я просто заметил часть '\ d +' вашего регулярного выражения, поэтому это может быть не совсем применимо, но если вы хотите только подсчитать строки с '' DramBaseAddress'' в них (то есть, если конечные цифры не делают разница), то вы можете заменить 'if (re.search (base_register, line)):' либо с помощью if if DramBaseAddress в строке: 'или' if line.find ("DramBaseAddress")> -1: '. Однако, если важные цифры важны, тогда ваш лучший выбор - ответ @ Veedrac ниже. – oxymor0n

1

В этом случае вы используете фиксированную строку, а не регулярное выражение ,

Для обычных струн существуют более быстрые методы:

>>> timeit.timeit('re.search(regexp, "banana")', setup = "import re;  regexp=r'nan'") 
1.2156920433044434 
>>> timeit.timeit('"banana".index("nan")') 
0.23752403259277344 
>>> timeit.timeit('"banana".find("nan")') 
0.2411658763885498 

Сейчас этот вид обработки текста является сладкое пятно Perl (ака извлечения и отчетности язык) (ака Патологическая Эклектичный Мусорный Листер) и оптимизирована в течение многих лет. Все это объединяет коллективный фокус.

1

Накладные расходы re.compile, несмотря на кэширование, являются массовыми. Использовать

is_wanted_line = re.compile(r"DramBaseAddress\d+").search 

for i, line in enumerate(open('rndcfg.cfg')): 
    if is_wanted_line(line): 
     j += 1 

вместо этого.

Кроме того, вы можете сделать

key = "DramBaseAddress" 
is_wanted_line = re.compile(r"DramBaseAddress\d+").search 

for i, line in enumerate(open('rndcfg.cfg')): 
    if key in line and is_wanted_line(line): 
     j += 1 

для дальнейшего сокращения накладных расходов.

Вы также можете рассмотреть возможность сделать свою собственную буферизацию:

key = b"DramBaseAddress" 
is_wanted_line = re.compile(rb"DramBaseAddress\d+").search 

with open("rndcfg.cfg", "rb") as file: 
    rest = b"" 

    for chunk in iter(lambda: file.read(32768), b""): 
     i += chunk.count(b"\n") 
     chunk, _, rest = (rest + chunk).rpartition(b"\n") 

     if key in rest and is_wanted_line(chunk): 
      j += 1 

    if key in rest and is_wanted_line(rest): 
     j += 1 

который удаляет строки расщепления и кодирование над головой. (Это не совсем то же самое, что в нем не учитывается несколько экземпляров на кусок. Такое поведение относительно просто добавить, но не обязательно строго необходимо в вашем случае.)

Это немного тяжеловес, но три раза быстрее, чем Perl - 8x, если вы удалите i += chunk.count(b"\n")!

+0

есть ли у вас какой-либо тест, подтверждающий требование «Накладные расходы на вызов re.compile, несмотря на кэширование, являются массовыми».? Я использую много 're.compile' lol – oxymor0n

+1

@ oxymor0n Я приурочил его. Это заняло почти 2/3 времени. Обратите внимание, что для одноразовых использования или поиска длинных строк накладные расходы, скорее всего, будут амортизированы намного лучше. – Veedrac

+0

интересно, спасибо. Я всегда предполагаю, что производительность кэширования будет лучше: / – oxymor0n