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


88

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에서 실행되어야하지만 이식성은 큰 문제가 아닙니다.


1
다른 프로세스가 해당 어레이에 기록 할 multiprocessing경우 각 프로세스에 대해 전체 내용을 복사 할 수 있습니다.
티아고

3
@tiago : "나는 배열 (실제로는 매트릭스) 이후 잠금 어떤 종류를 필요로하지 않는 것입니다 수 읽기 전용"
박사 1 월 필립 Gehrcke

1
@tiago : 또한 멀티 프로세싱은 (에 대한 인수를 통해) 명시 적으로 말하지 않는 한 복사본을 만들지 않습니다 target_function. 운영 체제는 수정시에만 상위 메모리의 일부를 하위 메모리 공간에 복사합니다.
Dr. Jan-Philip Gehrcke


나는 묻는 몇 가지 질문을 하기 전에이 약을. 내 솔루션은 여기에서 찾을 수 있습니다 : github.com/david-hoffman/peaks/blob/… (죄송합니다 코드는 재앙입니다).
David Hoffman

답변:


30

@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'>

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


37

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. 주어진 실행 파일에서 완전히 새로운 프로세스를 생성합니다. 윈도우 하나의 이유가되는 것을 필요한 아이에게 피클 데이터의 경우 부모의 런타임 중에 생성 된 하나의 필요 데이터입니다.


3
Copy-on-write는 참조 카운터가 포함 된 페이지를 복사하지만 (따라서 분기 된 각 파이썬은 자체 참조 카운터를 갖습니다) 전체 데이터 배열을 복사하지는 않습니다.
robince 2013-07-22

1
나는 전역 변수보다 모듈 수준 변수로 더 많은 성공을 거두었다고 덧붙일 것입니다. 즉 포크 전에 전역 범위의 모듈에 변수를 추가합니다
robince

5
이 질문 / 답변을 방해하는 사람들에 대한주의 사항 : 다중 스레드 작업을 위해 OpenBLAS 연결 Numpy를 사용하는 경우, 사용 multiprocessing하거나 하위 프로세스가 중단 될 수있을 때 다중 스레딩 (내보내기 OPENBLAS_NUM_THREADS = 1)을 비활성화해야합니다 ( 공유 글로벌 어레이 / 매트릭스에서 선형 대수 연산을 수행 할 때 일반적으로 n 개의 프로세서가 아닌 하나의 프로세서 1 / n을 사용 합니다. OpenBLAS 알려진 멀티 스레드 충돌이 파이썬 확장하는 것multiprocessing
Dologan

1
누구든지 파이썬이 fork매개 변수 Process를 직렬화하는 대신에 전달 하기 위해 OS 를 사용하지 않는 이유를 설명 할 수 있습니까 ? 즉, 호출 되기fork 직전에 부모 프로세스에 적용 할 수 없어서 매개 변수 값을 OS에서 계속 사용할 수 있습니까? 직렬화하는 것보다 더 효율적인 것 같습니까? child
최대

2
우리 모두 fork()는 Windows에서 사용할 수 없다는 것을 알고 있으며 내 답변과 의견에 여러 번 언급되었습니다. 나는 이것이 당신의 처음 질문 이었다는 것을 알고, 나는 네 의견보다 그 대답 "타협은 더 나은 유지 보수성과 동일한 동작을 보장하기위한 기본적으로 두 플랫폼에서 파라미터 전송, 동일한 방법을 사용하는 것입니다.". 두 가지 방법 모두 장점과 단점이 있으므로 Python 3에서는 사용자가 방법을 선택할 수있는 유연성이 더 높습니다. 이 토론은 세부 사항에 대한 설명 없이는 생산적이지 않으므로 여기서는 안됩니다.
Dr. Jan-Philip Gehrcke 2015 년

24

내가 작성한 작은 코드에 관심이있을 수 있습니다. 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 ).


감사합니다, 매우 유익합니다. 그러나 메모리 누수 sharedmem는 큰 문제처럼 들립니다. 해결에 대한 단서가 있습니까?

1
누수를 알아 차리는 것 외에도 코드에서 찾아 본 적이 없습니다. 위의 "sharedmem safety"아래 sharedmem에 참조를 위해 모듈 의 두 오픈 소스 저장소의 보관 인을 추가했습니다 .
Velimir Mlaker

14

어레이가 그렇게 크면 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))

관련 답변 :


3

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


0

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.