Обробка тексту - Python проти продуктивності Perl [закрито]


74

Ось мій сценарій Perl та Python, щоб виконати просту обробку тексту з приблизно 21 файлу журналу, кожен приблизно від 300 КБ до 1 МБ (максимум) x 5 разів (загалом 125 файлів, через те, що журнал повторюється 5 разів).

Код Python (код, модифікований для компіляції reта використання re.I)

#!/usr/bin/python

import re
import fileinput

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for line in fileinput.input():
    fn = fileinput.filename()
    currline = line.rstrip()

    mprev = exists_re.search(currline)

    if(mprev):
        xlogtime = mprev.group(1)

    mcurr = location_re.search(currline)

    if(mcurr):
        print fn, xlogtime, mcurr.group(1)

Код Perl

#!/usr/bin/perl

while (<>) {
    chomp;

    if (m/^(.*?) INFO.*Such a record already exists/i) {
        $xlogtime = $1;
    }

    if (m/^AwbLocation (.*?) insert into/i) {
        print "$ARGV $xlogtime $1\n";
    }
}

І на моєму ПК обидва коди генерують абсолютно однаковий файл результатів із 10 790 рядків. І ось час, зроблений для реалізації Cygwin Perl та Python.

User@UserHP /cygdrive/d/tmp/Clipboard
# time /tmp/scripts/python/afs/process_file.py *log* *log* *log* *log* *log* >
summarypy.log

real    0m8.185s
user    0m8.018s
sys     0m0.092s

User@UserHP /cygdrive/d/tmp/Clipboard
# time /tmp/scripts/python/afs/process_file.pl *log* *log* *log* *log* *log* >
summarypl.log

real    0m1.481s
user    0m1.294s
sys     0m0.124s

Спочатку на цю просту обробку тексту потрібно було 10,2 секунди за допомогою Python і лише 1,9 секунди за допомогою Perl.

(UPDATE), але після скомпільованої reверсії Python тепер це займає 8,2 секунди в Python і 1,5 секунди в Perl. Все ж Perl набагато швидший.

Чи є спосіб взагалі покращити швидкість Python АБО очевидно, що Perl буде швидким для простої обробки тексту.

До речі, це був не єдиний тест, який я робив для простої обробки тексту ... І, по-різному, як я роблю вихідний код, Perl завжди завжди виграє з великим відривом. І жодного разу Python не виступав ефективніше для простого m/regex/збігу та друку.

Будь ласка, не пропонуйте використовувати C, C ++, Assembly, інші смаки Python тощо.

Я шукаю рішення із використанням стандартного Python із вбудованими модулями порівняно зі стандартним Perl (навіть не використовуючи модулі). Хлопче, я хочу використовувати Python для всіх своїх завдань завдяки його читабельності, але, щоб відмовитись від швидкості, я не думаю.

Тож, будь ласка, підкажіть, як можна вдосконалити код, щоб отримати порівнянні результати з Perl.

ОНОВЛЕННЯ: 18.10.2012

Як припускали інші користувачі, Perl має своє місце, а Python - своє.

Отже, для цього питання можна впевнено зробити висновок, що для простого збігу регулярних виразів у кожному рядку для сотень або тисяч текстових файлів та запису результатів у файл (або друку на екран) Perl завжди, завжди ПЕРЕМОЖЕ у виконанні цієї роботи . Це так просто.

Зверніть увагу, що коли я кажу, що Perl виграє у продуктивності ... порівнюються лише стандартні Perl і Python ... не вдаючись до деяких незрозумілих модулів (незрозумілих для звичайного користувача, як я), а також не викликаючи C, C ++, бібліотеки збірки з Python або Perl. У нас немає часу, щоб вивчити всі ці додаткові кроки та встановлення для простої роботи зі збору тексту.

Отже, Perl скелі для обробки тексту та регулярних виразів.

Python має своє місце для розгойдування в інших місцях.

Оновлення 2013-05-29: відмінна стаття , яка робить таке порівняння тут . Perl знову виграє за просте узгодження тексту ... А для деталей прочитайте статтю.


Чи шаблони компілюються лише один раз у Python (як у Perl)?
ikegami

1
Цікаво, чи різниця в часі, витраченому на зворотне відстеження в рядках, які не збігаються.
ikegami

3
Я пропустив би код Python через профайлер, щоб дізнатись, де він проводить свій час. Ви також можете спробувати використовувати PCRE (Perl-сумісні регулярні вирази), а не Python, вбудований у регулярні вирази (ось ще одна реалізація ), і подивитися, чи це буде краще.
Schwern,

3
«Закритий як занадто локалізований» мені здається занадто смішним та суб’єктивним.
pepr

