11

Mam funkcji foo, która przyjmuje wskaźnik do pamięci jako argument i obu zapisuje i odczytuje do tej pamięci:Cython pamięci współdzielonej w cython.parallel.prange - blok

cdef void foo (double *data): 
    data[some_index_int] = some_value_double 
    do_something_dependent_on (data) 

ja przyznając data jak więc:

cdef int N = some_int 
cdef double *data = <double*> malloc (N * sizeof (double)) 

cdef int i 
for i in cython.parallel.prange (N, nogil=True): 
    foo (data) 

readout (data) 

Moje pytanie brzmi teraz: jak traktują to różne wątki? Domyślam się, że pamięć wskazywana przez data będzie współdzielona przez wszystkie wątki i "jednocześnie" odczytywana lub zapisywana podczas działania funkcji foo. To zepsułoby wszystkie wyniki, ponieważ nie można polegać na wcześniej ustawionej wartości danych (w ramach foo)? Czy moje przypuszczenia są słuszne, czy też jakiś magiczny pas bezpieczeństwa został zaimplementowany w kompilatorze cythonowym?

Dziękuję bardzo z góry.

Odpowiedz

2

Zakładam, że bez synchronizacji do odczytu i zapisu blokady do data wątków będą odczytywać/zapisywać w miejscu pamięci i zastępować nawzajem swoje zmiany. Nie uzyskasz spójnych wyników bez synchronizacji.

Chociaż dokumenty() wydają się sugerować, że OpenMP (domyślny backend) automatycznie tworzy loci wątków.

+0

** Uwaga: ** Nie mam OpenMP na żadnym z moich systemów, więc nie mogę tego przetestować z łatwością. –

8

Dobrym sposobem jest doprowadzenie głównej tablicy do alokowania w wątkach. Następnie nadajesz każdemu wątkowi wskaźnik do części głównej tablicy, która powinna zostać obliczona przez wątek.

Poniższy przykład implementacja mnożenie macierzy (podobny do dot tablic 2-D), w którym:

c = a*b 

Równoległość tu realizowane przez rzędy a. Sprawdź, w jaki sposób wskaźniki są przekazywane do funkcji multiply, aby różne wątki mogły współdzielić te same tablice.

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

ctypedef np.double_t cDOUBLE 
DOUBLE = np.float64 


def mydot(np.ndarray[cDOUBLE, ndim=2] a, np.ndarray[cDOUBLE, ndim=2] b): 
    cdef np.ndarray[cDOUBLE, ndim=2] c 
    cdef int i, M, N, K 

    c = np.zeros((a.shape[0], b.shape[1]), dtype=DOUBLE) 
    M = a.shape[0] 
    N = a.shape[1] 
    K = b.shape[1] 

    for i in prange(M, nogil=True): 
     multiply(&a[i,0], &b[0,0], &c[i,0], N, K) 

    return c 


@cython.wraparound(False) 
@cython.boundscheck(False) 
@cython.nonecheck(False) 
cdef void multiply(double *a, double *b, double *c, int N, int K) nogil: 
    cdef int j, k 
    for j in range(N): 
     for k in range(K): 
      c[k] += a[j]*b[k+j*K] 

Aby sprawdzić można użyć tego skryptu:

import time 

import numpy as np 

import _stack 

a = np.random.random((10000,500)) 
b = np.random.random((500,2000)) 

t = time.clock() 
c = np.dot(a, b) 
print('finished dot: {} s'.format(time.clock()-t)) 

t = time.clock() 
c2 = _stack.mydot(a, b) 
print('finished mydot: {} s'.format(time.clock()-t)) 

print 'Passed test:', np.allclose(c, c2) 

Gdzie na moim komputerze to otrzymujemy:

finished dot: 0.601547366526 s 
finished mydot: 2.834147917 s 
Passed test: True 

Jeżeli liczba rzędów a był mniejszy niż wtedy liczby przełęcze lub liczba kolumn w b jest gorsza, co wymaga lepszego sprawdzenia, który wymiar tworzy paralelizm.