LinuxForum.Fans
Тема

grep sed perl awk - что быстрее для простых выборок

Автор: valet 32 сообщений 9056 просмотров
#305598

Может кому будет интересно, простые выборки из огромного файла на 13 млн. строк:

text
valet@valet-comp:~/Documents/all$ wc -l all
13107193 all
valet@valet-comp:~/Documents/all$ time awk '/foo/ { print }' all > /dev/null 

real 0m16.518s user 0m16.381s sys 0m0.128s valet@valet-comp:~/Documents/all$ time sed -n '/foo/p' all > /dev/null

real 0m2.959s user 0m2.888s sys 0m0.064s valet@valet-comp:~/Documents/all$ time grep 'foo' all > /dev/null

real 0m0.872s user 0m0.788s sys 0m0.080s valet@valet-comp:~/Documents/all$ time perl -ne 'print if /foo/' all > /dev/null

real 0m3.303s user 0m3.260s sys 0m0.036s valet@valet-comp:~/Documents/all$ time grep 'foo' all > /dev/null

real 0m0.878s user 0m0.800s sys 0m0.076s valet@valet-comp:~/Documents/all$ time perl -ne 'print if /foo/' all > /dev/null

real 0m3.284s user 0m3.180s sys 0m0.100s

По получается такой топ по скорости:

1. grep
2. sed
3. perl
4. awk

Честно говоря, меня сильно удивили эти тесты (повторял много раз - результаты те же).

1. Почему grep оказался шустрее всех - я думал он наоборот самый медленный - всегда даже старался использовать sed -n для выборок.
2. Как же так получается, что perl медленнее чем sed и grep? Я даже специально повторил несколько раз подряд тесты с grep и perl - так как не поверил свои глазам :)
3. awk совсем разочаровал - что он там такое делает что так долго отрабатывает :)

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

#305602

Проверить бы еще Python но не могу чето нагулить как сделать выборку строки из файла.

#305603
"valet" написал:
Я конечно понимаю, что сравнение этих инструментов может не совсем корректное, так как они могут выполнять разный по сложности функционал

Вы сами ответили.

#305604
"valet" написал:
Как же так получается, что perl медленнее чем sed и grep?

perl - интерпретируемый язык, а sed и grep написаны на Си. Хотя, многое ещё от алгоритма зависит.

#305609

"Lupo Alberto" написал:
Вы сами ответили.
Ну вообще-то сравнение шло в контексте вот такой вот тривиальной задачи как выборка - то что мы как порядочные линуксоиды :) сотни раз в сутки используем из консоли.
#305610
"valet" написал:
Проверить бы еще Python но не могу чето нагулить как сделать выборку строки из файла.

Ну, что-нибудь вроде этого:

text

#!/usr/bin/env python
#_*_ coding: utf-8 _*_

import sys

if len(sys.argv) != 3: sys.stderr.write("Usage %s <substr> <filename>\n" % sys.argv[0]) raise SystemExit(1)

with open(sys.argv[2], 'r') as f: for line in f: if sys.argv[1] in line: print line,

#305612
"-=Serj=-" написал:
Ну, что-нибудь вроде этого:

давай-ка по-честному, с регэкспами...

valet, кстати, интересно, как разбрасываются результаты в зависимости от сложности выражения... [по идее только перловка будет меньше в процентах отставать]

#305618
"Fat-Zer" написал:
давай-ка по-честному, с регэкспами...
text

#!/usr/bin/env python
#_*_ coding: utf-8 _*_

import sys, re

if len(sys.argv) != 3: sys.stderr.write("Usage %s <substr> <filename>\n" % sys.argv[0]) raise SystemExit(1) patt = re.compile(sys.argv[1])

with open(sys.argv[2], 'r') as f: for line in f: if patt.search(line): print line,

Как такового регэкспа тут нет...
Или что ты имел ввиду?

#305623

