-
고성능 파이썬 Chapter 02 - 프로파일링으로 병목 지점 찾기STUDY/고성능 파이썬 2024. 12. 1. 18:56
들어가며
1장에서는 파이썬의 코드 수행이 느린 이유와 기본적인 프로파일링의 필요성에 대해여 설명을 하는 챕터였다. Chapter2에서는 실질적으로 병목지점을 찾는 프로파일링 방법에 대한 내용을 다룬다. CPU 사용량과 RAM 사용량 등을 확인하는 라이브러리를 통해 실습이 기대가 된다.
이 장에서 배울 내용
- 코드에서 RAM 병목 지점을 찾는 방법
- CPU와 메모리 사용량을 프로파일링 하는 방법
- 바람직한 프로파일링의 깊이
- 장시간 실행되는 애플리케이션의 프로파일링 방법
- C파이썬의 내부 동작
- 성능 튜닝 중 코드의 올바름을 유지하는 방법
프로파일링으로 CPU 시간, 메모리 사용량을 살펴 볼 것이고, 네트워크 대역폭과 I/O측정에서도 비슷한 기법을 적용할 수 있다.
프로파일링을 하면 병목지점을 빠르게 찾아낼 수 있고, 해당 부분을 수정하여 성능을 끌어낼 수 있다. 이런 프로파일링 기법을 적용하기위해 프로그램을 "모듈" 단위로 구성해놓았다면 더욱 측정이 쉬울 것이다.
2.1 효과적으로 프로파일링 하기
프로파일링의 첫번째 목표는 병목 지점을 확인하는 것이다. 프로파일링을 하면 실제 수행보다 10~100배 느려지는데, 실제 상황과 유사한 상황을 확인하기 위해 테스트할 부분만 따로 분리해서 테스트 하도록 하자, 따라서 코드를 작성할때 모듈별로 미리 나눠놓는 편이 좋다.
Ipython의 %timeit 매직 명령어, time.time()을 이용한 시간 측정,
cProfile을 이용한 어떤 함수에서 오래 시간이 걸리는지,line_profiler를 이용하여 한줄씩 검사하는 방법,
memory_profiler를 이용하여 RAM 사용량 측정,
C파이썬 내부의 byte-code에 대한 설명을 할 것임.
2.2 줄리아 집합
줄리아 집합 연산을 통해 프로파일링 하는 방법을 소개하려 함.
줄리아 집합의 연산은
$$ f(z)=z^{2}+c $$
로 수행한다. z의 절댓값이 2 보다 작으면 함수를 계속 반복 수행, 2 이상이면 loop를 종료하고, 수행한 반복 횟수를 기록함. 복소수 c의 값에 따라 반복 횟수가 달라지며, 반복 횟수를 가늠하기 힘듦.
c= -0.62772-0.42193j인 줄리아 집합의 시각화 2.3 전체 줄리아 집합 계산하기
아래 코드는 프로파일링을 하려는 가장 기본 julia 집합 계산 코드이다.
julia_nopil.py julia1_nopil.py를 수행하면 다음과 같은 결과를 얻을 수 있다.
책 에서는 8초가 소요되었는데, 맥북 에어 m2로 수행하니 2초 소요...julia_nopil.py 수행 결과; 2.4 시간을 측정하는 간단한 방법: print와 데커레이터
위 calc_pure_python 코드에서 start_time, end_time 등을 추가하여 함수의 동작에 대한 시간을 측정하였는데, 이보다 더 쉽게 "데커레이터"를 이용하여 시간을 측정할 수 있도록 해주는 방법을 알아본다.
데커레이터는 함수 전,후 동작을 정의해주는 함수이다. 참고 위 코드에서는 데코레이터를 만들어서 fn 함수를 수행하기 전 후 시간을 측정해서 함수 수행 소요 시간을 출력해주는 time_fn이라는 데코레이터를 만들어서 calculate_z_serial_purepython 함수에 적용한 것을 볼 수 있다.
julia1_decorator.py 수행 결과 위 수행 결과에서 "@timefn : ~" 은 데코레이터에서 출력된 결과.
유사하게 timeit 모듈을 이용해서 시간을 측정할 수 있다.
timeit을 이용하여 1라운드, 라운드당 5회 시간 측정 유닉스 time 명령어를 이용한 시간 측정 real : 경과 시간, user : CPU가 커널 함수 외 작업을 처리하느라 소비한 시간, sys : 커널 함수를 수행하는데 소비한 시간
--vervose 옵션을 추가하여 항목을 더 상세하게 봐도 좋음
(mac에선 해당 옵션이 없더라..)2.6 cProfile 모듈 사용하기
cProfile을 이용한 프로파일링 결과; 누적된 소비시간(cumulative) 기준으로 sort(-s) 기존의 프로파일링 기법을 사용하였을때는 2.3초 정도 소요되었으나, cProfiling을 수행할때는 6초 정도 소요되었음. --> cProfile으로 인한 오버헤드가 3.7초 정도 더 사용
<위 결과로 알 수 있는 사실>
1. 코드가 실행되는 julia1_nopil.py에서 6초 소요됨 (line 1)
2. main에서 calc_pure_python 함수를 한번 수행해였으므로 ncalls는 1 (line 23)
3. calculate_z_serial_purepython 함수가 5.5초 소비 --> calculate_z_serial_purepython 제외한 부분에서 0.5초 소요됨
4. calculate_z_serial_purepython 함수는 해당 함수를 실행하는데 4초 소요됨.
5. calculate_z_serial_purepython의 abs 함수는 총 34,219,980번 수행, 수행하면서 함수호출을 하며 1.4초 소요, 함수당 call 당 소요 시간은 매우 짧음 (0.000초)
6. list append는 2002000번 수행됨.
pstats 라이브러리를 이용해서 출력 print_callers() 와 print_callees()를 이용하여 어떤 함수에서 어떤 함수가 불렸는지 확인 가능
p.print_callers() p.print_callees() 2.7 SnakeViz로 cProfile 결과 시각화 하기
또한 snakeViz로 profile 결과 시각화도 가능함
2.8 line_profiler로 한 줄씩 측정하기
line_profiler를 이용하려면 함수 위에 @profile 데코레이터를 붙여야 함.
<라인 프로파일러로 알 수 있는 점>
라인 프로파일러를 실행하면서 총 10.4초가 소요되었다.
17번째 줄의 while문에서 전체 소요 시간의 43%를 소요했다는 것을 확인 가능.
z의 값을 갱신하는 line 18에서도 29.7%가 소요, line 19에서도 23.2%가 소요.
20번째 라인인 output 리스트를 생성하고 값을 갱신하는 코드는 비교적 빨리 끝남.
julia1_lineprofiler2.py 코드는 julia_lineprofiler.py의 17라인을 3 줄로 나누었다.
abs 함수와 비교 연산 중 비교연산자 수행 속도가 더 빨라서 빨리 끝나는 코드를 and로 묶어준 비교문의 왼쪽에 써주면 더 빨리 끝낼 수 있음. 해당 코드를 linefprofiler3.py와 같이 변경하면 더 빠른 수행 시간을 얻을 수 있음.
2.9 memory_profiler로 메모리 사용량 진단하기
line profiler와 memory_profiler의 코드상의 차이는 없다. 동일하게 @profile 데커레이터를 달아주면 됨.
저자의 노트북에서 수행하는데 2시간이 걸렸다고 함.
memory profiler와 내부의 mprof를 이용하여 통계를 시각화 할 수 있음.
calc_pure_python 함수에서 zs,cs 리스트들을 계산하여 append하기보다 calculate_z_serial_purepython 에서 값을 그때그때 계산해서 사용하는 쪽이 memory 사용량 측면에서 더 좋음.
2.10 Py-Spy로 기존 프로세스 살펴보기
py-spy는 실행중인 파이썬 프로세스들 보면서 top과 같은 인터페이스로 출력해주는 역할을 한다. (pyspy와 다름 주의)
2.11 바이트코드: 내부 작동
dis 모듈을 이용해 바이트코드 역어셈블 결과를 얻을 수 있음
원래 코드의 줄 번호, jump 지점, 연산 주소, 연산 이름, 매개변수, 원본 python 코드 등을 나타냄
동일한 동작을 하는 함수여도 bytecode를 더 많이 동작하게 하는 코드는 수행이 느리다. 따라서 bytecode를 더 짧게 만드는 함수를 만들어라.
2.12 최적화 중에 단위 테스트하기
단위 테스트와 coverage.py 를 이용하라.
@profile 데커레이터를 사용하면 단위 테스트에서 NameError가 발생하는데, 아무 일도 수행하지 않는 profile 데커레이터를 만들어서 수행되도록 하기.
2.13 성공적인 프로파일링 전략
- 성공적으로 프로파일링을 하려면 매 수행에서 (거의)동일한 결과가 나올 수 있도록 해주어야 함. (cpu, 전력 등의 문제 확인)
- 모듈 단위로 코드를 작성하고, 프로파일링을 할때는 단위테스트, 통합테스트를 모두 수행
- 코드 예상 동작에 대한 가설을 세운 뒤 프로파일링을 통해 검증
- git 등의 버전 관리 툴을 이용하여 소스코드 관리
느낀점:
실습이 매우 많았다. ㅎㅎ 프로파일링을 통해 알게되는 사실은 프로파일링 자체에서 나오는 것이 아니고, 결과를 분석하는데서 나오는 것 같다.
프로파일링 전 모듈화를 시켜서 쉬운 프로파일링과 유지보수를 도모하자
프로파일링 전 코드에서 의심이 가는 부분에 대한 가설을 세우자
바이트코드 생성을 하게되면 실제로 어떤 명령 순서로 코드가 진행되는 지 알 수 있기 때문에 동작 파악등을 수월하게 수행할 수 있을 것 같음.
'STUDY > 고성능 파이썬' 카테고리의 다른 글
고성능 파이썬 Chapter 05 - 이터레이터와 제너레이터 (1) 2024.12.08 고성능 파이썬 Chapter 04 - 사전과 셋 (0) 2024.12.08 고성능 파이썬 Chapter 03 - 리스트와 튜플 (1) 2024.12.08 고성능 파이썬 Chapter 1 실습 (1) 2024.11.28 고성능 파이썬 Chapter 1 - 고성능 파이썬 이해하기 (2) 2024.11.23