최적화 요약

by Eonil

최적화에 대해 이런저런 말이 많지만 어려운 부분은 저로서는 감당하기 힘드네요.

개발시 간편하게 쓸 수 있는 최적화 기법만 정리해 봅니다.

  1. 하드웨어 활용
  2. 컴파일러 최적화
  3. 동적 분기 최소화
  4. 연산 결과 캐시

하드웨어 활용

첫째로 해야 하는 것이 하드웨어 활용입니다. 소프트웨어 레벨에서 아무리 뭘 하더라도 하드웨어를 따라갈 수는 없습니다. 단, 이것은 프로젝트 초기에 결정해야 합니다. 어떤 플랫폼과 프레임워크를 쓰는지에 대한 문제이니까요.

일반적으로 특정 하드웨어에 종속적인 해결법은 하드웨어가 바뀜에 따라 무효해지므로, 구현비용을 잘 생각해서 결정해야 합니다. 간단히 구현할 수 있다면 조건부 컴파일로 쓸 수 있겠죠. 표준 스펙에 기반한 하드웨어 가속 기능을 사용하는 것이 가장 이상적입니다. 이런 관점에서 볼 때 크로노스 그룹의 오픈 표준 시리즈는 상당히 괜찮습니다.

만약 가상 하드웨어 샌드박스 플랫폼 안에 안에 있더라도 동일합니다. 이경우 일반적인 하드웨어와는 달리 종류를 막론하고 샌드박스 밖에서 바로 수입된 기능의 성능이 가장 좋겠죠.

컴파일러 최적화

Code Complete 책에서도 현대 컴파일러 최적화는 너무 다양해 예측하기 힘드므로 프로파일링해야 한다고 나와 있습니다. 컴파일러 최적화는 제가 아는 한 가장 쉽습니다. 하나의 룰만 지키면 됩니다.

  • 수동 최적화하지 말 것

일반적으로 수동 최적화는 컴파일러 최적화를 방해합니다. 컴파일러나 하드웨어에 대해 통달하지 않았다면 항상 컴파일러 최적화가 더 똑똑합니다. 수동 최적화된 코드는 특이하므로 컴파일러가 인지하기 어렵습니다. 현재 컴파일러들은 배열 언롤이나 함수 인라이닝은 물론, SIMD를 자동으로 활용하는 자동 벡터화 기능도 포함하고 있습니다. 각 하드웨어에 맞는 SIMD를 수동 작성하는것보다는 이쪽이 훨씬 낫겠죠.

동적 분기 최소화

Dynamic Dispatch는 동적 언어의 핵심입니다. 언어가 유연할수록 느려질 수 밖에 없는 이유가 바로 이것입니다. 유연성의 핵심은 분기가 얼마나 자유로우냐인데, 자유로운 분기는 CPU상에서 실행시 최적화되기 힘드므로 빨라질 수 없습니다. 가상 함수 계열은 모두 동적 분기이므로 이를 줄이면 큰 성능 향상을 가져올 수 있습니다. 그래서 이러한 가상 함수를 지원하는 언어는 모두 가상 함수를 finalize 해 성능을 높이는 키워드를 지원합니다. 보통은 final 키워드이죠. 함수명에 사용해 함수의 오버라이드를 금지시키거나, 클래스명에 사용해 모든 멤버의 오버라이딩을 한번에 봉쇄하기도 하죠.

Objective-C의 메시징 메커니즘은 기본적으로 동적 분기입니다. 여기에는 final 같은 키워드도 없으니 분기 예측을 통한 실행 성능 형상은 기대할 수 없습니다. 그러므로 성능이 중요한 코드는 Objective-C로 작성해서는 안되며, C로 작성되어야 합니다. 하지만 보통 어플리케이션 로직과 UI는 성능보다 유연성을 필요로 하므로 Cocoa의 역할은 알맞다고 할 수 있습니다.

연산 결과 캐시

사실상 프로그래머가 로직으로 간단히 할 수 있는 유일한 최적화 기법이라 할 수 있습니다. 대부분의 경우 소개되는 최적화 기법은

  1. 컴파일러 최적화를 방해하거나
  2. 캐시하거나

둘 중에 하나입니다. 캐시는 일종의 연산 제거입니다. 동일한 연산의 불필요한 중복 수행을 방지하는 컨셉이죠. 단점이라면 메모리가 필요하다는 것입니다. 제 생각에는 일반적인 프로그래머의 입장에서 캐시 외에는 답이 없다고 봅니다. 하드웨어에 밀접하고 컴파일러가 최적화를 잘 못해준다면 이야기가 좀 다르겠지만요.

캐시를 잘 작성하기 위한 방법은 한 가지입니다.

  • 최적화하지 말것. 느리다면 그 때 프로파일링하고 병목을 제거할 것.

맥코넬씨가 강조하는 핵심이죠. 일단 프로그램 작성시에는 효율을 생각하지 않고, 로직과 구조에만 집중해서 제작합니다. 예를 들면:

데이터가 필요하다면 그때그때 파일을 열어 DB에서 읽어옵니다.

  1. 이게 느리면 파일 핸들을 캐시하고,
  2. 그래도 느리면, DB 연결도 캐시하고,
  3. 그래도 느리면 쿼리 결과도 캐시합니다.
  4. 그래도 느리다면 결과셋이 표시된 UI도 캐시해야겠죠.

DB 내에도 일종의 캐시 구조가 있을 것입니다. 인덱스라는 기능이 있죠. 미리 계산된 데이터 위치를 따로 저장하는 것입니다. 캐시는 메모리와 프로세싱 사이에서 트레이드 오프가 가능하므로, 전체적인 시스템 사양에 따라 동적으로 조절될 수도 있습니다. 하지만 무엇보다도 만들기 쉽다는 것이 장점입니다. 전체 시스템 구조를 변경하지 않고 캐시를 추가하는 것은 어렵지 않습니다.