Tenho um arquivo grande (gigabyte) onde uma expressão S aparece, e quero pular para o final da expressão S. A profundidade da expressão S é limitada a 2, então tentei usar uma expressão regular Python ( b'\\((?:[^()]|\\((?:[^()]|)*\\))*\\)'
). Isso acabou consumindo muita RAM, e pesquisando mais a fundo descobri que o consumo de memória de expressões regulares moderadamente complexas parece altamente imprevisível se a correspondência for grande. Por exemplo, as quatro expressões regulares equivalentes a seguir correspondem a uma string completa de dez megabytes. A quarta (indiscutivelmente a mais complexa) usa uma quantidade razoável (30 M) de RAM, enquanto as outras consomem um gigabyte:
import re
dot = b'[' + b''.join(b'\\x%02x' % (c,) for c in range(256)) + b']'
assert re.match(b'(?:.|.)*', b'a'*10000000).end() > 1000000
assert re.match(b'(?:.|.|a)*', b'a'*10000000).end() > 1000000
assert re.match(b'(?:%s|%s)*' % (dot,dot), b'a'*10000000).end() > 1000000
assert re.match(b'(?:%s|%s|a)*' % (dot,dot), b'a'*10000000).end() > 1000000
(usando Python 3.12.3)
Existe uma maneira razoável de prever se o desempenho de uma regexp Python pode ser escalável? E, em particular, existem alguns princípios de design que eu possa seguir se quiser evitar armadilhas de desempenho?
(Esta pergunta é especificamente sobre o re
módulo, porque prefiro usar bibliotecas Python padrão; suspeito que isso não seria um problema se eu mudasse para uma biblioteca de terceiros como regex
)
No Python 3.11 (ou posterior), tente usar "quantificadores possessivos" em vez disso. Ou seja, no seu exemplo, substituir instâncias de
*
por*+.
Sua regexp não requer backtracking, e um quantificador possessivo diz ao mecanismo de correspondência para não se preocupar em salvar nenhuma informação de backtracking para começar. Isso pode economizar muita RAM e tempo.
Um mecanismo mais capaz poderia deduzir por si só que o backtracking não é útil no seu caso, mas isso está além do que o mecanismo do Python pode fazer.
Da mesma forma, quando o backtracking não é útil, também pode ajudar a substituir grupos não-capturadores (
(?:
) por grupos atômicos ((?>)
, Grupos atômicos também não são capturadores, mas também pulam a informação de backtracking salva. Quantificadores possessivos são, na verdade, açúcar sintático para escrever um grupo atômico mais longo.