파이썬에서 명시 적으로 메모리를 비울 수 있습니까?


387

삼각형을 나타내는 수백만 개의 객체를 만들기 위해 큰 입력 파일에서 작동하는 Python 프로그램을 작성했습니다. 알고리즘은 다음과 같습니다.

  1. 입력 파일을 읽습니다
  2. 파일을 처리하고 정점으로 표시되는 삼각형 목록을 만듭니다.
  3. 정점을 OFF 형식으로 출력합니다 : 정점 목록과 삼각형 목록. 삼각형은 정점 목록에 인덱스로 표시됩니다.

삼각형을 인쇄하기 전에 정점의 전체 목록을 인쇄 해야하는 OFF의 요구 사항은 출력을 파일에 쓰기 전에 삼각형 목록을 메모리에 보관해야한다는 것을 의미합니다. 한편 목록의 크기 때문에 메모리 오류가 발생합니다.

파이썬에게 더 이상 데이터가 필요하지 않다고 알려주는 가장 좋은 방법은 무엇입니까?


11
삼각형을 중간 파일로 인쇄하고 필요할 때 다시 읽지 않으시겠습니까?
Alice Purcell

2
이 질문은 잠재적으로 다른 두 가지가 될 수 있습니다. 동일한 Python 프로세스의 오류 있습니까? 이 경우 메모리를 Python 프로세스의 힙에 비우는 것이 중요합니까, 아니면 시스템의 다른 프로세스에서 비롯된 것입니까?이 경우 OS에 메모리를 비우는 것이 중요합니까?
Charles Duffy

답변:


453

Python Official Documentation 에 따르면 가비지 콜렉터가 강제로 참조되지 않은 메모리를 해제하도록 할 수 있습니다 gc.collect(). 예:

import gc
gc.collect()

19
어떤 경우에는 예외적 인 경우를 제외하고는 자주 가비지가 수집되므로 그다지 도움이되지 않을 것이라고 생각합니다.
Lennart Regebro

24
일반적으로 gc.collect ()는 피해야합니다. 가비지 수집기는 작업 수행 방법을 알고 있습니다. 즉, OP가 갑자기 수백만 개의 개체와 같은 많은 개체를 할당 해제하는 상황 이라면 gc.collect가 유용 할 수 있습니다.
Jason Baker

164
실제로 gc.collect()루프가 끝날 때 자신을 호출 하면 메모리 조각화를 피할 수 있으므로 성능이 향상됩니다. 나는 이것이 중요한 차이를 만드는 것을 보았습니다 (~ 20 % 런타임 IIRC)
RobM

38
파이썬 3.6을 사용하고 있습니다. gc.collect()hdf5 (500k 행)에서 팬더 데이터 프레임을로드 한 후 호출 하면 메모리 사용량이 1.7GB에서 500MB로 줄었습니다.
John

15
32GB 메모리가있는 시스템에서 25GB의 여러 배열을로드하고 처리해야합니다. 배열을 처리 del my_arraygc.collect()후 계속 사용 하는 것은 메모리가 실제로 해제되는 유일한 방법이며 내 프로세스는 다음 배열을로드하기 위해 살아남습니다.
David

113

불행히도 (파이썬 버전과 릴리스에 따라) 일부 유형의 객체는 깔끔한 로컬 최적화이지만 "특정 유형의 객체에 대해서만 더 많은 메모리를"이어 마킹 "하여 메모리 조각화를 일으킬 수있는"무료 목록 "을 사용합니다. 따라서 "일반 기금"에는 이용할 수 없습니다.

메모리의 대량이지만 일시적으로 사용되는 DOES가 모든 자원을 시스템에 반환하도록 보장하는 유일하게 신뢰할 수있는 방법은 사용이 하위 프로세스에서 발생하도록하는 것인데, 이로 인해 메모리 부족한 작업이 종료됩니다. 이러한 조건에서 운영 체제는 작업을 수행하고 하위 프로세스가 고갈 된 모든 리소스를 기꺼이 재활용합니다. 다행스럽게도,이 multiprocessing모듈은 현대 버전의 파이썬에서 이런 종류의 작업을 (아주 고통 스러웠던) 너무 나쁘지 않게 만듭니다.

유스 케이스에서 하위 프로세스가 일부 결과를 누적하고 결과를 기본 프로세스에서 사용할 수 있도록하는 가장 좋은 방법은 반 임시 파일을 사용하는 것입니다. 닫히면 자동으로 사라지고 일반 파일은 모두 삭제하면 명시 적으로 삭제됩니다.


31
나는 이것의 사소한 예를보고 싶다.
Aaron Hall

