11

Ho una funzione foo che prende un puntatore alla memoria come argomento ed entrambe le scrive e legge a quel ricordo:Cython memoria condivisa in cython.parallel.prange - blocco

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

Sto allocando a data come so:

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) 

La mia domanda è ora: come trattano i diversi thread? La mia ipotesi è che la memoria puntata da data sarà condivisa da tutti i thread e "simultaneamente" letti o scritti all'interno della funzione foo. In questo modo tutti i risultati verrebbero compromessi dal momento che non è possibile fare affidamento su un valore dati impostato in precedenza (entro foo)? La mia ipotesi è corretta o c'è qualche cintura di sicurezza magica implementata nel cython-compilatore?

Grazie mille in anticipo.

risposta

2

Suppongo che senza blocchi di sincronizzazione di lettura o scrittura su data i thread leggano/scrivano nella posizione di memoria e sovrascrivano le modifiche reciproche. Non otterrai risultati coerenti senza alcun tipo di sincronizzazione.

Sebbene i documenti (http://docs.cython.org/src/userguide/parallelism.html) sembrano suggerire che OpenMP (il backend predefinito) crea automaticamente locali di thread.

+0

** NB: ** Non ho OpenMP su nessuno dei miei sistemi, quindi non posso testarlo facilmente. –

8

Un buon modo è quello di avere la matrice principale al di fuori dei filetti. Quindi dai a ciascun thread il puntatore alla parte dell'array principale che deve essere calcolato dal thread.

L'esempio seguente è un'implementazione di una moltiplicazione matrice (simile a dot per array 2-D) dove:

c = a*b 

Il parallelismo qui viene implementato sulle righe di a. Controllare come i puntatori vengono passati alla funzione multiply per consentire ai diversi thread di condividere gli stessi matrici.

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] 

Per controllare è possibile utilizzare questo script:

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) 

Dove sul mio computer dà:

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

Se il numero di righe di a era più piccolo allora il numero dei colli o il numero di colonne in b il mydot sarebbe peggio, che richiede un controllo migliore su quale dimensione per rendere il parallelismo.