Size: a a a

2020 December 22

Б

Боброний in PiterPy Meetup
Always precompile regular expressions using re.compile if the expression is known in advance:

# generate random string
from string import printable
from random import choice
text = ''.join(choice(printable) for _ in range(10 * 8))

# let's find numbers
pat = r'\d(?:[\d\.]+\d)*'
rex = re.compile(pat)

%timeit re.findall(pat, text)
# 2.08 µs ± 1.89 ns per loop

# pre-compiled almost twice faster
%timeit rex.findall(text)
# 1.3 µs ± 68.8 ns per loop


The secret is that module-level re functions just compile the expression and call the corresponding method, no optimizations involved:

def findall(pattern, string, flags=0):
   return _compile(pattern, flags).findall(string)


If the expression is not known in advance but can be used repeatedly, consider using functools.lru_cache:

from functools import lru_cache

cached_compile = lru_cache(maxsize=64)(re.compile)

def find_all(pattern, text):
   return cached_compile(pattern).findall(text)
источник

Б

Боброний in PiterPy Meetup
Боброний
Always precompile regular expressions using re.compile if the expression is known in advance:

# generate random string
from string import printable
from random import choice
text = ''.join(choice(printable) for _ in range(10 * 8))

# let's find numbers
pat = r'\d(?:[\d\.]+\d)*'
rex = re.compile(pat)

%timeit re.findall(pat, text)
# 2.08 µs ± 1.89 ns per loop

# pre-compiled almost twice faster
%timeit rex.findall(text)
# 1.3 µs ± 68.8 ns per loop


The secret is that module-level re functions just compile the expression and call the corresponding method, no optimizations involved:

def findall(pattern, string, flags=0):
   return _compile(pattern, flags).findall(string)


If the expression is not known in advance but can be used repeatedly, consider using functools.lru_cache:

from functools import lru_cache

cached_compile = lru_cache(maxsize=64)(re.compile)

def find_all(pattern, text):
   return cached_compile(pattern).findall(text)
Я где-то слышал то ли что re сам компилирует и кэширует паттерны при использовании, то ли, что компилирование на скорость не влияет почти. Не слышали такого?
источник

AK

Alex 🌼 Karantinsky... in PiterPy Meetup
Боброний
Always precompile regular expressions using re.compile if the expression is known in advance:

# generate random string
from string import printable
from random import choice
text = ''.join(choice(printable) for _ in range(10 * 8))

# let's find numbers
pat = r'\d(?:[\d\.]+\d)*'
rex = re.compile(pat)

%timeit re.findall(pat, text)
# 2.08 µs ± 1.89 ns per loop

# pre-compiled almost twice faster
%timeit rex.findall(text)
# 1.3 µs ± 68.8 ns per loop


The secret is that module-level re functions just compile the expression and call the corresponding method, no optimizations involved:

def findall(pattern, string, flags=0):
   return _compile(pattern, flags).findall(string)


If the expression is not known in advance but can be used repeatedly, consider using functools.lru_cache:

from functools import lru_cache

cached_compile = lru_cache(maxsize=64)(re.compile)

def find_all(pattern, text):
   return cached_compile(pattern).findall(text)
Ну тут тестируется время работы скомпилированного выражения против время работы скомпилированного выражения + поиск в кеше скомпилированных выражений
источник

AK

Alex 🌼 Karantinsky... in PiterPy Meetup
Потому что любой метод модуля re кеширует скомпилированное выражение (см функцию _compile)
источник

DB

Dima Boger in PiterPy Meetup
Alex 🌼 Karantinsky
Потому что любой метод модуля re кеширует скомпилированное выражение (см функцию _compile)
TIL
источник

Б

Боброний in PiterPy Meetup
То есть компилировать выражение самому имеет смысл только для удобства разве что
источник

Б

Боброний in PiterPy Meetup
Хотя сейчас проверил, всё равно быстрее скомпилированный паттерн
источник

AK

Alex 🌼 Karantinsky... in PiterPy Meetup
Боброний
То есть компилировать выражение самому имеет смысл только для удобства разве что
Компилировать имеет смысл если выражение простое и хочется сохранить почти микросекунду на каждом вызове, или если нет уверенности что выражение не вымоется из кеша
источник

Б

Боброний in PiterPy Meetup
Вспомнил, из pydantic то ли хотели убрать, то ли убрали компилирование паттернов, чтобы сократить время импорта пакета
источник

AK

Alex 🌼 Karantinsky... in PiterPy Meetup
Настоящая разница между скомпилированны и нет выражением:

In [16]: %timeit re.findall(pat, text); re.purge()                                                                              
105 µs ± 3.41 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [17]: %timeit rex.findall(text)                                                                                              
2.6 µs ± 46.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


Довольно существенно, чтобы не компилировать сотни регекспов, которые не нужны прямо сейчас
источник

AK

Alex 🌼 Karantinsky... in PiterPy Meetup
В третьем питоне дефолтный кеш увеличили с 100 до 512 элементов и теперь он не полностью очищается, а удаляется самый старый элемент (всегда один)
источник

AZ

Andrey Zakharevich in PiterPy Meetup
Alex 🌼 Karantinsky
Настоящая разница между скомпилированны и нет выражением:

In [16]: %timeit re.findall(pat, text); re.purge()                                                                              
105 µs ± 3.41 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [17]: %timeit rex.findall(text)                                                                                              
2.6 µs ± 46.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


Довольно существенно, чтобы не компилировать сотни регекспов, которые не нужны прямо сейчас
нене, первое время у тебя включает в себя еще и время на purge
источник

AZ

Andrey Zakharevich in PiterPy Meetup
In [6]: re.purge()

In [7]: %timeit re.findall(pat, text)
2.38 µs ± 62.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [8]: re.purge()

In [9]: %timeit rex.findall(text)
1.97 µs ± 36.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [10]: re.purge()

In [11]: %timeit re.findall(pat, text); re.purge()
69.7 µs ± 1.35 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
источник

AK

Alex 🌼 Karantinsky... in PiterPy Meetup
Andrey Zakharevich
In [6]: re.purge()

In [7]: %timeit re.findall(pat, text)
2.38 µs ± 62.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [8]: re.purge()

In [9]: %timeit rex.findall(text)
1.97 µs ± 36.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [10]: re.purge()

In [11]: %timeit re.findall(pat, text); re.purge()
69.7 µs ± 1.35 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Ох
источник

DB

Dima Boger in PiterPy Meetup
Andrey Zakharevich
In [6]: re.purge()

In [7]: %timeit re.findall(pat, text)
2.38 µs ± 62.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [8]: re.purge()

In [9]: %timeit rex.findall(text)
1.97 µs ± 36.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [10]: re.purge()

In [11]: %timeit re.findall(pat, text); re.purge()
69.7 µs ± 1.35 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Всё не так же
источник

DB

Dima Boger in PiterPy Meetup
Ты .purge() делаешь между подходами 🤔, а внутри %timeit он запускает тысячи раз
источник

Б

Боброний in PiterPy Meetup
Dima Boger
Ты .purge() делаешь между подходами 🤔, а внутри %timeit он запускает тысячи раз
Ага
источник

AZ

Andrey Zakharevich in PiterPy Meetup
Dima Boger
Ты .purge() делаешь между подходами 🤔, а внутри %timeit он запускает тысячи раз
а, блин
источник

Б

Боброний in PiterPy Meetup
Нужно тогда вручную мерить время
источник

Б

Боброний in PiterPy Meetup
В timeit не помешал бы какой-нибудь аргумент типа cleanup
источник