3
진심으로. @AaronHall이 말한 것.
Noob Saibot

17
@AaronHall 사소한 예 사용할 수 , 사용하여 multiprocessing.Manager공유 상태를 구현하기 위해 파일보다는.
user4815162342

48

del명령문은 사용 중일 수 있지만 IIRC 는 메모리를 해제한다고 보장하지 않습니다 . 문서는 여기에 있습니다 ... 그리고 출시되지 않는 이유가 여기에있다 .

Linux 및 Unix 유형 시스템의 사람들이 파이썬 프로세스를 통해 작업을 수행하고 결과를 얻은 다음 죽이는 것을 들었습니다.

이 기사 에는 Python 가비지 수집기에 대한 메모가 있지만 메모리 제어 부족은 관리되는 메모리의 단점 이라고 생각 합니다.


IronPython과 Jython이이 문제를 피하기위한 또 다른 옵션일까요?
Esteban Küber

@voyager : 아뇨. 그리고 다른 언어도 마찬가지입니다. 문제는 많은 양의 데이터를 목록으로 읽고 데이터가 메모리에 비해 너무 크다는 것입니다.
Lennart Regebro

1
IronPython 또는 Jython에서는 더 나쁠 수 있습니다 . 이러한 환경에서는 참조를 보유하고있는 것이 없으면 메모리가 해제 될 것이라고 보장 할 수 없습니다.
Jason Baker

Java 가상 머신은 전 세계적으로 메모리를 확보하기 때문에 @voyager입니다. JVM에게 자이 썬은 특별한 것이 아니다. 반면에 JVM에는 자체 단점이 있습니다. 예를 들어 사용 가능한 힙 크기를 미리 선언해야합니다.
Falken 교수 계약은

32

파이썬은 가비지 수집되므로 목록의 크기를 줄이면 메모리가 회수됩니다. "del"문을 사용하여 변수를 완전히 제거 할 수도 있습니다.

biglist = [blah,blah,blah]
#...
del biglist

18
이것은 사실이며 사실이 아닙니다. 목록의 크기를 줄이면 메모리를 회수 할 수 있지만 이러한 상황이 언제 발생하는지 보장 할 수는 없습니다.
user142350

3
아니요, 그러나 일반적으로 도움이됩니다. 그러나 여기서 질문을 이해하면 문제는 목록을 읽을 때 모든 개체를 처리하기 전에 메모리가 부족하여 너무 많은 개체가 있어야한다는 것입니다. 처리가 완료되기 전에 목록을 삭제하는 것은 유용한 해결책이 아닙니다. ;)
Lennart Regebro

3
메모리 부족 / 메모리 부족 상태가 가비지 수집기의 "비상 실행"을 트리거하지 않습니까?
Jeremy Friesner

4
biglist = []가 메모리를 해제합니까?
neouyghur

3
예, 이전 목록이 다른 것으로 참조되지 않으면
Ned Batchelder

22

메모리를 명시 적으로 비울 수 없습니다. 객체에 대한 참조를 유지하지 않도록해야합니다. 그런 다음 가비지 수집되어 메모리를 비 웁니다.

큰 목록이 필요한 경우 일반적으로 생성기 / 반복기를 대신 사용하여 코드를 재구성해야합니다. 그렇게하면 메모리에 큰 목록이 필요하지 않습니다.

http://www.prasannatech.net/2009/07/introduction-python-generators.html


1
이 방법이 가능하다면 아마도 그만한 가치가 있습니다. 그러나 반복자에 대한 임의 액세스를 수행 할 수 없으므로 문제가 발생할 수 있습니다.
Jason Baker

사실이며, 필요한 경우 큰 데이터 데이터 세트에 무작위로 액세스하려면 일종의 데이터베이스가 필요할 수 있습니다.
Lennart Regebro

반복자를 사용하여 다른 반복자의 무작위 서브 세트를 추출 할 수 있습니다.
S.Lott

사실이지만 하위 집합을 얻으려면 모든 것을 반복해야합니다. 매우 느립니다.
Lennart Regebro

21

( del객체는 객체에 대한 다른 참조가 없을 때 객체를 삭제 가능한 것으로 표시하므로 친구가 될 수 있습니다. 이제 CPython 인터프리터는 나중에 사용하기 위해이 메모리를 유지하므로 운영 체제에 "사용 가능한"메모리가 표시되지 않을 수 있습니다.)

데이터에 더 컴팩트 한 구조를 사용하여 처음에는 메모리 문제가 발생하지 않을 수 있습니다. 따라서 숫자 목록은 표준 array모듈 또는 타사 numpy모듈 에서 사용하는 형식보다 메모리 효율성이 훨씬 떨어 집니다. 정점을 NumPy 3xN 배열에, 삼각형을 N 요소 배열에 넣으면 메모리가 절약됩니다.


