Локальные массивы потока в cython's prange без огромного выделения памяти

У меня есть несколько независимых вычислений, которые я хотел бы выполнять параллельно с помощью Cython.

Сейчас я использую этот подход:

import numpy as np
cimport numpy as cnp
from cython.parallel import prange

[...]

cdef cnp.ndarray[cnp.float64_t, ndim=2] temporary_variable = \
    np.zeros((INPUT_SIZE, RESULT_SIZE), np.float64)
cdef cnp.ndarray[cnp.float64_t, ndim=2] result = \
    np.zeros((INPUT_SIZE, RESULT_SIZE), np.float64)

for i in prange(INPUT_SIZE, nogil=True):
    for j in range(RESULT_SIZE):
        [...]
        temporary_variable[i, j] = some_very_heavy_mathematics(my_input_array)
        result[i, j] = some_more_maths(temporary_variable[i, j])

Эта методология работает, но моя проблема связана с тем, что на самом деле мне нужно несколько temporary_variable. Это приводит к огромному использованию памяти при увеличении INPUT_SIZE. Но я считаю, что действительно нужна временная переменная в каждом потоке.

Я столкнулся с ограничением Cython prange и мне нужно изучить правильный C, или я делаю/понимаю что-то ужасно неправильное?

EDIT: функции, которые я искал, были openmp.omp_get_max_threads() и openmp.omp_get_thread_num() для создания временного массива разумного размера. Я должен был cimport openmp сначала.


person nicoco    schedule 30.08.2018    source источник
comment
Cython обычно правильно назначает локальные переменные потока (если вы просто сделаете его скаляром, а не массивом). В случае неудачи посмотрите, сможете ли вы поместить тело цикла в отдельную функцию со своими локальными переменными.   -  person DavidW    schedule 31.08.2018
comment
@DavidW Спасибо за вашу помощь. Вероятно, мне следует разделить мой код на более мелкие функции, потому что мне нужны массивы. Я изо всех сил пытаюсь понять, как это сделать, к сожалению.   -  person nicoco    schedule 31.08.2018
comment
Я постараюсь написать заметку с полным ответом в ближайшие несколько дней, но мое предложение заключалось в том, что если две показанные строки (temp_var = ... и some_more_maths(temp_var)) содержатся в функции, то переменная является локальной для функции (поэтому определенно поток локальный)   -  person DavidW    schedule 31.08.2018


Ответы (1)


Это то, что Cython пытается обнаружить, и в большинстве случаев это удается. Если взять более полный пример кода:

import numpy as np
from cython.parallel import prange

cdef double f1(double[:,:] x, int i, int j) nogil:
    return 2*x[i,j]

cdef double f2(double y) nogil:
    return y+10

def example_function(double[:,:] arr_in):
    cdef double[:,:] result = np.zeros(arr_in.shape)
    cdef double temporary_variable
    cdef int i,j
    for i in prange(arr_in.shape[0], nogil=True):
        for j in range(arr_in.shape[1]):
            temporary_variable = f1(arr_in,i,j)
            result[i,j] = f2(temporary_variable)
    return result

(это в основном то же самое, что и ваше, но компилируемое). Это компилируется в код C:

#pragma omp for firstprivate(__pyx_v_i) lastprivate(__pyx_v_i) lastprivate(__pyx_v_j) lastprivate(__pyx_v_temporary_variable)
                #endif /* _OPENMP */
                for (__pyx_t_8 = 0; __pyx_t_8 < __pyx_t_9; __pyx_t_8++){

Вы можете видеть, что temporary_variable установлен как локальный для потока. Если Cython не определяет это правильно (я часто слишком стремлюсь сделать переменные сокращением), то мое предложение состоит в том, чтобы инкапсулировать (некоторые) содержимое цикла в функцию:

cdef double loop_contents(double[:,:] arr_in, int i, int j) nogil:
    cdef double temporary_variable
    temporary_variable = f1(arr_in,i,j)
    return f2(temporary_variable)

Это заставляет temporary_variable быть локальным для функции (и, следовательно, для потока).


Что касается создания локального массива потока: я не совсем понимаю, что вы хотите сделать, но я попытаюсь предположить...

  1. Я не верю, что можно создать локальное представление памяти потока.
  2. Вы можете создать локальный для потока массив C с malloc и free, но если вы не хорошо понимаете C, я бы не рекомендовал это делать.
  3. Самый простой способ — выделить 2D-массив, в котором у вас есть один столбец для каждого потока. Массив является общим, но поскольку каждый поток касается только своего собственного столбца, это не имеет значения. Простой пример:

    cdef double[:] f1(double[:,:] x, int i) nogil:
        return x[i,:]
    
    def example_function(double[:,:] arr_in):
        cdef double[:,:] temporary_variable = np.zeros((arr_in.shape[1],openmp.omp_get_max_threads()))
        cdef int i
        for i in prange(arr_in.shape[0],nogil=True):
            temporary_variable[:,openmp.omp_get_thread_num()] = f1(arr_in,i)
    
person DavidW    schedule 01.09.2018
comment
Большое спасибо за ваш подробный ответ. Однако я до сих пор не понимаю, как сделать temporary_variable локальным для потока массивом (см. редактирование в моем посте). Возможно, это не то, что можно сделать в cython, и мне нужно реорганизовать свой код, чтобы избежать необходимости в локальных массивах потоков. - person nicoco; 03.09.2018
comment
Спасибо еще раз. № 3 — это то, что я уже делал; проблема в том, что для больших входных данных требуется огромное количество оперативной памяти. Я думаю, что № 2 — это то, что мне нужно сделать, но сначала мне нужно улучшить свои навыки C. Прямо сейчас я просто отказался от параллелизма для этого конкретного случая, и это дает мне повод позависать на SO, ожидая моих результатов. :о) - person nicoco; 04.09.2018
comment
Это не то же самое, что вы показываете в вопросе. Вы создаете массив input_size x result_size. Я создаю массив input_size x number_of_threads. number_of_threads обычно достаточно мал (4 или 8?). - person DavidW; 04.09.2018
comment
О, извините, я пропустил это. Думаю, это именно то, что я искал. Я попробую как можно скорее. Большое спасибо. - person nicoco; 04.09.2018
comment
Есть ли смысл что-то вернуть? Я адаптировал cdef к возвращаемому типу void и передал ему temporary_variable, поэтому cython -a становится полностью белым, и, похоже, это работает. - person nicoco; 04.09.2018
comment
Ваш способ должен работать нормально - вам не нужно возвращать его. - person DavidW; 04.09.2018