1
Я бачив бенчмарк перед тим, як вважаю, що реалізація регулярного виразу Perl набагато швидша, ніж Pythons. В іншому випадку вони повинні мати порівнянну швидкість.
Леон Тіммерманс,

Відповіді:


18

Саме такі речі був розроблений Perl, тому мене не дивує, що це швидше.

Однією з простих оптимізацій вашого коду Python було б попередньо скомпілювати ці регулярні вирази, щоб вони не перекомпілювались кожного разу.

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists')
location_re = re.compile(r'^AwbLocation (.*?) insert into')

А потім у вашому циклі:

mprev = exists_re.search(currline)

і

mcurr = location_re.search(currline)

Це саме по собі не призведе до магічного приведення вашого сценарію Python у відповідність із вашим скриптом Perl, але неодноразове виклик повторного циклу без попередньої компіляції - погана практика в Python.


3
reкешує нещодавно використані регулярні вирази, тому це, мабуть, не величезна проблема.
nneonneo

5
@nneonneo Я це чув неодноразово і бачив рядки у reвихідному коді, які виконують кешування. Але я якось ніколи не бачив орієнтир, який ставить два в один порядок величини, але кілька орієнтирів (у тому числі швидкий і брудний, який я зробив секунду тому), які ставлять опцію попередньої компіляції в кілька разів швидше.

3
Цікаво. Ну, це, безумовно, хороша практика попередньо компілювати регулярні вирази, але я не дуже звертав увагу на розрив у продуктивності. Хочете поділитися цифрами?
nneonneo

14

Гіпотеза: Perl витрачає менше часу на зворотне відстеження в рядках, які не збігаються через оптимізацію, яку має Python.

Що ви отримуєте заміною

^(.*?) INFO.*Such a record already exists

з

^((?:(?! INFO).)*?) INFO.*Such a record already 

або

^(?>(.*?) INFO).*Such a record already exists

4

Виклики функцій є трохи дорогими з точки зору часу в Python. І все ж у вас є виклик інваріантної функції циклу, щоб отримати ім'я файлу всередині циклу:

fn = fileinput.filename()

Перемістіть цей рядок вище forциклу, і ви побачите деяке покращення часу синхронізації Python. Можливо, недостатньо, щоб перемогти Perl.


1
+1 за приємне око, але ... Ну, але назва файлу змінюється. Це не інваріант циклу. У будь-якому випадку, може бути швидше не використовувати fileinputмодуль і додати ще один зовнішній цикл через імена файлів. Тоді ім'я файлу буде незмінним.
pepr

1
Цікавий момент, але це має бути незначним порівняно з часом обробки двох регулярних виразів.
dan1111

1

Загалом, усі штучні орієнтири - це зло. Однак, за інших рівних умов (алгоритмічний підхід), ви можете вдосконалити відносно. Однак слід зазначити, що я не використовую Perl, тому не можу сперечатися на його користь. З огляду на це, за допомогою Python ви можете спробувати використовувати Pyrex або Cython для підвищення продуктивності. Або, якщо ви любитель авантюри, ви можете спробувати перетворити код Python в C ++ за допомогою ShedSkin (який працює для більшості основних мов, а також деяких, але не всіх основних модулів).

Тим не менше, ви можете дотримуватися деяких порад, розміщених тут:

http://wiki.python.org/moin/PythonSpeed/PerformanceTips


я не є ані експертом, ані програмістом perl, ані python. Я використовую perl та python таким чином, від прочитаного від звичайного новачка до книги середнього рівня. Якщо я дбаю про реальну продуктивність, я, звичайно, скористаюся вашими пропозиціями і навіть використаю збірку (якщо я коли-небудь її вивчу). Використання того, що легко доступно в perl або python та його модулях, повинно бути єдиною пропозицією покращити код продуктивності. Я не сподіваюся використовувати деякі інші чарівні модні слова і витрачати час, щоб вивчити решту. Будь ласка, підкажіть чисте рішення, яке існує в установці nromal python.
ihightower

1
Я розумію, що всі штучні орієнтири можуть бути злими. Але обробка тексту проста, і це те, що я зазвичай роблю щодня. Отже, якщо python не може покращити швидкість використання основного синтаксису в оригінальній установці python ... (так само, як я це роблю з perl) ... мені доведеться вдатися до perl для своїх завдань з обробки тексту .. 100-ті або 100000-ті файли, які мені доводиться обробляти ... і треба буде визнати, що python повільний для простої обробки тексту, як це вказано в моєму коді. Але, хлопче, чи хочу я використовувати python для чистого синтаксису, але з відставанням швидкості .. не думаю.
ihightower

