Быстрее datetime.strptime - PullRequest
       74

Быстрее datetime.strptime

1 голос
/ 06 мая 2020

Попытка получить unixtimestamp из миллионов bytes объектов

Использование этого

import datetime 
dt_bytes = b'2019-05-23 09:37:56.362965'
#fmt = '%m/%d/%Y %H:%M:%S.%f'
fmt = '%Y-%m-%d %H:%M:%S.%f'
dt_ts = datetime.datetime.strptime(dt_bytes.decode('utf-8'), fmt)
unix_ts = dt_ts.timestamp()

отлично работает:

In [82]: unix_ts                                                                                                             
Out[82]: 1558604276.362965

Но decode('utf-8') сокращает скорость потока вдвое (с 38k / se c до 20k / se c).

Так есть ли способ получить unixtimestamp из ввода bytes вместо этого ввода str?

__ ОБНОВЛЕНИЕ: __

Я обнаружил, что узким местом является datetime.datetime.strptime(..), поэтому я переключился на np.datetime64 ( см. Ниже )

__ ОБНОВЛЕНИЕ 2: __ Проверьте принятый ответ ниже, чтобы получить хороший тест производительности для различных подходов.

Ответы [ 2 ]

1 голос
/ 07 мая 2020

Сначала предположим, что у вас есть строки в формате ISO, '% Y-% m-% dT% H:% M:% S.% f', в list (давайте также не будем рассматривать декодирование из байтового массива для сейчас):

from datetime import datetime, timedelta
base, n = datetime(2000, 1, 1, 1, 2, 3, 420001), 1000
datelist = [(base + timedelta(days=i)).isoformat(' ') for i in range(n)]
# datelist
# ['2000-01-01 01:02:03.420001'
# ...
# '2002-09-26 01:02:03.420001']

из строки в объект datetime

Давайте определим некоторые функции, которые анализируют строку до datetime, используя разные методы:

import re
import numpy as np

def strp_isostr(l):
    return list(map(datetime.fromisoformat, l))

def isostr_to_nparr(l):
    return np.array(l, dtype=np.datetime64)

def split_isostr(l):
    def splitter(s):
        tmp = s.split(' ')
        tmp = tmp[0].split('-') + [tmp[1]]
        tmp = tmp[:3] + tmp[3].split(':')
        tmp = tmp[:5] + tmp[5].split('.')
        return datetime(*map(int, tmp))
    return list(map(splitter, l))

def resplit_isostr(l):
    # return list(map(lambda s: datetime(*map(int, re.split('T|-|\:|\.', s))), l))
    return [datetime(*map(int, re.split('\ |-|\:|\.', s))) for s in l]

def full_stptime(l):
    # return list(map(lambda s: datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f'), l))
    return [datetime.strptime(s, '%Y-%m-%d %H:%M:%S.%f') for s in l]

Если я запускаю %timeit в консоли I Python для этих функций на моем компьютере, я получаю

%timeit strp_isostr(datelist)
98.2 µs ± 766 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%timeit isostr_to_nparr(datelist)
1.49 ms ± 13.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit split_isostr(datelist)
3.02 ms ± 236 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit resplit_isostr(datelist)
3.8 ms ± 256 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit full_stptime(datelist)
16.7 ms ± 780 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Итак, мы можем сделать вывод, что встроенный datetime.fromisoformat - безусловно, самый быстрый вариант для ввода 1000 элементов. Однако это предполагает, что вы хотите работать с list. Если вам все равно нужно np.array из datetime64, переход прямо к нему кажется лучшим вариантом.


сторонний вариант: ciso8601

Если вы можете установить дополнительные пакеты, ciso8601 заслуживает внимания:

import ciso8601
def ciso(l):
    return list(map(ciso8601.parse_datetime, l))

%timeit ciso(datelist)
138 µs ± 1.83 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

от объекта datetime до секунд с начала эпохи

Просмотр преобразования объекта datetime в метку времени POSIX , использование наиболее очевидного метода datetime.timestamp кажется наиболее эффективным:

import time
def dt_ts(l):
    return list(map(datetime.timestamp, l))

def timetup(l):
    return list(map(time.mktime, map(datetime.timetuple, l)))

%timeit dt_ts(strp_isostr(datelist))
572 µs ± 4.57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit timetup(strp_isostr(datelist))
1.44 ms ± 15.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
1 голос
/ 06 мая 2020

Я перехожу к numpy.datetime64, потому что он имеет меньшую задержку, чем datetime.strptime

import numpy as np

# This is the format np.datetime64 needs:
#np.datetime64('2002-06-28T01:00:00.000000000+0100')

dt_bytes = b'2019-05-23 09:37:56.362965'
#dt_bytes_for_np = dt_bytes.split(b' ')[0] + b'T' + dt_bytes.split(b' ')[1]
dt_bytes_for_np = dt_bytes.replace(b' ', b'T')
ts = np.datetime64(dt_bytes_for_np)

, и получаю unixtimestamp (это добавляет немного задержки, но все же лучше, чем datetime.strptime:

ts.astype('datetime64[ns]').astype('float') / 1000000000
1558604276.362965
...