Почему numpy медленнее, чем для цикла

У меня есть функция, использующая некоторые для циклов, и я хотел бы улучшить скорость с помощью numpy. Но, похоже, это не делает трюк, поскольку ухабистая версия выглядит в 2 раза медленнее. Вот код:

import numpy as np import itertools import timeit def func(): sample = np.random.random_sample((100, 2)) disc1 = 0 disc2 = 0 n_sample = len(sample) dim = sample.shape[1] for i in range(n_sample): prod = 1 for k in range(dim): sub = np.abs(sample[i, k] - 0.5) prod *= 1 + 0.5 * sub - 0.5 * sub ** 2 disc1 += prod for i, j in itertools.product(range(n_sample), range(n_sample)): prod = 1 for k in range(dim): a = 0.5 * np.abs(sample[i, k] - 0.5) b = 0.5 * np.abs(sample[j, k] - 0.5) c = 0.5 * np.abs(sample[i, k] - sample[j, k]) prod *= 1 + a + b - c disc2 += prod c2 = (13 / 12) ** dim - 2 / n_sample * disc1 + 1 / (n_sample ** 2) * disc2 def func_numpy(): sample = np.random.random_sample((100, 2)) disc1 = 0 disc2 = 0 n_sample = len(sample) dim = sample.shape[1] disc1 = np.sum(np.prod(1 + 0.5 * np.abs(sample - 0.5) - 0.5 * np.abs(sample - 0.5) ** 2, axis=1)) for i, j in itertools.product(range(n_sample), range(n_sample)): disc2 += np.prod(1 + 0.5 * np.abs(sample[i] - 0.5) + 0.5 * np.abs(sample[j] - 0.5) - 0.5 * np.abs(sample[i] - sample[j])) c2 = (13 / 12) ** dim - 2 / n_sample * disc1 + 1 / (n_sample ** 2) * disc2 print('Normal function time: ' , timeit.repeat('func()', number=20, repeat=5, setup="from __main__ import func")) print('numpy function time: ', timeit.repeat('func_numpy()', number=20, repeat=5, setup="from __main__ import func_numpy")) 

Выход синхронизации:

 Normal function time: [2.831496894999873, 2.832342429959681, 2.8009242500411347, 2.8075121529982425, 2.824807019031141] numpy function time: [5.154757721000351, 5.2011515340418555, 5.148996959964279, 5.095560318033677, 5.125199959962629] 

Что мне здесь не хватает? Я знаю, что узким местом является часть itertools, потому что раньше у меня цикл 100x100x2 вместо цикла 100×2. Вы видите другой способ сделать это?

2 Solutions collect form web for “Почему numpy медленнее, чем для цикла”

С NumPy нужно смотреть на векторизация вещей, и мы могли бы сделать это здесь.

При более внимательном рассмотрении части цикла мы повторяем по первой оси входных данных дважды с запуском этого цикла:

 for i, j in itertools.product(range(n_sample), range(n_sample)): 

Мы могли бы преобразовать эти итерации в векторизованные операции, как только мы разрешим broadcasting обрабатывать их.

Теперь, чтобы иметь полностью векторизованное решение, нам понадобится намного больше пространства памяти, в частности (N,N,M) , где (N,M) является формой входных данных.

Другим заметным аспектом здесь является то, что на каждой итерации мы не выполняем много работы, так как мы выполняем операции над каждой строкой, и каждая строка содержит только 2 элемента для данного образца. Итак, идея, которая выходит, заключается в том, что мы могли бы запустить цикл вдоль M , чтобы на каждой итерации мы вычисляли prod и накапливали. Таким образом, для данного образца это всего лишь две петлевые итерации.

Выйдя из цикла, у нас будет накопленный prod , который просто требует суммирования для disc2 в качестве конечного результата.

Вот реализация для реализации вышеупомянутых идей –

 prod_arr = 1 for i in range(sample.shape[1]): si = sample[:,i] prod_arr *= 1 + 0.5 * np.abs(si[:,None] - 0.5) + 0.5 * np.abs(si - 0.5) - \ 0.5 * np.abs(si[:,None] - si) disc2 = prod_arr.sum() 

Тест времени выполнения

Вырезанная версия части цикла с оригинальным подходом и модифицированными версиями как подходы перечислены ниже:

 def org_app(sample): disc2 = 0 n_sample = len(sample) for i, j in itertools.product(range(n_sample), range(n_sample)): disc2 += np.prod(1 + 0.5 * np.abs(sample[i] - 0.5) + 0.5 * \ np.abs(sample[j] - 0.5) - 0.5 * np.abs(sample[i] - sample[j])) return disc2 def mod_app(sample): prod_arr = 1 for i in range(sample.shape[1]): si = sample[:,i] prod_arr *= 1 + 0.5 * np.abs(si[:,None] - 0.5) + 0.5 * np.abs(si - 0.5) - \ 0.5 * np.abs(si[:,None] - si) disc2 = prod_arr.sum() return disc2 

Сроки и проверка –

 In [10]: sample = np.random.random_sample((100, 2)) In [11]: org_app(sample) Out[11]: 11934.878683659041 In [12]: mod_app(sample) Out[12]: 11934.878683659068 In [14]: %timeit org_app(sample) 10 loops, best of 3: 84.4 ms per loop In [15]: %timeit mod_app(sample) 10000 loops, best of 3: 94.6 µs per loop 

