Grep -E, Sed -E - kinerja rendah ketika '[x] {1,9999}' digunakan, tetapi mengapa?

9

Ketika grepatau seddigunakan dengan opsi --extended-regexpdan pola {1,9999}adalah bagian dari regexp yang digunakan, kinerja perintah ini menjadi rendah. Agar lebih jelas, di bawah ini diterapkan beberapa tes. [1] [2]

  • Kinerja relatif dari grep -E, egrepdan sed -Ehampir sama, jadi hanya tes yang dibuat dengan grep -Eyang disediakan.

Tes 1

$ time grep -E '[0-9]{1,99}' < /dev/null

real    0m0.002s

Tes 2

$ time grep -E '[0-9]{1,9999}' < /dev/null

> real    0m0.494s

Tes 3

$ time grep -E '[0123456789] {1,9999}' </ dev / null

> 21m43.947s nyata

Tes 4

$ time grep -E '[0123456789]+' < /dev/null
$ time grep -E '[0123456789]*' < /dev/null
$ time grep -E '[0123456789]{1,}' < /dev/null
$ time grep -P '[0123456789]{1,9999}' < /dev/null

real    0m0.002s       

Apa alasan perbedaan kinerja yang signifikan ini?

pa4080
sumber
3
Itu pengamatan yang menarik - tebakan saya adalah Anda harus menggali lebih dalam ke internal grep untuk menemukan bagaimana tepatnya membangun pohon parse (akan menarik untuk membandingkan [0-9]+juga)
steeldriver
3
Masukan tidak masalah. Seperti yang disarankan @steeldriver, perlambatan mendahului pencocokan. Sebuah tes sederhana adalah time grep -E '[0-9]{1,99}' </dev/nullvs time grep -E '[0-9]{1,9999}' </dev/null. Bahkan tanpa input , perintah kedua lambat (pada 16.04). Seperti yang diharapkan, menghilangkan -Edan melarikan diri {dan }berperilaku sama dan mengganti -Edengan -Ptidak lambat (PCRE adalah mesin yang berbeda). Paling menarik adalah berapa banyak cepat [0-9] adalah dari ., x, dan bahkan [0123456789]. Dengan semua itu dan {1,9999}, grepmengkonsumsi sejumlah besar RAM; Saya belum berani membiarkannya berjalan lebih dari ~ 10 menit.
Eliah Kagan
3
@ αғsнιη Tidak, { }ini ' 'dikutip ; shell melewati mereka tidak berubah grep. Bagaimanapun, {1,9999}akan menjadi ekspansi penjepit yang sangat cepat dan sederhana . Shell hanya akan memperluas ke 1 9999.
Eliah Kagan
4
@ αғsнιη Saya tidak tahu apa yang Anda maksud, tapi ini jelas tidak ada hubungannya dengan shell. Selama perintah berjalan lama, saya menggunakan psdan topmemverifikasi grepmelewati argumen yang diharapkan dan itu, tidak bash, mengkonsumsi banyak RAM dan CPU. Saya berharap grepdan sedkeduanya menggunakan fungsi regex POSIX diimplementasikan dalam libc untuk pencocokan BRE / ERE; Saya seharusnya tidak benar-benar berbicara tentang grepdesain secara khusus, kecuali sejauh greppengembang memilih untuk menggunakan perpustakaan itu.
Eliah Kagan
3
Saya menyarankan Anda mengganti tes time grep ... < /dev/null, sehingga orang tidak mengacaukan masalah sebenarnya dengan data yang diumpankan ke grepdan hal-hal asing lainnya.
muru

Jawaban:

10

Perhatikan bahwa bukan pencocokan yang membutuhkan waktu, tetapi pembangunan RE. Anda akan menemukan bahwa itu menggunakan cukup banyak RAM juga:

$ valgrind grep -Eo '[0-9]{1,9999}' < /dev/null
==6518== HEAP SUMMARY:
==6518==     in use at exit: 1,603,530,656 bytes in 60,013 blocks
==6518==   total heap usage: 123,613 allocs, 63,600 frees, 1,612,381,621 bytes allocated
$ valgrind grep -Eo '[0-9]{1,99}' < /dev/null
==6578==     in use at exit: 242,028 bytes in 613 blocks
==6578==   total heap usage: 1,459 allocs, 846 frees, 362,387 bytes allocated
$ valgrind grep -Eo '[0-9]{1,999}' < /dev/null
==6594== HEAP SUMMARY:
==6594==     in use at exit: 16,429,496 bytes in 6,013 blocks
==6594==   total heap usage: 12,586 allocs, 6,573 frees, 17,378,572 bytes allocated

Jumlah allocs tampaknya kira-kira sebanding dengan jumlah iterasi, tetapi memori yang dialokasikan tampaknya tumbuh secara eksponensial.

Itu tergantung pada bagaimana GNU regexps diimplementasikan. Jika Anda mengkompilasi GNU grepdengan CPPFLAGS=-DDEBUG ./configure && make, dan menjalankan perintah-perintah itu, Anda akan melihat efek eksponensial dalam aksi. Pergi lebih dalam dari itu akan berarti melalui banyak teori tentang DFA dan terjun ke implementasi regn gnulib.

Di sini, Anda dapat menggunakan PCRE sebagai gantinya yang tampaknya tidak memiliki masalah yang sama: grep -Po '[0-9]{1,65535}'(maksimum, meskipun Anda selalu dapat melakukan hal-hal seperti [0-9](?:[0-9]{0,10000}){100}untuk 1 hingga 1.000 repetisi) tidak membutuhkan waktu atau memori lebih lama dari itu grep -Po '[0-9]{1,2}'.

Stéphane Chazelas
sumber
Apakah ada cara untuk mengatasi ini?
Sergiy Kolodyazhnyy
3
@SergiyKolodyazhnyy, Anda dapat menggunakan grep -Po '[0-9]{1,9999}yang tampaknya tidak memiliki masalah.
Stéphane Chazelas
1
Ini tidak hanya di sed -Eatau grep -E, tetapi di awkjuga memiliki kinerja yang rendah ini (sekitar perintah awk terakhir). mungkin awkjuga tidak bisa menggunakan PCRE?
αғsнιη