numba не распараллеливает диапазон

У меня есть циклы в моем коде, которые я хочу распараллелить

from numba import njit, prange
from time import time


@njit
def f1(n):
    s = 0
    for i in range(n):
        for j in range(n):
            for k in range(n):
                s += (i * k < j * j) - (i * k > j * j)
    return s


@njit
def f2(n):
    s = 0
    for i in prange(n):
        for j in prange(n):
            for k in prange(n):
                s += (i * k < j * j) - (i * k > j * j)
    return s


@njit(parallel=True)
def f3(n):
    s = 0
    for i in range(n):
        for j in range(n):
            for k in range(n):
                s += (i * k < j * j) - (i * k > j * j)
    return s


@njit(parallel=True)
def f4(n):
    s = 0
    for i in prange(n):
        for j in prange(n):
            for k in prange(n):
                s += (i * k < j * j) - (i * k > j * j)
    return s


for f in [f1, f2, f3, f4]:
    d = time()
    f(2500)
    print('%.02f' % (time() - d))

Я получаю время:

27.44
27.34
26.83
13.05

Я проверил активность своего процессора, и если первые три функции были на 100%, то четвертая на ~300%.

Я не понимаю, почему указание parallel ничего не изменило, и нужно использовать prange. В документе есть пример с диапазоном.


person Labo    schedule 09.05.2018    source источник
comment
Интересно. Это кажется мне ошибкой. Я пробовал все комбинации jit и njit, с nogil и без него, и всегда получаю 11 секунд от f1 до f3 и около 2,5 с от f4. Но afaik parallel=True все еще экспериментальная функция. Возможно, вам следует отправить отчет об ошибке на numba github.   -  person JE_Muc    schedule 09.05.2018
comment
@Scotty1- Спасибо за вашу помощь, я подожду один день, прежде чем сообщить. Влияет ли ногил на производительность в целом?   -  person Labo    schedule 09.05.2018
comment
По моему опыту это не так, но я не использую параллельные потоки. Мне просто нравится, когда он проверяет, может ли работать параллельно без каких-либо конфликтов.   -  person JE_Muc    schedule 09.05.2018
comment
Не используйте prange для всех циклов (вы бы не сделали этого и с OpenMP). Обычно целью является SIMD-векторизация внутренних циклов и распараллеливание внешних циклов для достижения наилучшей производительности. Ключевое слово fastmath также подходит для этого типа операций. Это предложит более алгебраически правильную оптимизацию, которая может повлиять на числовую точность (суммирование с использованием частичных сумм).   -  person max9111    schedule 15.05.2018


Ответы (2)


Из документации Numba:

Экспериментальная опция parallel=True для @jit попытается оптимизировать операции с массивами и выполнять их параллельно. Он также добавляет поддержку prange() для явного распараллеливания цикла.

Теперь, поскольку вы не выполняете никаких операций с массивами в своей функции, Numba ничего не может распараллелить без явной маркировки циклов с помощью prange.

Так что просто чтобы не было путаницы. Numba разделит ваш цикл на потоки только в том случае, если вы установите parallel=True в оформлении, и явно пометит циклы путем изменения; диапазон -> диапазон.

В вашем f4() вы поместили prange во все циклы for, я бы рекомендовал помещать prange только в самый внешний цикл, потому что вы не хотите рисковать порождением потоков из потоков. То есть:

@njit(parallel=True)
def f5(n):
    s = 0
    for i in prange(n):
        for j in range(n):
            for k in range(n):
                s += (i * k < j * j) - (i * k > j * j)
    return s
person Erik Kjellgren    schedule 09.05.2018
comment
Разве s не считается 0-мерным массивом? Изменится ли это, если я инициализирую его как numpy ndarray? Вы уверены насчет вложенного prange? Я не думаю, что это работает так же, как OMP (например). Вы имели в виду процесс вместо потока? - person Labo; 10.05.2018

Я создал задачу на Github, и один из участников очень точно ответил (https://github.com/numba/numba/issues/2960#issuecomment-388767318).

Если он захочет опубликовать свой ответ здесь, я приму его.

person Labo    schedule 14.05.2018