Около 900x ! Ну, это должно быть достаточно мотивирующим, и мы надеемся, что когда-нибудь посмотрим, что делать.

Как я уже упоминал в комментариях, ваши решения не очень оптимальны, и на самом деле нет смысла сравнивать не идеальные подходы.

С одной стороны, итерация или индексирование отдельных элементов массива NumPy происходит очень медленно. Недавно я ответил на вопрос, содержащий много деталей (если вас это интересует, вы можете взглянуть на него: «Преобразование np-массива в набор занимает слишком много времени» ). Таким образом, подход Python может быть быстрее, просто преобразовывая array в list :

 def func(): sample = np.random.random_sample((100, 2)) disc1 = 0 n_sample = len(sample) dim = sample.shape[1] sample = sample.tolist() # converted to list for i in range(n_sample): prod = 1 for item in sample[i]: sub = abs(item - 0.5) prod *= 1 + 0.5 * sub - 0.5 * sub ** 2 disc1 += prod disc2 = 0 for i, j in itertools.product(range(n_sample), range(n_sample)): prod = 1 for k in range(dim): a = 0.5 * abs(sample[i][k] - 0.5) b = 0.5 * abs(sample[j][k] - 0.5) c = 0.5 * abs(sample[i][k] - sample[j][k]) prod *= 1 + a + b - c disc2 += prod c2 = (13 / 12) ** dim - 2 / n_sample * disc1 + 1 / (n_sample ** 2) * disc2 

Я также заменил вызовы np.abs нормальным abs . Нормальный abs имеет более низкие накладные расходы! А также изменили некоторые другие части. В итоге это более чем в 10-20 раз быстрее, чем ваш оригинальный «нормальный» подход.

У меня не было времени, чтобы проверить подход NumPy, и @Divarkar уже включил в себя действительно хороший и оптимизированный подход. Сравнивая два подхода:

 def func_numpy(): sample = np.random.random_sample((100, 2)) disc1 = 0 disc2 = 0 n_sample = len(sample) dim = sample.shape[1] disc1 = np.sum(np.prod(1 + 0.5 * np.abs(sample - 0.5) - 0.5 * np.abs(sample - 0.5) ** 2, axis=1)) prod_arr = 1 for i in range(sample.shape[1]): s0 = sample[:,i] prod_arr *= (1 + 0.5 * np.abs(s0[:,None] - 0.5) + 0.5 * np.abs(s0 - 0.5) - 0.5 * np.abs(s0[:,None] - s0)) disc2 = prod_arr.sum() c2 = (13 / 12) ** dim - 2 / n_sample * disc1 + 1 / (n_sample ** 2) * disc2 print('Normal function time: ' , timeit.repeat('func()', number=20, repeat=3, setup="from __main__ import func")) # Normal function time: [1.4846746248249474, 1.5018398493266432, 1.5476674017127152] print('numpy function time: ', timeit.repeat('func_numpy()', number=20, repeat=3, setup="from __main__ import func_numpy")) # numpy function time: [0.020140038561976326, 0.016502230831292763, 0.016452520269695015] 

Таким образом, оптимизированный подход NumPy может определенно превзойти «оптимизированный» подход Python. Это почти в 100 раз быстрее. Если вы захотите еще быстрее, вы можете использовать numba в слегка модифицированной версии чистого кода python:

 import numba as nb @nb.njit def func_numba(): sample = np.random.random_sample((100, 2)) disc1 = 0 n_sample = len(sample) dim = sample.shape[1] for i in range(n_sample): prod = 1 for item in sample[i]: sub = abs(item - 0.5) prod *= 1 + 0.5 * sub - 0.5 * sub ** 2 disc1 += prod disc2 = 0 for i in range(n_sample): for j in range(n_sample): prod = 1 for k in range(dim): a = 0.5 * abs(sample[i,k] - 0.5) b = 0.5 * abs(sample[j,k] - 0.5) c = 0.5 * abs(sample[i,k] - sample[j,k]) prod *= 1 + a + b - c disc2 += prod return (13 / 12) ** dim - 2 / n_sample * disc1 + 1 / (n_sample ** 2) * disc2 func_numba() print('numba function time: ' , timeit.repeat('func_numba()', number=20, repeat=3, setup="from __main__ import func_numba")) # numba function time: [0.003022848984983284, 0.0030429566279508435, 0.004060626777572907] 

Это почти на 8-10 раз быстрее, чем подход NumPy.

  • Определите среднее значение «данных», где максимальное число CONTINUOUS cond = True
  • Как профилировать приложение django в отношении времени выполнения?
  • Графические плагины процессора для Python
  • Самый быстрый способ фильтровать списки списков на основе третьего списка?
  • Являются ли кортежи более эффективными, чем списки в Python?
  • Читайте в большом файле и создайте словарь
  • Python: Есть ли способ сохранить автоматическое преобразование из int в long int из происходящего?
  • Установка значений в подмножестве Pandas DataFrame (копия) медленная
  • Python - лучший язык программирования в мире.