"-=Serj=-" написал:
Ну, что-нибудь вроде этого:
а что вообще делает это скрипт?
Вот я вкинул этот код
text
#!/usr/bin/env python
#_*_ coding: utf-8 _*_

import sys, re

if len(sys.argv) != 3: sys.stderr.write("Usage %s foo all\n" % sys.argv[0]) raise SystemExit(1) patt = re.compile(sys.argv[1])

with open(sys.argv[2], 'r') as f: for line in f: if patt.search(line): print line,

запустил и получил

text
Usage ./python_test foo all

real 0m0.019s user 0m0.012s sys 0m0.000s

Если это правда, то конечно круто. Но думаю я что-то неправильно сделал.

#305625
"valet" написал:
запустил и получил

Не-не. В скрипте править ничего не надо. Запускайте так:

text

./python_test foo all
#305626

Ага, понял, тогда так несколько раз подряд:

text
valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null 

real 0m4.696s user 0m4.616s sys 0m0.072s valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m4.708s user 0m4.616s sys 0m0.088s valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m4.663s user 0m4.536s sys 0m0.120s

То есть получается только получше, чем awk.

#305629
"valet" написал:
То есть получается только получше, чем awk.

C’est la vie :)
Могу для экспериментов ещё бинарник на Go попробовать собрать :)

ЗЫ. Для интереса, попробуйте первый вариант на Python - grep sed perl awk - что быстрее для простых выборок

#305634

"Fat-Zer" написал:
valet, кстати, интересно, как разбрасываются результаты в зависимости от сложности выражения... [по идее только перловка будет меньше в процентах отставать]
Ок, замена foo на bar сойдет?
text
valet@valet-comp:~/Documents/all$ time perl -p -e 's/foo/bar/g' all > /dev/null 

real 0m4.239s user 0m4.160s sys 0m0.072s valet@valet-comp:~/Documents/all$ time sed 's/foo/bar/g' all > /dev/null

real 0m3.917s user 0m3.796s sys 0m0.116s

sed тут опять таки почему-то быстрее perl'a, но зато perl умеет менять сразу несколько вхождений:

text
valet@valet-comp:~/Documents/all$ time perl -p -e 's/(foo|foo2|foo3|foon)/bar/g' all > /dev/null 

real 0m4.220s user 0m4.100s sys 0m0.116s

А это большой плюс, могу ошибаться, но кажется sed так не умеет - в случае sed'а пришлось бы делать отдельные замены через пайпы.

#305637

"-=Serj=-" написал:
ЗЫ. Для интереса, попробуйте первый вариант на Python
так я и попробовал.
#305638

"-=Serj=-" написал:
Могу для экспериментов ещё бинарник на Go попробовать собрать
попробуй, интересно :)
#305642

Ага, не обратил внимание, что это разные скрипты. Тогда так:

text
valet@valet-comp:~/Documents/all$ cat python_test 
#!/usr/bin/env python
#_*_ coding: utf-8 _*_

import sys, re

if len(sys.argv) != 3: sys.stderr.write("Usage %s <substr> <filename>\n" % sys.argv[0]) raise SystemExit(1) patt = re.compile(sys.argv[1])

with open(sys.argv[2], 'r') as f: for line in f: if patt.search(line): print line, valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m4.693s user 0m4.620s sys 0m0.068s valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m4.689s user 0m4.588s sys 0m0.096s valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m4.707s user 0m4.556s sys 0m0.144s valet@valet-comp:~/Documents/all$

и так:

text
valet@valet-comp:~/Documents/all$ cat python_test 
#!/usr/bin/env python
#_*_ coding: utf-8 _*_

import sys

if len(sys.argv) != 3: sys.stderr.write("Usage %s <substr> <filename>\n" % sys.argv[0]) raise SystemExit(1)