뭐라고? CPython의 가비지 콜렉션은 재 계산 기반입니다. (일반적인 JVM 구현에서와 같이) 주기적으로 마크 앤 스윕이 아니지만 참조 횟수가 0에 도달하는 순간 즉시 무언가를 삭제합니다. 사이클 (참조 횟수가 0이지만 참조 트리의 루프로 인한 것이 아님) 만주기적인 유지 보수가 필요합니다. del객체를 참조하는 모든 이름에 다른 값을 재 할당하는 것은하지 않습니다.
찰스 더피

나는 당신이 어디에서 왔는지 알 것입니다 : 그에 따라 답변을 업데이트 할 것입니다. CPython 인터프리터는 실제로 어떤 중간 방식으로 작동한다는 것을 이해합니다. del파이썬의 관점에서 메모리를 해제하지만 일반적으로 C 런타임 라이브러리 또는 OS의 관점에서는 메모리를 해제합니다. 참고 : stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/... .
Eric O Lebigot

링크의 내용에 동의하지만 OP가 동일한 Python 프로세스에서 발생하는 오류에 대해 이야기한다고 가정 할 때 메모리를 프로세스 로컬 힙과 OS에서 해제하는 것의 차이는 관련이없는 것으로 보입니다 ( 힙을 비우면 해당 Python 프로세스 내에서 새로운 할당에 해당 공간을 사용할 수있게됩니다). 그리고 그것을 위해, del범위에서 나가는 것, 재배치 등과 같은 효과가 있습니다.
Charles Duffy

11

파일에서 그래프를 읽는 데 비슷한 문제가있었습니다. 이 처리에는 메모리에 맞지 않는 200,000x200,000 부동 행렬 (한 번에 한 줄)의 계산이 포함되었습니다. gc.collect()문제의 메모리 관련 측면을 수정하여 계산간에 메모리를 확보하려고 시도 했지만 성능 문제가 발생했습니다. 왜 사용 된 메모리의 양이 일정하게 유지되었지만 새 호출마다 gc.collect()시간이 더 걸렸습니다. 이전 것. 따라서 가비지 수집이 계산 시간의 대부분을 차지했습니다.

메모리와 성능 문제를 해결하기 위해 한 번 읽은 멀티 스레딩 트릭을 사용하기로 전환했습니다 (죄송합니다, 더 이상 관련 게시물을 찾을 수 없습니다). 파일의 각 줄을 큰 for루프 로 읽고 처리하고 gc.collect()메모리 공간을 확보하기 위해 가끔씩 실행 하기 전에 . 이제 새 스레드에서 파일 청크를 읽고 처리하는 함수를 호출합니다. 스레드가 종료되면 이상한 성능 문제없이 메모리가 자동으로 해제됩니다.

실제로 다음과 같이 작동합니다.

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

1
주석에서 파이썬에서 # 대신`//``s를 사용하는 이유가 궁금합니다.
JC Rocamonde '

나는 언어 사이에 혼란스러워했다. 발언 감사합니다. 구문을 업데이트했습니다.
Retzod

9

다른 사람들은 파이썬 인터프리터가 메모리를 비우기 위해 (또는 메모리 문제가 없도록) "동축"할 수있는 몇 가지 방법을 게시했습니다. 그들의 아이디어를 먼저 시도해야 할 것입니다. 그러나 귀하의 질문에 직접 답변하는 것이 중요하다고 생각합니다.

파이썬에게 메모리를 비우도록 직접 지시하는 방법은 실제로 없습니다. 그 문제의 사실은 낮은 수준의 제어를 원한다면 C 또는 C ++로 확장을 작성해야한다는 것입니다.

즉, 이것에 도움이되는 몇 가지 도구가 있습니다.


3
GC.Collect를 ()와 델 gc.garbage [:] 내가 많은 양의 메모리를 사용하고 작업 잘
앤드류 스캇 에반스

3

정점 재사용에 신경 쓰지 않는다면 정점과 삼각형에 각각 하나씩 두 개의 출력 파일이있을 수 있습니다. 그런 다음 삼각형 파일을 정점 파일에 추가하십시오.


1
메모리에 꼭짓점 만 유지하고 삼각형을 파일로 인쇄 한 다음 끝에 꼭짓점을 인쇄 할 수 있다고 생각합니다. 그러나 삼각형을 파일에 쓰는 행위는 성능이 크게 저하됩니다. 속도를 높일 수있는 방법이 있나요 위로는?
Nathan Fellman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.