Я разработал numpy
-только версию, которая работает, но после тестирования я обнаружил, что она работает довольно плохо, потому что не может воспользоваться коротким замыканием . Так как это то, что вы просили, я опишу это ниже. Тем не менее, есть намного лучший подход, использующий numba
со слегка измененной версией вашего кода. (Обратите внимание, что все они возвращают индекс первого совпадения в a
, а не само значение. Я считаю этот подход более гибким.)
@numba.jit(nopython=True)
def find_reps_numba(a, max_len):
streak = 1
val = a[0]
for i in range(1, len(a)):
if a[i] == val:
streak += 1
if streak >= max_len:
return i - max_len + 1
else:
streak = 1
val = a[i]
return -1
Это оказывается в ~ 100 раз быстрее, чем чистая версия Python.
Версия numpy
использует трюк с скользящим окном и трюк argmax . Но опять же, оказывается, что это намного медленнее, чем даже в чистой версии Python, примерно в 30 раз.
def rolling_window(a, window):
a = numpy.ascontiguousarray(a) # This approach requires a C-ordered array
shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
strides = a.strides + (a.strides[-1],)
return numpy.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
def find_reps_numpy(a, max_len):
windows = rolling_window(a, max_len)
return (windows == windows[:, 0:1]).sum(axis=1).argmax()
Я проверил оба этих варианта в сравнении с первой версией функции без сопряжения. (Для тестирования я использовал функцию %%timeit
компании Jupyter.)
a = numpy.random.randint(0, 100, 1000000)
%%timeit
find_reps_numpy(a, 3)
28.6 ms ± 553 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
find_reps_orig(a, 3)
4.04 ms ± 40.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%%timeit
find_reps_numba(a, 3)
8.29 µs ± 89.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
Обратите внимание, что эти цифры могут сильно различаться в зависимости от того, как глубоко в a
функции должны искать. Чтобы лучше оценить ожидаемую производительность, мы можем каждый раз заново генерировать новый набор случайных чисел, но это трудно сделать без учета этого шага во времени. Поэтому для сравнения здесь я включаю время, необходимое для генерации случайного массива без выполнения чего-либо еще:
a = numpy.random.randint(0, 100, 1000000)
9.91 ms ± 129 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
a = numpy.random.randint(0, 100, 1000000)
find_reps_numpy(a, 3)
38.2 ms ± 453 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
a = numpy.random.randint(0, 100, 1000000)
find_reps_orig(a, 3)
13.7 ms ± 404 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
a = numpy.random.randint(0, 100, 1000000)
find_reps_numba(a, 3)
9.87 ms ± 124 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Как видите, find_reps_numba
настолько быстр, что разница во времени, необходимом для запуска numpy.random.randint(0, 100, 1000000)
намного больше - отсюда и иллюзорное ускорение между первым и последним тестами.
Итак, большая мораль этой истории в том, что решения numpy
не всегда являются лучшими. Иногда даже чистый Python быстрее. В этих случаях numba
в режиме nopython
может быть лучшим вариантом на сегодняшний день.