루프 최적화는 특정 언어에 국한되지 않는 보편적인 주제입니다. 루프는 거의 모든 프로그래밍 언어에 존재하며, 최적화 원칙 역시 어디서든 동일하게 적용됩니다. 컴파일러가 루프 최적화의 상당 부분을 자동으로 처리해 주지만, 개발자 역시 루프를 효율적으로 작성하기 위해 노력해야 합니다.
루프 최적화의 핵심 원칙
가장 먼저 기억해야 할 사실은 루프 내부에 작성한 모든 코드가 매 반복(iteration)마다 실행된다는 점입니다. 따라서 루프 최적화의 핵심은 루프가 수행하는 작업량을 최소한으로 줄이는 것입니다.
겉보기에는 매우 빠른 연산이라도 수백만 번 반복되면 상당한 시간이 소요됩니다. 예를 들어 1마이크로초(μs)가 걸리는 연산을 백만 번 실행하면 총 1초가 걸립니다. 이처럼 사소해 보이는 비용도 반복 횟수가 많아지면 무시할 수 없게 됩니다.
len() 호출을 루프 밖으로 빼내기
대표적인 안티 패턴은 루프 본문이나 시작 조건에서 len(list)와 같은 함수를 반복해서 호출하는 것입니다. 다음 두 코드를 비교해 보세요.
a = [i for i in range(1000000)]
length = len(a)
for i in a:
print(i - length)위 코드는 아래 코드보다 훨씬 빠르게 실행됩니다.
a = [i for i in range(1000000)]
for i in a:
print(i - len(a))첫 번째 코드는 리스트 길이를 미리 변수에 저장해 두었기 때문에, 백만 번의 반복 동안 불필요한 함수 호출이 전혀 발생하지 않습니다. 반면 두 번째 코드는 매 반복마다 len()을 호출하므로 그만큼 오버헤드가 누적됩니다.
루프 언롤링(Loop Unrolling) 활용
성능이 중요한 경우 루프 언롤링(Loop Unrolling) 기법을 고려할 수 있습니다. 이는 루프 변환(loop transformation) 기법의 하나로, 반복 횟수를 줄이기 위해 루프 본문을 여러 번 펼쳐서 작성함으로써 프로그램의 실행 속도를 높입니다. 다만 코드 크기(바이너리 크기)가 증가하는 trade-off가 있으며, 이러한 접근 방식을 공간-시간 트레이드오프(space-time tradeoff)라고 부릅니다.
map, filter 등 내장 함수 활용
명시적인 for 루프를 직접 작성하는 대신 map, filter와 같은 내장 함수를 사용하면 추가적인 성능 향상을 얻을 수 있습니다. 이러한 함수들은 C 레벨에서 구현되어 있어 파이썬 인터프리터의 반복 오버헤드를 줄여주기 때문입니다.