В последние несколько дней я работал над улучшением времени выполнения функции python, которая требует многого использования функции остатка (%) между прочим. Мой основной тестовый пример - это массив с размером в 80 000 элементов (монотонно возрастающий), с 10000 итерациями, хотя я также пробовал и другие размеры.
В конце концов я достиг точки, где остаточная функция является основным узким местом и пробовала различные решения. Это поведение, которое я обнаружил при запуске следующего кода:
import numpy as np
import time
a = np.random.rand(80000)
a = np.cumsum(a)
d = 3
start_time1 = time.time()
for i in range(10000):
b = a % d
d += 0.001
end_time1 = time.time()
d = 3
start_time2 = time.time()
for i in range(10000):
b = a - (d * np.floor(a / d))
d += 0.001
end_time2 = time.time()
print((end_time1 - start_time1) / 10000)
print((end_time2 - start_time2) / 10000)
Выход:
0.0031344462633132934
0.00022937238216400147
при увеличении размера массива до 800 000:
0.014903099656105041
0.010498356819152833
(Для этого сообщения я запускал код только один раз для фактического вывода, пытаясь понять проблему, я получал эти результаты последовательно).
Хотя это решает мою проблему во время выполнения - мне трудно понять, почему. Я что-то пропустил? Единственное отличие, о котором я могу думать, это накладные расходы на дополнительный вызов функции, но первый случай довольно экстремальный (и 1.5x время исполнения тоже недостаточно), и если бы это было так, я бы подумал, что существование функция np.remainder
бессмысленна.
Изменить: я пробовал тестировать один и тот же код с помощью не-numpy-петель:
import numpy as np
import time
def pythonic_remainder(array, d):
b = np.zeros(len(array))
for i in range(len(array)):
b[i] = array[i] % d
def split_pythonic_remainder(array, d):
b = np.zeros(len(array))
for i in range(len(array)):
b[i] = array[i] - (d * np.floor(array[i] / d))
def split_remainder(a, d):
return a - (d * np.floor(a / d))
def divide(array, iterations, action):
d = 3
for i in range(iterations):
b = action(array, d)
d += 0.001
a = np.random.rand(80000)
a = np.cumsum(a)
start_time = time.time()
divide(a, 10000, split_remainder)
print((time.time() - start_time) / 10000)
start_time = time.time()
divide(a, 10000, np.remainder)
print((time.time() - start_time) / 10000)
start_time = time.time()
divide(a, 10000, pythonic_remainder)
print((time.time() - start_time) / 10000)
start_time = time.time()
divide(a, 10000, split_pythonic_remainder)
print((time.time() - start_time) / 10000)
В результате я получаю:
0.0003770533800125122
0.003932329940795899
0.018835473942756652
0.10940513386726379
Мне интересно, что в случае, отличном от numpy, верно обратное.