[python] 다중 처리 프로세스간에 대규모 읽기 전용 Numpy 배열 공유

60GB SciPy Array (Matrix)가 있는데 5 multiprocessing Process개 이상의 개체 간에 공유해야 합니다. numpy-sharedmem을 보았고 SciPy 목록 에서이 토론 을 읽었습니다 . 두 가지 접근 방식이있는 것 같습니다. numpy-sharedmema를 사용하고 multiprocessing.RawArray()NumPy를 dtypes에 매핑 ctype합니다. 이제 numpy-sharedmem갈 길인 것 같지만 아직 좋은 참조 예를 보지 못했습니다. 배열 (실제로는 행렬)이 읽기 전용이기 때문에 어떤 종류의 잠금도 필요하지 않습니다. 이제 크기 때문에 사본을 피하고 싶습니다. 그것은 같은데 정확한 방법은 생성하는 A와 배열의 카피 sharedmem배열하고 그것이 패스 Process개체? 몇 가지 구체적인 질문 :

  1. 실제로 sharedmem 핸들을 서브에 전달하는 가장 좋은 방법은 무엇입니까 Process()? 하나의 배열을 전달하기 위해 대기열이 필요합니까? 파이프가 더 좋을까요? Process()서브 클래스의 init (피클이라고 가정하는 곳)에 인수로 전달할 수 있습니까 ?

  2. 위에서 링크 한 토론 numpy-sharedmem에서 64 비트 안전하지 않다는 언급이 있습니까? 32 비트 주소 지정이 불가능한 구조를 확실히 사용하고 있습니다.

  3. RawArray()접근 방식에 트레이드 오프가 있습니까? 더 느리게, 벌레?

  4. numpy-sharedmem 메서드에 대해 ctype-to-dtype 매핑이 필요합니까?

  5. 누구든지 이것을 수행하는 일부 OpenSource 코드의 예가 있습니까? 나는 매우 실습을 배웠고 어떤 종류의 좋은 예 없이는 이것을 작동시키기가 어렵습니다.

다른 사람들을 위해 이것을 명확히하는 데 도움이되는 추가 정보가 있으면 의견을 남겨 주시면 추가하겠습니다. 감사!

이것은 Ubuntu Linux 및 Maybe Mac OS에서 실행되어야하지만 이식성은 큰 문제가 아닙니다.



답변

@Velimir Mlaker가 훌륭한 대답을했습니다. 나는 약간의 코멘트와 작은 예를 추가 할 수 있다고 생각했다.

(sharedmem에 대한 많은 문서를 찾을 수 없었습니다. 이것은 내 실험의 결과입니다.)

  1. 하위 프로세스가 시작될 때 또는 시작된 후에 핸들을 전달해야합니까? 전자 인 경우에는 targetargs인수를 사용할 수 있습니다.Process . 이것은 잠재적으로 전역 변수를 사용하는 것보다 낫습니다.
  2. 링크 한 토론 페이지에서 64 비트 Linux에 대한 지원이 한동안 sharedmem에 추가 된 것으로 보이므로 문제가되지 않을 수 있습니다.
  3. 나는 이것에 대해 모른다.
  4. 아니요. 아래 예를 참조하십시오.

#!/usr/bin/env python
from multiprocessing import Process
import sharedmem
import numpy

def do_work(data, start):
    data[start] = 0;

def split_work(num):
    n = 20
    width  = n/num
    shared = sharedmem.empty(n)
    shared[:] = numpy.random.rand(1, n)[0]
    print "values are %s" % shared

    processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print "values are %s" % shared
    print "type is %s" % type(shared[0])

if __name__ == '__main__':
    split_work(4)

산출

values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
  0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
  0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
  0.7292129   0.06063283]
values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
  0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
  0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
  0.7292129   0.06063283]
type is <type 'numpy.float64'>

관련 질문 이 유용 할 수 있습니다.


답변

Linux (또는 POSIX 호환 시스템)를 사용하는 경우이 배열을 전역 변수로 정의 할 수 있습니다. 새 자식 프로세스를 시작할 때 Linux에서 multiprocessing사용 중 fork()입니다. 새로 생성 된 자식 프로세스는 메모리를 변경하지 않는 한 자동으로 부모와 메모리를 공유합니다 (기록 중 복사 메커니즘).

“나는 어떤 종류의 잠금도 필요하지 않습니다. 배열 (실제로는 행렬)이 읽기 전용이기 때문에”이 동작을 활용하는 것은 매우 간단하면서도 매우 효율적인 접근 방식입니다. 모든 하위 프로세스가 액세스합니다. 이 큰 numpy 배열을 읽을 때 실제 메모리의 동일한 데이터.

