1

cython을 사용하여 함수를 올바르게 병렬 처리하는 데 어려움을 겪고 있습니다. 기본적으로 문제는 일부 데이터를 보관하는 것입니다. 실제 코드는 조금 긴했지만 결국은 이런 일을 수행합니다cython에서 for 루프를 Parallelising : prange 너머

def bin_var(double[:] dist, 
      double[:] values, 
      double[:] bin_def, 
      double[:] varg, long[:] count): 

    dbin = (bin_def[1] - bin_def[0])/bin_def[2] 

    for n1 in range(values.size): 
      if (dist[n1] < bin_def[0]) or (dist[n1] >= bin_def[1]): 
       continue 
      else: 
       ni = int((dist - bin_def[0])/dbin) 
       count[ni] += 1 
       varg[ni] += calc_something(values[ni]) 

    # compute the mean 
    for n1 in range(int(bin_def[2])): 
     varg[ni] /= count[ni] 

이 코드는 간단한 parallelisation (valuesdist이 매우 큰)라는 것으로를 하나 첫 번째 for 루프를 분할 할 필요가 별도의 프로세스를 통해 각각은 countvarg 어레이의 자체 버전에서 작업합니다. 이것이 완료되면 두 번째 for 루프 (훨씬 짧음) 전에 countvarg의 다른 버전을 합하여 모든 것을 결합해야합니다.

즉, 효율적으로 이것을 구현하는 방법을 이틀간에 알고 싶습니다. cython에서이 언어의 최신 버전을 사용할 수 없다고 생각하기 시작했습니다. 첫 번째 루프에 대해 prange에서 cython.parallel까지만 사용하면 다른 스레드에서 ni, countvarg의 동시 액세스가 있기 때문에 정확한 결과를 제공하지 않습니다.

cython 병렬 지원이 정말로 제한적입니까? 나는 그런 좋은 속도 향상은 단일 스레드 얻고 있었다, 난 그냥

가 가

답변

2

내가 여기 세 가지 옵션을 생각할 수 있습니다 ... 내가 계속할 수 희망 :

  1. 사용 GIL은 += 단일 수행되는 것을 보장하기 위해 스레드 :

    varg_ni = calc_something(values[ni]) # keep this out 
           # of the single threaded block... 
    with gil: 
        count[ni] += 1 
        varg[ni] += varg_ni 
    

    이 용이하고 calc_something에서 수행 된 작업 제공 나쁘지되지 않습니다 합리적으로 큰

  2. countvarg 2D 배열을 각 스레드가 다른 열에 쓰도록하십시오. 이후에 두 번째 차원에 따라 합계 :

    # rough, untested outline.... 
    
    # might need to go in a `with parallel()` block 
    num_threads = openmp.omp_get_num_threads() 
    
    cdef double[:,:] count_tmp = np.zeros((count.shape[0],num_threads)) 
    cdef double[:,:] varg_tmp = np.zeros((varg.shape[0],num_threads)) 
    
    # then in the loop: 
    count_tmp[ni,cython.parallel.threadid()] += 1 
    varg_tmp[ni,cython.parallel.threadid()] += calc_something(values[ni]) 
    
    # after the loop: 
    count[:] = np.sum(count_tmp,axis=1) 
    varg[:] = np.sum(varg_tmp,axis=1) 
    

    또한 local_buf example in the documentation의 아이디어를 사용하여 비슷한 일을 할 수 있습니다.

  3. (참고 - 나는 그것이 작동한다고 생각하지만, 잠시 동안이 작동하지 않는 것, 그래서 자신에 옵션 3을 시도 - GCC는 현재이 나에게 "내부 컴파일러 오류"를주고있다 위험 ...) openmp atomic directive을 사용하여 원자 적으로 추가합니다. Cython을 회피하기 위해서는 약간의 작업이 필요하지만 너무 어려워서는 안됩니다. add_inplace 매크로 짧은 C 헤더 파일을 만듭니다

    #define add_inplace(x,y) _Pragma("omp atomic") x+=y 
    

    _Pragma 당신이 전처리 문에서 프라그 마를 넣어 수 있도록해야 C99 기능입니다. (이 함수 인 것처럼) 그런 다음 헤더 파일에 대한 사이 썬 알려주기 :

    cdef extern from "header.h": 
        void add_inplace(...) nogil # just use varargs to make Cython think it accepts anything 
    

    루프에서 다음을 수행하십시오이 매크로 속임수를 사용하기 때문에

    add_inplace(count[ni], 1) 
    add_inplace(varg[ni], calc_something(values[ni])) 
    

    이 조금 취약 할 수있다 (즉,PyObject*으로는 작동하지 않지만 표준 C 숫자 유형을 사용할 때는 올바른 C 코드가 생성되어야합니다. (코드를 확인하십시오)

+0

고마워, 나는 길을 길로 시도했지만 내 'calc_something'은 너무 느려서 언급하지 않았다. 다른 옵션은 매우 유망한 것 같습니다. – user6760680

+0

솔루션 2는 나를위한 길 이었지만, 큰 문제 (실제로 관심이있는 문제)에 대해서만 노력할 가치가 있습니다. 그러나'prange'는 루프 이후에'return'을 강요하는 것처럼 이상한 행동을합니다. 나는 솔루션 3을 시도하지 않았다. – user6760680

+0

나는 해결책 3을 얻지 못해서 네가 잘 피하는 것이 좋다고 생각한다. :) – DavidW