23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
이번 포스팅은, CPython Level의 동작을 파악하기 보다 실험적인 글 입니다.
Python의 Thread를 이야기 할 때 "Thread는 I/O 작업에만 유리하다" 라는 이야기가 많이 나오고, 이는 Python 공식 홈페이지에서도 확인할 수가 있습니다.
.png)
그렇다면, 이부분에서 의문이 듭니다. "Thread를 사용해서 I/O 성능을 높일수는 있다는데, 그러면 Thread개수를 무한정 늘리면 I/O 성능이 무한정 늘어나나?"
최근 DataLake의 경우 Data를 하나의 파일이 아니라 .CSV, Parquet 형식으로 Data가 분할된 파일로 저장되게 됩니다.
이 경우에 파일을 하나 씩 읽을 경우 Data를 Read하는데 긴 시간이 소요됩니다.
이번 포스팅에서는 하나의 파일 크기가 약 500mb정도 되는 분할된 파일을 읽는 Case를 가정하고 테스트를 진행해 보았습니다.
아래는 Test에 사용된 코드 입니다.
import os
import copy
import time
#약 500MB에 해당하는 String Const 생성
temp_txt = ""
for i in range(100000):
temp_txt = temp_txt + str(i)
temp_txt = temp_txt*1000
#I/O Write작업만 진행하는 Function
temp_list = []
def s_foo(j):
temp_list.append([str(time.time()-st_tm),f"write {j}th thread start"])
f = open(f"{j}.txt", "w")
temp_list.append([str(time.time()-st_tm),f"write {j}th file start"])
f.write(temp_txt)
f.close()
temp_list.append([str(time.time()-st_tm),f"write {j}th file complete"])
#I/O Read작업만 진행하는 Function
temp_list2 = []
def o_foo(j):
temp_list2.append([str(time.time()-st_tm),f"read {j}th thread start"])
f = open(f"{j}.txt", "r")
temp_list2.append([str(time.time()-st_tm),f"read {j}th file start"])
f.read()
f.close()
temp_list2.append([str(time.time()-st_tm),f"read {j}th file complete"])
from multiprocessing.pool import ThreadPool as pool
#50개의 Thread를 생성하고 동시에 I/O Write 작업 요청
st_tm = time.time()
args = zip((i for i in range(50)))
with pool(50) as t_pool:
t_pool.starmap(func = s_foo, iterable = args)
#50개의 Thread를 생성하고 동시에 I/O Read 작업 요청
st_tm = time.time()
args = zip((i for i in range(50)))
with pool(50) as t_pool:
t_pool.starmap(func = o_foo, iterable = args)
for i in temp_list:
print(i)
print("==============================================================")
for i in temp_list2:
print(i)
print("==============================================================")
temp_list = list(reversed(sorted(temp_list, key = lambda x : x[-1])))
temp_list2 = list(reversed(sorted(temp_list2, key = lambda x : x[-1])))
for i in temp_list:
print(i)
print("==============================================================")
for i in temp_list2:
print(i)일단, 순서는 아래와 같습니다.
1. 500MB 크기의 text를 생성한다.
2. 동일한 File을 다수의 Thread를 이용해서 저장한다.
3. 저장된 File을 다수의 Thread를 이용해서 불러온다.
만약 Thread마다 독립적으로 I/O 작업을 실행한다면 Thread개수만큼 시간이 줄어들것을 기대합니다.
하지만 그럴까요?
1. Thread의 활동은 시스템에 가능한 개수까지 동시에 시작이 가능하다.
file object를 읽기 전에, Thread의 시작시간을 확인해보면 실험에 사용된 50개(개인적인 실험에선 200개넘게)의 Thread 모두 0.1초 이내에 Thread Start가 이뤄진 것을 알 수가 있습니다.
.png)
짧은 시간 간격으로 시작되는 다수의 Thread
2. file object를 Open하는 방식에 따라 고정 시간이 발생한다.
아무리 thread로 나뉘어서 I/O작업을 한다고 하여도, python의 open method를 사용하려 경우 고정된 시간이 소요되는것을 알 수 있습니다. "r"와 "w"다 다른 소요시간을 보여줍니다.
이때 open "w"의 소요시간이 긴 것은 파일 쓰기에 기본적으로 시스템 콜의 시간이 더 많이 들기 때문으로 예상됩니다.
.png)
write thread에서 write의 발생 시간 간격(약 2초정도의 고정 시간간격이 발생)
.png)
read thread에서 read의 발생 시간 간격은 거의 없음
3. Thread마다 read, write에 걸리는 시간이 불규칙하다
이번 실험의 경우 모든 file의 크기가 똑같았습니다. Thread마다 I/O 작업이 독립적으로 시행될 경우 각 Thread마다 read, write에 걸리는 시간이 대부분 비슷해야합니다.
하지만 read와 write모두 불규칙한 모습을 보여줍니다.(한개의 file을 read하는데 보통 3초정도가 걸립니다)
.png)
Write의 경우 35~100초까지 넓은 범위의 소요시간이 발생
.png)
Read의 경우 29~155초까지 넓은 범위의 소요시간이 발생
4. 멀티Thread와 싱글Thread의 N개 파일 읽기 시간은 큰 차이가 안난다.
아무리 봐도 이상해서, 10개의 파일을 multi vs single Thread로 읽는 작업을 측정해본 결과, single 작업 대비 multi Thread가 근소하게 빠르긴 하나, Thread의 개수만큼의 성능 향상은 없는것이 확인 되었습니다.
.png)
10%정도의 속도차이가 발생
왜 이런일이 일어났는지 컴퓨터 구조에 대해서 간단히 생각해봅니다.

현대 컴퓨터의 경우 Data I/O 작업을 더이상 Processor에서 하지않고 DMA(Direct Memory Access)에 명령을 전달한 뒤 ALU관련 작업을 진행합니다.
Python의 Thread를 사용할 경우 DMA에 다수의 Thread에서 Memory Operation 신호를 준다고 할 수 있습니다.
하지만 DMA와 Memory, 보조기억장치 사이의 Data Bus는 한계가 있으며, 위 실험의 경우
CPU -> DMA -> 보조기억장치 -> Memory 순으로 신호가 흐르게 될 것이고
CPU에서 다수 Thread로 부터 온 Memory Operation 신호는 결국 순서대로 실행되게 될 것입니다.
때문에, 컴퓨터 내부의 I/O 동작 보다 네트워크를 이용한 Requests, Response 동작에서 큰 효율을 발휘할 수 있는 방법이 됩니다.
결론 요약.
0. 다수의 I/O 동작은 큰 Merit이 없고, 하나의 I/O작업 + 하나의 CPU작업 을 동시에 할때 효율이 가장 좋다.
1. Thread는 가능한 많이 만들 수 있으며, 시작하는데 큰 Cost가 들지 않는다.
2. file open 후 file을 read, write하는 동작 중에는 Thread가 다르더라도 동시에 실행이 불가능하다.
3. 작은 file을 N개 읽을 경우, 오히려 ThreadPool간 Race가 발생해서 생각보다 성능향상이 낮을 수 있다.
하지만 아직 정확히 파악하기엔 부족합니다. 결국 GIL의 정체가 무엇인지, 어떤 동작을 하는지를 알아야 이런 결과가 도출된 진짜 원인을 알 수가 있습니다.
추후에 GIL에 대한 포스팅을 할수 있다면 해 보겠습니다!
감사합니다!
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.