성능 최적화
NumPy의 성능을 최대한 활용하려면 벡터화(Vectorization), 메모리 레이아웃, 효율적 연산 패턴을 이해해야 합니다. Python 반복문을 NumPy 연산으로 대체하는 것만으로도 10~100배의 속도 향상을 얻을 수 있습니다.학습 목표
- 벡터화의 원리를 이해하고 반복문을 벡터화 코드로 변환할 수 있다
- C-order와 F-order 메모리 레이아웃의 차이를 이해한다
np.einsum()으로 복잡한 텐서 연산을 간결하게 표현할 수 있다- 대용량 배열 처리 전략을 적용할 수 있다
왜 중요한가
대규모 데이터셋을 다룰 때 연산 속도는 생산성에 직접적인 영향을 미칩니다. 100만 행의 데이터를 처리할 때 벡터화 코드는 반복문 대비 수십 배 빠르며, 메모리 레이아웃을 최적화하면 캐시 효율이 극적으로 향상됩니다.벡터화 vs 반복문
벡터화 변환 패턴
| 반복문 패턴 | 벡터화 대체 |
|---|---|
for + 조건문 | np.where() |
for + 누적 | np.cumsum(), np.cumprod() |
이중 for (거리 계산) | 브로드캐스팅 |
for + if/else | np.select() |
for + 집계 | np.sum(axis=...) |
메모리 레이아웃
C-order vs F-order
| 레이아웃 | 저장 순서 | 빠른 접근 방향 | 사용 |
|---|---|---|---|
| C-order (행 우선) | 행 → 열 | 행 방향 순회 | NumPy 기본, 이미지 |
| F-order (열 우선) | 열 → 행 | 열 방향 순회 | FORTRAN, 일부 선형대수 |
contiguous 배열 만들기
np.einsum
np.einsum()은 아인슈타인 합 규칙(Einstein Summation Convention)으로 복잡한 텐서 연산을 간결하게 표현합니다.
| einsum 표기 | 연산 | 동등 함수 |
|---|---|---|
'ij,jk->ik' | 행렬 곱셈 | A @ B |
'ii->' | 대각합 | np.trace() |
'i,i->' | 내적 | np.dot() |
'i,j->ij' | 외적 | np.outer() |
'ij->ji' | 전치 | A.T |
'ij->' | 전체 합 | np.sum() |
'ij->i' | 행별 합 | np.sum(axis=1) |
대용량 배열 전략
AI/ML에서의 활용
- 배치 처리: 벡터화 연산으로 전체 배치를 한 번에 처리하여 학습 속도를 높입니다
- 텐서 연산:
einsum()으로 어텐션 메커니즘의 복잡한 텐서 연산을 구현합니다 - 메모리 최적화:
float32로 dtype을 줄여 GPU 메모리를 절약합니다 - 대용량 데이터:
memmap으로 메모리보다 큰 데이터셋을 처리합니다
np.einsum이 항상 더 빠른가요?
np.einsum이 항상 더 빠른가요?
반드시 그렇지는 않습니다. 간단한 연산(행렬 곱셈 등)에서는
@ 연산자가 BLAS 라이브러리를 직접 호출하므로 더 빠를 수 있습니다. einsum()은 복잡한 다차원 연산을 간결하게 표현할 때 주로 유용합니다. optimize=True 파라미터를 사용하면 연산 순서를 자동 최적화합니다.벡터화할 수 없는 연산은 어떻게 하나요?
벡터화할 수 없는 연산은 어떻게 하나요?
np.vectorize()를 사용할 수 있지만, 이것은 내부적으로 반복문이므로 진정한 벡터화가 아닙니다. 성능이 중요하다면 Numba의 @jit 데코레이터나 Cython을 고려하세요.체크리스트
- 반복문 코드를 벡터화 코드로 변환할 수 있다
- C-order와 F-order의 차이를 이해하고 접근 패턴에 맞게 선택할 수 있다
-
np.einsum()으로 기본적인 텐서 연산을 표현할 수 있다 - dtype 다운캐스트와 memmap으로 메모리를 최적화할 수 있다
- strides의 의미를 이해하고 캐시 효율적인 접근 패턴을 설계할 수 있다