with open(sys.argv[2], 'r') as f: for line in f: if sys.argv[1] in line: print line, valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m2.784s user 0m2.688s sys 0m0.092s valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m2.788s user 0m2.696s sys 0m0.088s valet@valet-comp:~/Documents/all$ time ./python_test foo all > /dev/null

real 0m2.780s user 0m2.696s sys 0m0.080s valet@valet-comp:~/Documents/all$

То есть это уже неплохо и топ по скорости будет таким:

1. grep
2. python
3. sed
4. perl
5. awk
#305651

valet написал:
[quote="-=Serj=-"]Могу для экспериментов ещё бинарник на Go попробовать собрать
попробуй, интересно :)[/quote]
Бинарник на GO:
http://ubuntuone.com/2lSIeO2ssKSgBUcUU2iYmU
Запускать так же:
text

./go-grep foo all

ЗЫ. Этот вариант должен быть по-шустрее первого: http://ubuntuone.com/2EpcfsqzWPes75UGpHrjlQ
Исходник:

text

package main

import ( "bufio" "fmt" "os" "regexp" )

func main() {

if len(os.Args) != 3 { fmt.Printf("Usage: %s <substring> <filename>\n", os.Args[0]) return } substr := os.Args[1] fname := os.Args[2]

f, err := os.Open(fname) if err != nil { return } defer f.Close()

r := bufio.NewReader(f) patt, _ := regexp.Compile(substr)

for { s, ok := r.ReadString('\n') if ok != nil { break } matches := patt.FindString(s) if matches != "" { fmt.Print(s) }

}

}

#305654

набросал тут тестовый скриптик и прогнал его на сложном выражение...
места сильно отличаются:
grep - бесспорный лидер,
perl - второе место с многократным отставанием
awk - немного недотянул до перловки
sed - на порядок отстаёт от остальных
python - плетётся в самом конце

результаты

[console]# тестировать будем на исходниках ядра
alexander@goblin /tmp $ find linux-3.0/ -type f -exec cat {} + | tee megafile |wc -l
14647033
# сложное выражение
alexander@goblin /tmp $ ./grep-test.sh '\s*return.*;'
wc -l for grep: 411133
wc -l for perl: 411133
wc -l for awk: 411133
wc -l for sed: 411133
wc -l for python: 411133
tests number: 10
time for grep: 4.04 (avg 0.40)
time for perl: 27.28 (avg 2.73)
time for awk: 28.76 (avg 2.88)
time for sed: 174.89 (avg 17.49)
time for python: 182.10 (avg 18.21)
# простое выражение
# все улучшили результаты, отставание аутсайдеров сократилось
wc -l for grep: 453902
wc -l for perl: 453902
wc -l for awk: 453902
wc -l for sed: 453902
wc -l for python: 453902
tests number: 10
time for grep: 4.23 (avg 0.42)
time for perl: 25.33 (avg 2.53)
time for awk: 28.56 (avg 2.86)
time for sed: 36.24 (avg 3.62)
time for python: 69.19 (avg 6.92)
# не встречающееся простое выражение
# ещё немного лучше, примечательно трёхкратное, улучшение grep'а, sed наоборот немного скатился
./grep_test.sh 'this_statment_is_false'
wc -l for grep:0
wc -l for perl:0
wc -l for awk:0
wc -l for sed:0
wc -l for python:0
tests number: 10
time for grep: 1.60 (avg 0.16)
time for perl: 21.87 (avg 2.19)
time for awk: 28.30 (avg 2.83)
time for sed: 37.31 (avg 3.73)
time for python: 68.18 (avg 6.82)
# не встречающееся сложное выражение
# без особых изменений
alexander@goblin /tmp $ ./grep_test.sh 'this.*statment.*is.*false[!?]'
wc -l for grep:0
wc -l for perl:0
wc -l for awk:0
wc -l for sed:0
wc -l for python:0
tests number: 10
time for grep: 2.80 (avg 0.28)
time for perl: 22.88 (avg 2.29)
time for awk: 28.24 (avg 2.82)
time for sed: 45.55 (avg 4.55)
time for python: 68.14 (avg 6.81)
# короткое часто встречающееся и простое выражение
# а вот и вариант, где perl проигрывает awk
alexander@goblin /tmp $ ./grep_test.sh ' '
wc -l for grep:9869702
wc -l for perl:9869702
wc -l for awk:9869702
wc -l for sed:9869702
wc -l for python:9869702
tests number: 10
time for grep: 29.31 (avg 2.93)
time for perl: 32.03 (avg 3.20)
time for awk: 28.74 (avg 2.87)
time for sed: 39.30 (avg 3.93)
time for python: 87.30 (avg 8.73)
# короткое часто встречающееся и сложное выражение
# ещё один вариант, где perl лажает... примечателен также тем, что sed не нашёл 15 строк
alexander@goblin /tmp $ ./grep_test.sh '\S'
wc -l for grep:12654337
wc -l for perl:12654337
wc -l for awk:12654337
wc -l for sed:12654324
wc -l for python:12654337
tests number: 10
time for grep: 11.70 (avg 1.17)
time for perl: 47.23 (avg 4.72)
time for awk: 27.82 (avg 2.78)
time for sed: 70.22 (avg 7.02)
time for python: 112.62 (avg 11.26)[/console]
grep_test.sh
text
#!/bin/bash

