Производительность вложенного цикла в numba

По соображениям производительности я начал использовать Numba помимо NumPy. Мой алгоритм Numba работает, но я чувствую, что он должен быть быстрее. Есть один момент, который замедляет его. Вот фрагмент кода:

@nb.njit def rfunc1(ws, a, l): gn = a**l for x1 in range(gn): for x2 in range(gn): for x3 in range(gn): y = 0.0 for i in range(1, l): if numpy.all(ws[x1][0:i] == ws[x2][0:i]) and numpy.all(ws[x1][i:l] == ws[x3][i:l]): y += 1 if numpy.all(ws[x1][0:i] == ws[x2][0:i]) and numpy.all(ws[x1][i:l] == ws[x3][i:l]): y += 1 

По-моему, команда if замедляет ее. Есть ли способ лучше? (То, что я пытаюсь достичь здесь, связано с предыдущей проблемой: Count для одиночных кроссоверов ) ws – массив NumPy размера (gn, l) содержащий 0 и 1

2 Solutions collect form web for “Производительность вложенного цикла в numba”

Учитывая логику того, чтобы все элементы были равны, вы можете воспользоваться тем фактом, что, если они не равны, вы можете выполнить короткое замыкание (т.е. прекратить сравнение) вычисления. Я немного изменил вашу оригинальную функцию, чтобы (1) вы не повторяли одно и то же сравнение дважды, и (2) суммировали y по всем вложенным циклам, чтобы можно было сравнить результат:

 @nb.njit def rfunc1(ws, a, l): gn = a**l ysum = 0 for x1 in range(gn): for x2 in range(gn): for x3 in range(gn): y = 0.0 for i in range(1, l): if np.all(ws[x1][0:i] == ws[x2][0:i]) and np.all(ws[x1][i:l] == ws[x3][i:l]): y += 1 ysum += 1 return ysum @nb.njit def rfunc2(ws, a, l): gn = a**l ysum = 0 for x1 in range(gn): for x2 in range(gn): for x3 in range(gn): y = 0.0 for i in range(1, l): incr_y = True for j in range(i): if ws[x1,j] != ws[x2,j]: incr_y = False break if incr_y is True: for j in range(i,l): if ws[x1,j] != ws[x3,j]: incr_y = False break if incr_y is True: y += 1 ysum += 1 return ysum 

Я не знаю, как выглядит полная функция, но, надеюсь, это поможет вам начать работу по правильному пути.

Теперь для некоторых таймингов:

 l = 7 a = 2 gn = a**l ws = np.random.randint(0,2,size=(gn,l)) In [23]: %timeit rfunc1(ws, a , l) 1 loop, best of 3: 2.11 s per loop %timeit rfunc2(ws, a , l) 1 loop, best of 3: 39.9 ms per loop In [27]: rfunc1(ws, a , l) Out[27]: 131919 In [30]: rfunc2(ws, a , l) Out[30]: 131919 

Это дает вам 50-кратное ускорение.

Вместо того, чтобы просто «иметь чувство», где ваше узкое место, почему бы не профилировать ваш код и не найти именно там?

Первой целью профилирования является проверка репрезентативной системы для определения того, что происходит медленно (или с использованием слишком большого количества ОЗУ или чрезмерного количества операций ввода-вывода или сетевого ввода-вывода).

Профилирование обычно добавляет накладные расходы (от 10x до 100x замедление может быть типичным), и вы по-прежнему хотите, чтобы ваш код использовался как можно ближе к реальной ситуации. Извлеките тестовый кейс и изолируйте часть системы, которую необходимо протестировать. Предпочтительно, это будет написано уже в своем собственном наборе модулей.

Основные методы включают в %timeit магию %timeit в IPython, time.time(), и time.time(), timing decorator (см. Пример ниже). Вы можете использовать эти методы для понимания поведения операторов и функций.

Затем у вас есть cProfile который даст вам представление на высоком уровне проблемы, чтобы вы могли обратить ваше внимание на критические функции.

Затем просмотрите line_profiler, который будет профилировать выбранные вами функции по очереди. Результат будет включать в себя подсчет количества раз, когда вызывается каждая строка, и процент времени, затраченного на каждую строку. Это именно то, что вам нужно, чтобы понять, что работает медленно и почему.

perf stat помогает понять количество инструкций, которые в конечном итоге выполняются на процессоре, и насколько эффективно используются кеши процессора. Это позволяет осуществлять расширенную настройку матричных операций.

heapy может отслеживать все объекты внутри памяти Python. Это отлично подходит для поиска странных утечек памяти. Если вы работаете с многолетними системами, то dowser заинтересовал dowser : он позволяет вам интроспективно dowser живые объекты в длительном процессе через интерфейс веб-браузера.

Чтобы помочь вам понять, почему ваше использование ОЗУ очень велико, проверьте memory_profiler. Это особенно полезно для отслеживания использования ОЗУ с течением времени на маркированной диаграмме, поэтому вы можете объяснить коллегам (или вам самим), почему некоторые функции используют больше ОЗУ, чем ожидалось.

Пример: определение декоратора для автоматизации измерений времени

 from functools import wraps def timefn(fn): @wraps(fn) def measure_time(*args, **kwargs): t1 = time.time() result = fn(*args, **kwargs) t2 = time.time() print ("@timefn:" + fn.func_name + " took " + str(t2 - t1) + " seconds") return result return measure_time @timefn def your_func(var1, var2): ... 

Для получения дополнительной информации я предлагаю прочитать высокопроизводительный Python (Micha Gorelick, Ian Ozsvald), из которого было получено исходное.

Interesting Posts
Python - лучший язык программирования в мире.