Grep -E, Sed -E - низька продуктивність, коли використовується '[x] {1,9999}', але чому?


9

Коли grepабо sedвикористовується з опцією, --extended-regexpа шаблон {1,9999}є частиною регулярного вираження, який використовується, продуктивність цих команд стає низькою. Щоб бути більш зрозумілим, нижче застосовується кілька тестів. [1] [2]

  • Відносна продуктивність grep -E, egrepі sed -Eмайже дорівнює, тому тільки тест , які були зроблені з grep -Eпередбачені.

Тест 1

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

real    0m0.002s

Тест 2

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

> real    0m0.494s

Тест 3

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

> справжні 21м43.947с

Тест 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       

У чому причина цієї суттєвої різниці у виконанні?


3
Це цікаве спостереження - я здогадуюсь, що вам потрібно буде заглибитись у внутрішні частини grep, щоб дізнатися, як саме будується дерево розбору (було б цікаво також порівняти [0-9]+)
steeldriver

3
Вхід не має значення. Як підказує @steeldriver, уповільнення передує збігу. Більш простий тест time grep -E '[0-9]{1,99}' </dev/nullпроти time grep -E '[0-9]{1,9999}' </dev/null. Навіть без введення , друга команда повільна (16.04). Як і слід було очікувати, опускаючи -Eі втечу {і }поводишся так само і заміну -Eз -Pнегайному (PCRE є іншим двигуном). Найцікавіше, наскільки швидше [0-9] це ніж ., xі навіть [0123456789]. З будь-яким із них і {1,9999}, grepспоживає величезну кількість оперативної пам’яті; Я не наважувався пускати його більше ~ 10 хв.
Елія Каган

3
@ Αғsнιη немає, { }будуть ' 'вказані ; оболонка передає їх незмінним grep. У будь-якому випадку, це {1,9999}було б дуже швидким і простим розширенням брекетів . Оболонка просто розширить його 1 9999.
Елія Каган

4
@ αғsnιη Я не знаю зовсім, що ви маєте на увазі, але це, безумовно, не має нічого спільного з оболонкою. Під час тривалої команди я використовував psі topдля того, щоб перевірити, чи grepбули передані очікувані аргументи, і що вона, не так bash, споживає багато оперативної пам'яті та процесора. Я очікую, grepі sedобидва використовують функції регулярного вираження POSIX, реалізовані в libc, для відповідності BRE / ERE; Мені не варто було б особливо говорити про grepдизайн, за винятком тих випадків, коли grepрозробники вирішили використовувати цю бібліотеку.
Елія Каган

3
Я пропоную вам замінити тести на time grep ... < /dev/null, щоб люди не плутали фактичну проблему з даними, поданими grepта іншими сторонніми речами.
муру

Відповіді:


10

Зауважте, що не потрібна відповідність часу, а побудова ПЗ. Ви побачите, що він також використовує досить багато оперативної пам’яті:

$ 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

Кількість алоків здається приблизно пропорційною кількості ітерацій, але виділена пам'ять, схоже, зростає експоненціально.

Це зводиться до того, як реалізуються регулярні вирази GNU. Якщо ви компілюєте GNU grepз CPPFLAGS=-DDEBUG ./configure && makeцими командами та запускаєте ці команди, ви побачите експоненціальний ефект у дії. Заглибитись глибше, ніж це означало б пройти багато теорії щодо DFA та зануритися у впровадження ggelib regexp.

Тут ви можете використовувати PCRE замість того, що, схоже, не має тієї ж проблеми: grep -Po '[0-9]{1,65535}'(максимум, хоча ви завжди можете робити такі речі, як [0-9](?:[0-9]{0,10000}){100}від 1 до 1 000 001 повторень) не потребує більше часу і пам'яті, ніж grep -Po '[0-9]{1,2}'.


Чи є спосіб обійти це?
Сергій Колодяжний

3
@SergiyKolodyazhnyy, ви можете використовувати, grep -Po '[0-9]{1,9999}які, схоже, не мають проблеми.
Стефан Шазелас

1
Це не тільки sed -Eабо grep -E, але в awkтеж має цю низьку продуктивність (про останній команді AWK). можливо, awkтакож не можна використовувати PCRE?
αғsnιη
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.