Process()생성자 에게 배열을 넘기지 마십시오 . 이것은 데이터를 자식에게 지시 multiprocessingpickle것입니다. 이는 귀하의 경우에 극도로 비효율적이거나 불가능할 것입니다. Linux fork()에서 자식 바로 뒤에는 동일한 물리적 메모리를 사용하는 부모의 정확한 복사본이므로 .NET에 전달한 target함수 내에서 행렬을 ‘포함하는’Python 변수에 액세스 할 수 있는지 확인하기 만하면 됩니다 Process(). 이것은 일반적으로 ‘전역’변수로 달성 할 수 있습니다.

예제 코드 :

from multiprocessing import Process
from numpy import random


global_array = random.random(10**4)


def child():
    print sum(global_array)


def main():
    processes = [Process(target=child) for _ in xrange(10)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

지원하지 않습니다 – Windows에서 fork()multiprocessing는 Win32 API 호출을 사용한다 CreateProcess. 주어진 실행 파일에서 완전히 새로운 프로세스를 생성합니다. 윈도우 하나의 이유가되는 것을 필요한 아이에게 피클 데이터의 경우 부모의 런타임 중에 생성 된 하나의 필요 데이터입니다.


답변

내가 작성한 작은 코드에 관심이있을 수 있습니다. github.com/vmlaker/benchmark-sharedmem

관심있는 유일한 파일은 main.py. numpy-sharedmem 의 벤치 마크입니다 . 코드는 파이프를 통해 생성 된 프로세스에 배열 ( numpy또는 sharedmem)을 전달합니다 . 작업자 sum()는 데이터를 요청 합니다. 두 구현 간의 데이터 통신 시간을 비교하는 데에만 관심이있었습니다.

또 다른 복잡한 코드 인 github.com/vmlaker/sherlock을 작성했습니다 .

여기 에서는 OpenCV를 사용한 실시간 이미지 처리를 위해 numpy-sharedmem 모듈을 사용합니다 . 이미지는 OpenCV의 최신 cv2API에 따라 NumPy 배열 입니다. 이미지, 실제로 그 참조는에서 생성 된 사전 객체를 통해 프로세스간에 공유됩니다 multiprocessing.Manager(Queue 또는 Pipe를 사용하는 것과 반대). 일반 NumPy 배열을 사용하는 것과 비교할 때 성능이 크게 향상됩니다.

파이프 대 대기열 :

내 경험상 Pipe를 사용한 IPC는 Queue보다 빠릅니다. Queue는 여러 생산자 / 소비자에게 안전하도록 잠금을 추가하기 때문에 의미가 있습니다. 파이프는 그렇지 않습니다. 그러나 두 개의 프로세스 만 앞뒤로 대화하는 경우 Pipe를 사용하는 것이 안전합니다.

… 동시에 파이프의 다른 끝을 사용하는 프로세스로 인한 손상 위험이 없습니다.

sharedmem안전 :

sharedmem모듈 의 주요 문제 는 비정상적인 프로그램 종료시 메모리 누수 가능성입니다. 이것에 대해서는 여기서 긴 논의 에서 설명 합니다 . 2011 년 4 월 10 일에 Sturla가 메모리 누수에 대한 수정을 언급했지만 그 이후로 GitHub ( github.com/sturlamolden/sharedmem-numpy ) 의 Sturla Molden 자신 과 Bitbucket의 Chris Lee-Messer ( bitbucket.org/cleemesser/numpy-sharedmem ).


답변

어레이가 그렇게 크면 numpy.memmap. 예를 들어, 디스크에 저장된 어레이가있는 경우, 예를 들어 'test.array'“쓰기”모드에서도 동시 프로세스를 사용하여 데이터에 액세스 할 수 있지만 “읽기”모드 만 필요하므로 케이스가 더 간단합니다.

어레이 만들기 :

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

그런 다음 일반 배열에서와 같은 방식으로이 배열을 채울 수 있습니다. 예를 들면 :

a[:10,:100]=1.
a[10:,100:]=2.

변수를 삭제하면 데이터가 디스크에 저장됩니다 a.

나중에 데이터에 액세스하는 여러 프로세스를 사용할 수 있습니다 test.array.

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

관련 답변 :


답변

또한 작업을 적절하게 분할 할 수있는 것처럼 pyro 에 대한 문서를 살펴 보는 것이 유용 할 수 있습니다.이를 사용하여 동일한 시스템의 다른 코어뿐만 아니라 다른 시스템에서 다른 섹션을 실행할 수 있습니다.


답변

멀티 스레딩을 사용하지 않는 이유는 무엇입니까? 기본 프로세스의 리소스는 기본적으로 스레드에서 공유 할 수 있으므로 멀티 스레딩은 주 프로세스가 소유 한 개체를 공유하는 더 좋은 방법입니다.

당신은 파이썬의 GIL 메커니즘에 대해 걱정하는 경우, 어쩌면 당신은에 의존 수 nogilnumba.


답변