REG_EXP="$1" FILE="/tmp/megafile" TEST_NUM=10

PYTHON_SCRIPT="/tmp/grep.py"

SED_CMD=(sed -ne "/$REG_EXP/p") GREP_CMD=(grep "$REG_EXP") PERL_CMD=(perl -ne "print if /$REG_EXP/") AWK_CMD=(awk "/$REG_EXP/ { print }") PYTHON_CMD=(python ${PYTHON_SCRIPT} "$REG_EXP")

GNU_TIME="/usr/bin/time"

LC_ALL=C

wc_l() { printf "wc -l for %6s: " "${1}" "$@" $FILE | wc -l }

rtime() { overall=0; for((i=0; i<$TEST_NUM; i++)); do test_time="$(${GNU_TIME} -p "$@" $FILE 2>&1 1>/dev/null | sed -n '/real\s\(.*\)/{s//\1/;p}')" overall="$(python -c "print '%10.5f' % ($overall + $test_time)" )" done python -c "print 'time for %6s: %6.2f (avg %5.2f)' % ('${1}', $overall, $overall / $TEST_NUM)" }

wc_l "${GREP_CMD[@]}" wc_l "${PERL_CMD[@]}" wc_l "${AWK_CMD[@]}" wc_l "${SED_CMD[@]}" wc_l "${PYTHON_CMD[@]}"

echo "tests number: $TEST_NUM"

rtime "${GREP_CMD[@]}" rtime "${PERL_CMD[@]}" rtime "${AWK_CMD[@]}" rtime "${SED_CMD[@]}" rtime "${PYTHON_CMD[@]}"

/tmp/grep.py
text
#!/usr/bin/env python
#_*_ coding: utf-8 _*_

import sys, re

patt = re.compile(sys.argv[1])

with open(sys.argv[2], 'r') as f: for line in f: if patt.search(line): print line,

#305691

отсюда вопрос: valet, что у тебя за кривой awk такой?

[копирайты отрезаны]

text
alexander@goblin /tmp $ grep --version
grep (GNU grep) 2.14
alexander@goblin /tmp $ perl --version
This is perl 5, version 12, subversion 4 (v5.12.4) built for x86_64-linux
(with 12 registered patches, see perl -V for more detail)
alexander@goblin /tmp $ awk --version
GNU Awk 4.0.1
alexander@goblin /tmp $ sed --version
GNU sed версия 4.2.1
alexander@goblin /tmp $ python --version
Python 2.7.3
Войдите или зарегистрируйтесь, чтобы ответить.