Регулярні вирази на Python подаються через модуль. Регулярні вирази в Perl мають вбудований синтаксис і можуть бути скомпільовані як вбудовані (без додаткових витрат на виклик функції). Обробка тексту не повинна бути такою простою. У будь-якому випадку, використовуйте кращий інструмент для кожного завдання. Мій особистий досвід полягає в тому, що трохи складніші програми Perl набагато складніше читати і підтримувати в майбутньому.
pepr

9
-1. Що в цьому "зла"? Це проста вправа, яка ілюструє значну різницю в швидкодії між двома мовами. Як саме ви маєте порівняти ефективність двох інструментів, якщо не з таким тестом? Написати всю програму обома мовами, щоб вона не була "штучною"? Звичайно, у тестування є підводні камені, але ви узагальнили це до дуже тупого правила.
dan1111

1

Я сподіваюся, що Perl буде швидшим. Просто цікаво, чи можете ви спробувати наступне?

#!/usr/bin/python

import re
import glob
import sys
import os

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for mask in sys.argv[1:]:
    for fname in glob.glob(mask):
        if os.path.isfile(fname):
            f = open(fname)
            for line in f:
                mex = exists_re.search(line)
                if mex:
                    xlogtime = mex.group(1)

                mloc = location_re.search(line)
                if mloc:
                    print fname, xlogtime, mloc.group(1)
            f.close()

Оновлення як реакція на "це занадто складно" .

Звичайно, це виглядає складніше, ніж версія Perl. Perl був побудований навколо регулярних виразів. Таким чином, ви навряд чи можете знайти інтерпретовану мову, яка швидша у регулярних виразах. Синтаксис Perl ...

while (<>) {
    ...
}

... також приховує багато речей, які доводиться робити якось більш загальною мовою. З іншого боку, зробити код читання Python більш читабельним, якщо перемістити нечитабельну частину, досить просто:

#!/usr/bin/python

import re
import glob
import sys
import os

def input_files():
    '''The generator loops through the files defined by masks from cmd.'''
    for mask in sys.argv[1:]:
        for fname in glob.glob(mask):
            if os.path.isfile(fname):
                yield fname


exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for fname in input_files():
    with open(fname) as f:        # Now the f.close() is done automatically
        for line in f:
            mex = exists_re.search(line)
            if mex:
                xlogtime = mex.group(1)

            mloc = location_re.search(line)
            if mloc:
                print fname, xlogtime, mloc.group(1)

Тут def input_files()файл можна розмістити в іншому місці (скажімо в іншому модулі), або його можна використати повторно. Можна просто імітувати навіть Perl while (<>) {...}, хоча і не так синтаксично:

#!/usr/bin/python

import re
import glob
import sys
import os

def input_lines():
    '''The generator loops through the lines of the files defined by masks from cmd.'''
    for mask in sys.argv[1:]:
        for fname in glob.glob(mask):
            if os.path.isfile(fname):
                with open(fname) as f: # now the f.close() is done automatically
                    for line in f:
                        yield fname, line

exists_re = re.compile(r'^(.*?) INFO.*Such a record already exists', re.I)
location_re = re.compile(r'^AwbLocation (.*?) insert into', re.I)

for fname, line in input_lines():
    mex = exists_re.search(line)
    if mex:
        xlogtime = mex.group(1)

    mloc = location_re.search(line)
    if mloc:
        print fname, xlogtime, mloc.group(1)

Тоді останні forможуть виглядати так само легко (в принципі), як і Perl while (<>) {...}. Такі покращення читабельності складніші в Perl.

У будь-якому випадку, це не зробить програму Python швидшою. Тут Perl знову буде швидшим. Perl - це програма для обробки файлів / тексту. Але - на мій погляд - Python є кращою мовою програмування для більш загальних цілей.


@ihightower Будь ласка, опублікуйте спробу редагування як нову відповідь.
Крейг Рінгер,

@pepr я опублікував свої результати як окрему відповідь. тепер код працює за 6,1 с (покращення на 2 с порівняно з попереднім) порівняно з 1,8 с. perl. будь ласка, прочитайте мою відповідь для отримання додаткової інформації.
ihightower

@ihightower: Використовуючи withконструкцію, це було б на один рядок коротше. Це правда, що вкладений forвиглядає жахливо. Однак вони говорять, що саме зроблено: 1) отримати аргументи командного рядка, 2) розгорнути кожен аргумент як маску глобусу, 3) якщо це ім'я файлу, відкрити його та обробити його рядки.
pepr

Оскільки обробка тексту настільки універсальна, то чому Python не просто робить вбудований стандартний модуль, настільки загальний, що його можна застосовувати майже до всіх випадків .. він може покращити свою продуктивність для звичайних користувачів, як переважна більшість люди ... наприклад, наприклад, імпортуйте TextTool або щось інше, тоді знайдіть кілька стандартних матеріалів, які покращать ефективність обробки тексту.
ihightower
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.