Skip to content
isdnetworks
Go back

메모리를 쓰는 자리

(수정됨)

장비 로그를 일별로 묶는 Python 집계 배치 daily_rollup.py 가 메모리를 많이 써서 컨테이너 한도에 가까웠다. 코드에 재귀 호출이 있어서 그것 때문이라고 생각했다.

Table of contents

Open Table of contents

해 본 것 — 스위치로 기여를 분리했다

고치기 전에 그 부분의 기여만 따로 재기로 했다. 마침 --no-recurse 로 재귀를 켜고 끄는 인자가 있었다. 켠 것과 끈 것의 최대 사용량을 tracemalloc.get_traced_memory() 의 peak 로 각각 쟀다.

차이는 백몇십 메가였고 전체 사용량에 비하면 작은 몫이었다. 추측한 원인이 실제로 얼마를 차지하는지는 그것만 빼고 한 번 돌려 보면 바로 나온다.

원인 — 기저와 집계 본체

그러면 나머지가 어디서 오는지가 남는다. load_input() 직후의 사용량을 재 보니 시작하기도 전에 1기가에 가까웠다.

거기에 집계 본체가 대부분을 더하고 있었다. 최적화 대상이 재귀가 아니라 집계 본체라는 것이 이 분해로 확정됐다.

막힌 지점 — 입력 로드가 남기는 것

입력 파일 자체는 백몇십 메가였는데 로드 직후가 1기가였다. 나머지는 json.loads 로 파싱하면서 문자열을 만들고 버리는 과정의 할당자 잔류였다.

실제로 안 쓰는데 운영체제에 돌려주지 않는 몫이라 코드를 줄여도 안 없어진다. 파싱 방식을 바꾸거나 for line in f 로 한 줄씩 읽어야 줄어드는 종류였다.

주의 — 계측 도구가 보는 범위

Python 공식 문서대로 tracemalloc 은 Python 이 할당한 메모리 블록을 추적한다. 수치 배열처럼 다른 경로로 잡히는 것은 안 나온다.

그래서 resource.getrusage 의 ru_maxrss 와 도구가 보고한 값의 차이가 그 몫이 된다. 이 데이터는 대부분 문자열이라 잘 잡혔지만 수치 위주였으면 도구만 보고 판단했을 때 크게 틀렸을 것이다.

검증 — 최대치와 증분이 답하는 질문

이 도구에는 Snapshot.compare_to 로 두 시점을 비교하는 기능이 있다. 먼저 그것을 썼는데 증가분이 아주 작게 나왔다. 그 기능은 반환 뒤에도 살아남은 것만 보여 주므로 중간에 썼다가 해제한 것은 안 나온다.

한도를 볼 때는 peak 를 봐야 하고 누수를 찾을 때는 compare_to 의 증분을 본다. 둘 다 유효한 지표인데 답하는 질문이 다르므로 무엇을 알고 싶은지에 따라 골라야 한다.

이 측정으로 아무리 줄여도 남는 하한이 있다는 것도 드러났다. 집계 코드를 최적화해도 입력 잔류가 그대로면 전체가 그만큼 아래로는 안 내려간다. 그래서 집계 본체보다 스트리밍 읽기를 먼저 하는 쪽을 골랐다. 이유는 하한을 내리지 않으면 본체를 줄여도 한도와의 거리가 안 벌어져서다. 대신 파일을 한 번에 읽던 단순한 구조를 잃는 것을 감수했다.

정리


Share this post on:

Previous Post
계획을 깬 정상 메서드
Next Post
죽여도 다시 뜬다