행렬과 벡터계산
루프 안에서 sin(num_iterations) 값은 변경되지 않으므로 매번 계산할 필요가 없다.
파이썬은 벡터 연산을 기본으로 제공하지 않는다. 그 이유는 파이썬의 리스트는 실제 데이터를 가리키는 포인터를 저장한다는 점과 파이썬 바이트 코드는 벡터 연산에 최적화되지 않았다는 점이다.
이때 포인터는 벡터와 행렬 연산에서 성능 저하의 원인이 된다.
예를 들어 example[5][2] 를 실행하면 example 리스트에서 5번째 항목을 찾아 반환한 다음 2번째 항목을 찾아야지만 그 항목의 데이터 위치를 알수있기 때문이다.
CPU 로 행렬을 계산하게 되면 어마어마한 미스 레이턴시가 발생한다.
이로 인해 CPU의 성능이 느려지는데, 그 이유는 CPU의 캐시에 데이터를 여러벌 준비할수 없고, 만약 준비한다 하여도 파이썬의 리스트는 실제 데이터가 아니라, 데이터를 가리키는 포인터이기 때문에 메모리 여기저기에 실제 데이터가 흩허져 있어 벡터화 시킬수 없다. 이를 array 모듈로 완화할수 있지만 array를 순회하는 속도는 list 를 순회하는 속도보다 더 느리기 때문에 문제는 아직 남아있다.
이를 numpy 를 통해 해결할수 있다. numpy는 명령의 개수를 줄여 캐시 미스를 개선하였고, 제자리 연산으로 인해 메모리의 지역성이 향상된다. 속도가 개선됨을 볼수있는데, 이를 자세히 살펴보면 벡터 연산을 통한 속도 개선이 아닌, 메모리 단편화의 감소와 메모리 지역성 덕분임을 볼수있다.
Pandas
행 연산방식
1. iloc 를 사용해 한번에 한행씩 가져와서 계산 (18.6초)
iloc 은 새 row_idx 에서 행을 가져오려고 아주 많은 작업을 수행한다. 역참조에는 큰 비용이 들어 가장 느린 방식이다.
ms = []
for row_idx in range(df.shape[0]) :
row = df.iloc[row_idx]
m = function(row)
ms.append(m)
result = pd.Series(ms)
2. iterrows 를 사용해 파이썬 다운 효율적 행 연산 (12.4초)
이 방법은 위치르 그리 많이 검색하지 않으므로 조금 더 효율적이다.
iterrows는 순차적 검색을 하지 않고도 행 사이를 오갈수 있다. 하지만 여전히 새로운 Series 객체를 만들어서
row 에 저장한다
ms = []
for row_idx, row in df.iterrows() :
m = function(row)
ms.append(m)
result = pd.Series(ms)
3. apply 를 사용해 전형적인 Pandas 함수 적용 (6.8초)
이 방법은 수많은 Pandas 장치를 건너뛰므로 오버헤드를 많이 피할수 있다.
apply 는 새로운 파이썬 중간 참조를 만들지 않고 function 을 데이터 행에 직접 넘긴다.
하지만 여기서도 내부적으로 행마다 새 Series 가 만들어진다
ms = df.apply(function, axis = 1)
result = pd.Series(ms)
4. raw = True 를 사용해 중간 Series 생성 방지하기 (5.3초)
raw = True 인자로 넘기면 중간 Series 객체가 만들어 지지 않으므로, 역참조를 피하여 실행시간을 단축시킬수 있다.
ms = df.apply(function, axis = 1, raw = True)
result = pd.Series(ms)
# 부분 결과를 붙이지 않고 DataFrame 과 Series 만들기
일반적으로 pandas 에서 concat 을 반복 호출하는 일은 피해야 한다. 이는 중간 Series 를 만들어 역참조함을 피하여 시간 단축을 할수있게 한다.
# 문자열 처리에서 str Series 연산과 apply 비교
한줄짜리 방법에서는 중간중간 Series 객체를 만들어야 되는데 이런 부가비용이 모두 모이면 꽤 커진다.
하지만 find(9) 에서는 문자열 처리가 모두 한번에 한줄씩 처리된다.
#효율적인 pandas 개발을 위한 조언
1. 선택적 의존관계인 numexpr 과 bottleneck 설치해서 성능을 더 높여라
코드 기반에는 bottleneck 을 거의 사용하지 않지만, numexpr 을 활용하면 속도가 상당히 빨리지기도 한다.
2. 코드를 너무 간략하게 작성하지 말라
3. 처리를 필요 이상으로 하지 말라
계산하기 전에 데이터에 필터를 적용하는 편이 계산을 수핸한후 필터를 적용하는 것보다 낫다.
4. DataFrame 스키마를 변경하때마다 bulwark 같은 도구를 사용하여 스키마를 검사하라
이 검사를 텅해 데이터가 스키마를 만족하는지 보장, 코드 리뷰 과정에서 예상에 맞게
코드와 스키마가 작성됐는지 시각적으로 확인 가능
5. 카디널리티가 작은 문자열(ex."yes/no" , "타입1/타입2/타입3")
Series dtype 을 .astype('category') 로 변환하라
이렇게 하면 value_counts 와 groupby 같은 연산이 더 빨리 작동하고, Series 도 RAM 을 더 적게 사용한다
이와 비슷하게 8바이트 float64나 int64 도 2바이트인 float 16 이나 1 바이트인 int 8 로 RAM 사용량을 줄일수 있다.
6. inplace = True 연산자를 피하라
7. 모든 처리 코드에 단위 테스트를 추가하라
Vaex 라이브러리는 pandas 와 비슷한 인터페이스를 유지하면서 지연 계산을 활용해 RAM 크기를 벗어나는
아주 큰 데이터셋을 처리하도록 설계하였다.
또한 다양한 내장 시각화 도구를 제공한다.
'컴퓨터 공부 > 파이썬 공부' 카테고리의 다른 글
고성능 파이썬(7) (0) | 2021.07.11 |
---|---|
고성능 파이썬(5) (0) | 2021.07.04 |
고성능 파이썬(4) (0) | 2021.06.28 |
고성능 파이썬(3) (0) | 2021.06.27 |
고성능 파이썬(2) (0) | 2021.06.27 |