Почему Python в разы проигрывает Perl по скорости и расходу памяти при парсинге логов?

Стало интересно протестить питон и перл на скорость при работе с большими файлами. Для теста было написано 2 маленьких скрипта, суть скриптов: прочесть лог и создать хэш, в котором ключом является ip (первое поле в строчке лога), а значением все остальные запросы с данного ip.
В качестве лога был использован лог нджинкса, в котором поля разделены :%%:.
Размер файла лога - 1Гб.

#!/usr/bin/perl -w

open(F, "</var/logs/access.log");

while (<F>) {
 ($ip, $d) = split(/:%%:/, $_, 2);
 

 if (!(exists $host{$ip})) {
  $host{$ip} = {};
  $host{$ip}{data} = '';
 }

 $host{$ip}{data} .= $d; 

}


time ./speed_test.pl
real 0m14.713s
user 0m7.240s
sys 0m2.060s

#!/usr/bin/python3.2

fd = open('/var/logs/access.log', 'r')

host = {}

for line in fd:
    ip, d = line.split(r':%%:', 1)
  
    if ip not in host:
      host[ip] = {}
      #host[ip]['data'] = []
      host[ip]['data'] = ''
  
    #host[ip]['data'].append(d)
    host[ip]['data'] = host[ip]['data'] + d


time ./speed_test.py
Traceback (most recent call last):
File "./speed_test.py", line 16, in
host[ip]['data'] = host[ip]['data'] + d
MemoryError

real 6m35.528s
user 3m13.940s
sys 3m19.828s

На 6-ой минуте переполнилась память...
Если использовать для хранения последующих строк список (раскомментить закоменченные строки), то переполнение памяти происходит значительно раньше:
time ./speed_test.py
Traceback (most recent call last):
File "./speed_test.py", line 7, in
for line in fd:
MemoryError

real 0m25.717s
user 0m7.016s
sys 0m2.168s

Не могу понять, почему питон показывает такие отвратительные результаты...
  • Вопрос задан
  • 5111 просмотров
Пригласить эксперта
Ответы на вопрос 6
@throughtheether
human after all
Я небольшой специалист в python, но предполагаю, что основные ресурсы тратятся здесь:

host[ip]['data'] = ''
...
host[ip]['data'] = host[ip]['data'] + d

Почему вы используете словарь как значение ключей внешнего словаря (переменная host)? Попробуйте сделать так:
host[ip] = ''
...
host[ip] = host[ip] + d
Ответ написан
@Vaal
со списком можно попробовать так
from collections import defaultdict
host = defaultdict(list)
for line in open('1.24gb.log'):
    ip, d = line.split(' ', 1)
    host[ip].append(d)

лог на 1.24гб
на Python 2.7.6 - 6.7 сек
на Python 3.4.0 - 9.7 сек
Ответ написан
Комментировать
@FeRViD Автор вопроса
Как я понял, в питоне операция слияния строки в более длинную очень дорогая, настолько дорогая, что от нее нужно отказаться. И непонятно, почему память переполняется в итоге, а в перле все ок - 20% (т.к. 4 гб RAM).
Но, использование списка тоже не решает проблемы.
Ответ написан
@FeRViD Автор вопроса
#!/usr/bin/python3.2
from collections import deque, defaultdict

host = defaultdict(deque)
with open('/var/logs/access.log', 'r') as f:
    for line in f:
        ip, d = line.split(r':%%:', 1)
        host[ip].append(d)


time ./speed_test.py
Traceback (most recent call last):
File "./speed_test.py", line 6, in
for line in f:
MemoryError

real 0m17.845s
user 0m7.096s
sys 0m2.440s
Ответ написан
@snowpiercer
Строки в питоне immutable, поэтому при каждом вызове
host[ip]['data'] = host[ip]['data'] + d
создается новая строка и производится копирование
Ответ написан
Комментировать
@Ptktysq
В perl нужны только первая и последняя строчка тела цикла - остальное тормозящий мусор.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы