python matplotlib의 plt.save()를 여러 cpu로 병렬처리하여
다량의 이미지를 빠르게 저장하는 방법에 대해서 소개 하도록 하겠다.
1. 데이터 설명
python matplotlib으로 이미지 생성 시 소요 시간 :
(월 자료 데이터 * 12개월 * 30년) * 800 세트
월 자료 데이터 생성시 걸리는 시간 : 1~2초
= 약 80시간 소요 = 3일 이상
2. 문제 상황
이미지 생성 자체는 굉장히 빠르지만
생성해야 하는 이미지 양 자체가 너무 많아 시간이 너무 오래 걸리는 문제가 생겼다.
게다기 이런 식의 이미지를 한 3~4 종류는 더 그려야해서
순차적으로 그렸다가는 너무 시간이 많이 걸려서 안될 것 같아 다른 방법을 생각해 보기로 하였다.
3. 해결방법
회사 서버에 cpu가 여러개 달려있어서 그 점을 활용해 보기로 하였다.
multiprocessing을 이용해서 병렬로 처리하는 방법을 이용하였다.
이미지 save를 하기 때문에 disk I/O가 빈번하게 일어난다.
그렇기 때문에 cpu에 너무 부하를 주게 되면 서버가 매우 느려지다가 뻗을 수도 있기 때문에 조심해야한다!
너무 풀로 돌리지 않도록 적절히 조절해서 돌리도록 하자.
import pandas as pd
import matplotlib.pyplot as plt
from multiprocessing import Pool
#plt로 이미지 생성하는 코드 (예시)
def draw_img(dataPath, savePath, year, month):
#load Data
dataPath = f"{dataPath}/{year}/{month}"
df = pd.read_csv(f"{dataPath}/tmp_csv.csv"), parse_dates=["date"])
#make plot
plt.figure(figsize=(12, 5))
plt.plot(df['date'], df['arg'])
#save plot
savePath = f"{savePath}/{year}/{month}"
plt.savefig(f"{savePath}/tmp_img.png")
plt.close()
#멀티프로세스 돌리는데 필요한 함수
def worker(args):
year, month = args
draw_img(dataPath, savePath, year, month)
if __name__ == "__main__":
dataPath = "/your/data/path"
savePath = "/your/save/path"
# 각각의 프로세스들을 담아줄 list
tasks=[]
for year in range(2000, 2025):
for month in range(1, 13):
tasks.append((dataPath, savePath, year, month))
#가용 가능한 cpu 개수 써주기 (너무 무리하게 잡아주지 말기)
with Pool(processes=16) as pool:
pool.map(worker, tasks)

위와 같은 방법을 사용하면 여러 cpu 에서 독립적인 작업을 시킬 수 있다.
주의해야할 점은 if __name__ == "__main__"을 이용해서 실행시켜줘야한다는 점이다.
.py에서 사실 단순하게 함수 및 코드를 실행시키기 위해서 if __name__ == "__main__"는 필수는 아니지만
multiprocessing에서는 반드시 함께 써주어야 한다. 왜냐하면 무한 재귀가 될 수 있기 때문이다.
(무한 재귀가 되는 이유 상세 설명)
사용자가 실행하는 py코드 이름을 mycode.py라고 하자.
python mycode.py 시 if __name__ == "__main__"을 통해서 최초 1회 실행하게 된다.
이후 multiprocessing에서 내 코드를 이용해서 새로운 python을 실행하게 된다.
(= 터미널에서 ps aux | grep py 했을때 processes만큼의 python이 실행되는 이유)
새로운 python을 실행한다는 뜻은 독립적으로 수행되는 프로세스라는 의미이고
이때 분배된 tasks들을 각각 할당받아 병렬적으로 수행 하게 된다.
이때 만약에 if __name__ == "__main__"가 없다면 최초 1회 실행 코드와 pool이 수행시키는 코드가 구분이 되지 않아
무한 루프에 빠질수 있다는 의미이다.
좀더 자세히 설명하자면,
python mycode.py를 사용자가 실행하면, 위에 설명한대로 if __name__ == "__main__"를 통해서 실행하게 되고
해당 if문 하위에 있는 pool들이 mycode.py를 import하여 새로운 py를 실행시킨다.
이때 __name__ 은 import된 mycode가 된다. 따라서 if문을 타지 않아 재귀를 막아줄 수 있는 효과를 가진다.
사실 multiprocessing을 이번에 제대로 처음 써본다.
왜냐하면 그 이전까지는 반복작업을 이렇게 많이 할 일이 별로 없었다.
근데 이렇게 꽤나 많은 데이터를 가지고 처리해 보는 경험을 해서 좋았고
무엇보다 multiprocessing에 대해서 많이 알게되었고 자유자재로 쓸 수 있게 된 것 같아 좋다.
나중에 multi threading에 대해서도 공부해보고싶다.
(참고로 matplotlib은 multi thread 친화적이지 않아서 비추한다고 한다.)
'IT > Data Analysis' 카테고리의 다른 글
| [python 라이브러리 세팅] 내부망에 넣을 라이브러리 준비 (1) - python 3.6.8 설치 (0) | 2025.09.24 |
|---|---|
| [python oracle] oracledb insert할 때 execute, executemany 안되는 에러 (3) | 2025.07.10 |
| [데이터 전처리] 크기가 큰 csv파일 One hot encoding 시 메모리 부족 회피하는법 (0) | 2024.11.29 |
| [기상 데이터] LCC 투영법 (1) - LCC 투영법 기반 격자 데이터에서 위경도 구하기 (LCC 격자 <-> 위경도 변환) (2) | 2024.11.20 |
| [전처리] MaskedArray + np.where (np.ma.where) (1) | 2024.11.12 |