<질문>
세 개의 큰 목록이 있습니다. 첫 번째는 bitarrays(모듈 bitarray 0.8.0)를 포함하고 다른 두 개는 정수 배열을 포함합니다.
l1=[bitarray 1, bitarray 2, ... ,bitarray n]
l2=[array 1, array 2, ... , array n]
l3=[array 1, array 2, ... , array n]
이러한 데이터 구조에는 상당한 양의 RAM이 필요합니다(총 ~16GB).
다음을 사용하여 12개의 하위 프로세스를 시작하는 경우:
multiprocessing.Process(target=someFunction, args=(l1,l2,l3))
이것은 l1, l2 및 l3이 각 하위 프로세스에 대해 복사되거나 하위 프로세스가 이 목록을 공유한다는 것을 의미합니까? 아니면 더 직접적으로 16GB 또는 192GB RAM을 사용할 것입니까?
someFunction은 이 목록에서 일부 값을 읽은 다음 읽은 값을 기반으로 일부 계산을 수행합니다. 결과는 부모 프로세스로 반환됩니다. 목록 l1, l2 및 l3은 someFunction에 의해 수정되지 않습니다.
따라서 나는 하위 프로세스가 이러한 거대한 목록을 필요로 하지 않으며 복사하지 않고 대신 부모와 공유할 것이라고 가정합니다. Linux에서 copy-on-write 접근 방식으로 인해 프로그램이 시작하는 하위 프로세스 수에 관계없이 16GB의 RAM이 필요하다는 것을 의미합니까? 내가 맞습니까 아니면 목록이 복사되도록 할 수 있는 것을 놓치고 있습니까?
편집: 나는 주제에 대해 조금 더 읽은 후에도 여전히 혼란스러워합니다. 한편으로 Linux는 copy-on-write를 사용하므로 데이터가 복사되지 않습니다. 반면에 개체에 액세스하면 참조 횟수가 변경됩니다(나는 여전히 이유와 의미가 확실하지 않습니다). 그럼에도 불구하고 전체 개체가 복사됩니까?
예를 들어 내가 someFunction을 다음과 같이 정의한다면:
def someFunction(list1, list2, list3):
i=random.randint(0,99999)
print list1[i], list2[i], list3[i]
이 기능을 사용하면 각 하위 프로세스에 대해 l1, l2 및 l3이 완전히 복사됩니까?
이를 확인할 수 있는 방법이 있습니까?
편집2 조금 더 읽고 하위 프로세스가 실행되는 동안 시스템의 총 메모리 사용량을 모니터링하면 실제로 각 하위 프로세스에 대해 전체 개체가 복사되는 것 같습니다. 그리고 레퍼런스 카운팅 때문인 것 같습니다.
l1, l2 및 l3에 대한 참조 카운팅은 실제로 내 프로그램에서 필요하지 않습니다. 이는 l1, l2 및 l3이 부모 프로세스가 종료될 때까지 메모리에 변경되지 않고 유지되기 때문입니다. 그때까지는 이 목록에서 사용하는 메모리를 해제할 필요가 없습니다. 사실 나는 프로그램이 종료될 때까지 참조 횟수가 0(이 목록과 이 목록의 모든 개체에 대해) 이상으로 유지된다는 것을 알고 있습니다.
이제 질문은 개체가 각 하위 프로세스에 복사되지 않도록 하려면 어떻게 해야 합니까? 이 목록과 이 목록의 각 개체에 대한 참조 계산을 비활성화할 수 있습니까?
편집3 추가 참고 사항입니다. 하위 프로세스는 수정할 필요가 없습니다.l1
,l2
그리고l3
또는 이 목록에 있는 모든 개체. 하위 프로세스는 각 하위 프로세스에 대해 메모리를 복사하지 않고 이러한 개체 중 일부만 참조할 수 있으면 됩니다.
<답변1>
이것은 여전히 google에서 매우 높은 결과이고 아직 아무도 언급하지 않았기 때문에 python 버전 3.8.0에 도입된 '진정한' 공유 메모리의 새로운 가능성을 언급할 것이라고 생각했습니다.https://docs.python.org/3/library/multiprocessing.shared_memory.html
여기에 numpy 배열이 사용되는 작은 인위적인 예제(Linux에서 테스트됨)가 포함되어 있습니다. 이는 매우 일반적인 사용 사례일 수 있습니다.
# one dimension of the 2d array which is shared
dim = 5000
import numpy as np
from multiprocessing import shared_memory, Process, Lock
from multiprocessing import cpu_count, current_process
import time
lock = Lock()
def add_one(shr_name):
existing_shm = shared_memory.SharedMemory(name=shr_name)
np_array = np.ndarray((dim, dim,), dtype=np.int64, buffer=existing_shm.buf)
lock.acquire()
np_array[:] = np_array[0] + 1
lock.release()
time.sleep(10) # pause, to see the memory usage in top
print('added one')
existing_shm.close()
def create_shared_block():
a = np.ones(shape=(dim, dim), dtype=np.int64) # Start with an existing NumPy array
shm = shared_memory.SharedMemory(create=True, size=a.nbytes)
# # Now create a NumPy array backed by shared memory
np_array = np.ndarray(a.shape, dtype=np.int64, buffer=shm.buf)
np_array[:] = a[:] # Copy the original data into shared memory
return shm, np_array
if current_process().name == "MainProcess":
print("creating shared block")
shr, np_array = create_shared_block()
processes = []
for i in range(cpu_count()):
_process = Process(target=add_one, args=(shr.name,))
processes.append(_process)
_process.start()
for _process in processes:
_process.join()
print("Final array")
print(np_array[:10])
print(np_array[10:])
shr.close()
shr.unlink()
64비트 정수로 인해 이 코드를 실행하는 데 약 1GB의 램이 필요할 수 있으므로 이를 사용하여 시스템을 정지시키지 않도록 하십시오. ^_^
<답변2>
일반적으로 동일한 데이터를 공유하는 두 가지 방법이 있습니다.
- 멀티스레딩
- 공유 메모리
Python의 멀티스레딩은 CPU 바운드 작업(GIL 때문에)에 적합하지 않으므로 이 경우 일반적인 솔루션은 계속 진행하는 것입니다.multiprocessing
. 그러나 이 솔루션을 사용하면 명시적으로 데이터를 공유해야 합니다.multiprocessing.Value
그리고multiprocessing.Array
.
일반적으로 프로세스 간에 데이터를 공유하는 것은 모든 동기화 문제로 인해 최선의 선택이 아닐 수 있습니다. 액터가 메시지를 교환하는 방식이 일반적으로 더 나은 선택으로 간주됩니다. 또한보십시오Python documentation:
위에서 언급했듯이 동시 프로그래밍을 할 때 일반적으로 가능한 한 공유 상태를 사용하지 않는 것이 가장 좋습니다. 이것은 여러 프로세스를 사용할 때 특히 그렇습니다. 그러나 실제로 일부 공유 데이터를 사용해야 하는 경우 멀티프로세싱은 몇 가지 방법을 제공합니다.
귀하의 경우 포장해야합니다l1
,l2
그리고l3
어떻게 보면 이해할 수 있는multiprocessing
(예:multiprocessing.Array
), 매개변수로 전달합니다.
또한 쓰기 권한이 필요하지 않다고 말했듯이 다음을 전달해야 합니다.lock=False
개체를 만드는 동안 그렇지 않으면 모든 액세스가 계속 직렬화됩니다.
<답변3>
Python3.8 사용에 관심이 있는 분들을 위해shared_memory 모듈, 여전히bug 수정되지 않았으며 지금까지 Python3.8/3.9/3.10에 영향을 미치고 있습니다(2021-01-15). 이 버그는 posix 시스템에 영향을 미치며 다른 프로세스가 여전히 유효한 액세스 권한을 가져야 할 때 리소스 추적기가 공유 메모리 세그먼트를 파괴하는 것에 관한 것입니다. 따라서 코드에서 사용하는 경우 주의하십시오.
<답변4>
기록 중 복사 기능을 사용하고 데이터가 정적(자식 프로세스에서 변경되지 않음)인 경우 - 데이터가 있는 메모리 블록을 python이 엉망으로 만들지 않도록 해야 합니다. C 또는 C++ 구조(예: stl)를 컨테이너로 사용하고 파이썬 수준 객체가 생성될 때 데이터 메모리에 대한 포인터를 사용하는(또는 데이터 메모리를 복사할 수 있는) 자체 파이썬 래퍼를 제공하여 이를 쉽게 수행할 수 있습니다. . 이 모든 것은 거의 python 단순성과 구문으로 매우 쉽게 수행할 수 있습니다.cython.
# pseudo cython
cdef class FooContainer:
cdef char * data
def __cinit__(self, char * foo_value):
self.data = malloc(1024, sizeof(char))
memcpy(self.data, foo_value, min(1024, len(foo_value)))
def get(self):
return self.data
# python part
from foo import FooContainer
f = FooContainer("hello world")
pid = fork()
if not pid:
f.get() # this call will read same memory page to where
# parent process wrote 1024 chars of self.data
# and cython will automatically create a new python string
# object from it and return to caller
위의 의사 코드는 잘못 작성되었습니다. 그것을 사용하지 마십시오. self.data 대신 귀하의 경우 C 또는 C++ 컨테이너여야 합니다.
<답변5>
memcached 또는 redis를 사용하고 각각을 키 값 쌍으로 설정할 수 있습니다. {'l1'...
'개발 > Python' 카테고리의 다른 글
[파이썬] 문자열의 find와 index의 차이점 (0) | 2022.10.09 |
---|---|
[파이썬] 클래스 개체에서 새 인스턴스를 만드는 방법 (0) | 2022.10.09 |
[파이썬] 1년의 몇 번째 주에서 날짜 구하기 (1) | 2022.10.07 |
[파이썬] SVG를 PNG로 변환 (1) | 2022.10.07 |