Basic Performance Tips

OSXDEV

Jump to: navigation, 찾기

Introduction to Performance Overview로 이동


이 챕터는 프로그램을 튜닝하는 실질적인 조언을 제공한다. 퍼포먼스 툴을 이용하여 모니터해야 하는 영역에 대해서 이야기하고, 퍼포먼스를 향상시키는 실질적인 팁들을 제공한다.


목차

[편집] 일반적으로 모니터해야할 영역

많은 퍼포먼스 문제들은 프로그램의 특정 부분에 대해서 추적될 수 있다. 코드를 설계하고 구현할때, 다음과 같은 영역에 대해 모니터하여 여러분이 설정한 퍼포먼스 목표에 도달하는지 확인하도록 하자.


[편집] 프로그램의 주요 작업에 대한 코드

프로그램 디자인 단계에서는, 사용자가 가장 많이 접하게 되는 작업이나 작업흐름에 대해 고려해야 한다. 구현 단계에서는 그러한 작업에 대한 코드를 확실히 모니터 하고, 허용할 수 있는 수준 이하로 퍼포먼스가 떨어지지 않는지 확인한다. 만약 그렇다면, 바로 그러한 문제를 해결하기 위한 행동을 취해야 할 것이다.

프로그램에 의해 수행되는 주요 작업은 프로그램마다 다르다. 예를 들면, 워드프로세서는 문자를 입력하고 출력하는 속도가 빨라야 할 것이고, 파일 유틸리티 프로그램은 하드디스크의 파일과 디렉토리를 조사하는 속도가 빨라야 할 것이다. 사용자가 주로 수행하게 되는 작업을 결정하는 것은 전적으로 여러분의 선택에 달려있다.

프로그램에서 속도가 느린 연산을 식별하는것에 관련된 자료가 필요하면, Code Speed Performance Guidelines을 보기 바란다.


[편집] 드로잉 코드

대부분의 프로그램은 어느 정도의 드로잉을 수행한다. 만약 프로그램이 오직 표준 윈도우와 컨트롤을 사용한다면, 드로잉 퍼포먼스에 관해서 크게 걱정할 필요가 없다. 그러나, 어떤 사용자정의된 드로잉을 수행한다면, 여러분의 드로잉 코드를 모니터하여 허용가능한 수준으로 실행되는지 확인할 필요가 있다. 특별히 다음의 것들 중의 어떤 것을 지원한다면, 드로잉 코드를 최적화 시킬 방법에 대해 연구해 봐야할 필요가 있다.

  • 바로바로 처리되는 리사이징
  • Custom view 드로잉 코드, 특별히 전체 화면을 갱신하지 않고 필요한 화면의 일부만 갱신할 수 있는지의 여부.
  • 텍스쳐 그래픽
  • 완전히 불투명한 view.

드로잉 퍼포먼스를 최적화시키는 것에 관한 자료가 필요하면, Drawing Performance Guidelines를 보기 바란다.


[편집] 프로그램 시작시 초기화 코드

프로그램의 처음 시작시 걸리는 시간은 프로그램의 자료구조를 초기화하고 사용자의 입력을 기다리는데까지 걸리는 시간이다. 그러나, 많은 프로그램들이 필요 이상으로 처음 시작시에 많은 작업을 처리하고 있다. 많은 경우 처음 시작시 수행되는 작업은 애플리케이션이 사용자의 입력을 처리하기 시작하는 시점 이후로 미뤄버릴 수 있다. 이렇게 함으로써 사용자는 애플리케이션이 빠르게 동작한다고 느낄 수 있고, 사용자에게 좋은 인상을 남길 수 있다.

Mac OS X 버젼 10.3.3 과 그 이전 버젼에서 실행되는 애플리케이션에 대해서, 처음 시작시 걸리는 시간을 개선시킬 수 있는 또 다른 방법으로 애플리케이션을 prebind 시키는 것이 있다. prebinding은 라이브러리의 주소 영역을 미리 계산하여 이 값을 애플리케이션 바이너리에 저장해놓은 방법이다. 이렇게 하여 dynamic loader(dyld)가 처음 시작시에 라이브러리의 메모리 영역을 계산하는 과정이 생략된다. Mac OS X 버젼 10.3.4에서 dyld의 개선으로 prebinding이 크게 쓸모없게 되었다.

Launch Time Performance Guidelines를 보기 바란다.


[편집] 파일 엑세스 코드

파일 시스템은 메모리와 CPU에 정보를 올리는 과정에서 병목현상을 유발시키는 요인이다. 파일에 접근하는 동안, 천만개의 인스트럭션이 수행된다. 그러므로 프로그램이 실제로 필요한 파일을 사용하는지와 그 사용이 적절한지를 확인하여야 한다.

사용하는 파일의 갯수를 줄이는 것은 파일과 관련된 퍼포먼스를 향상시킬 수 있는 한가지 방법이다. 파일에 접근하여야 한다면, 사려깊게 판단하고 다음의 내용을 명심하도록 하자:

  • 시스템 캐쉬가 어떻게 동작하는지 이해하고 캐쉬 사용을 어떻게 최적화시키는지를 안다. 한번 이상 조회되지 않을 데이터가 캐슁되는 것을 피하도록 하자.
  • 데이터를 읽고 쓰는 것을 가능한한 연속적으로 처리하자. 파일안에서 처리하는 위치를 옮기는 것은 새로운 위치를 찾는데 많은 시간이 소모된다.
  • 가능한한 파일에서 많은 블럭의 데이터를 읽어오자, 그러나 너무 많은 양의 데이터를 한번에 읽어오는 것은 또 다른 문제를 초래할 수 있다. 예를 들면, 32메가의 파일의 내용을 한번에 읽어들이면 처리가 끝나기도 전에 그 내용에 대해 paging을 발생시킬수가 있다.
  • 불필요하게 파일을 닫고 다시 읽어오는것을 피하도록 하자. 캐쉬가 사용가능하면, 그렇게 하는 것은 데이터가 변경되지 않았을지라도 캐쉬를 리프레쉬 시킬 수도 있다.

파일처리에 관련된 퍼포먼스 문제를 식별하고 고치는 자료가 필요하다면, Launch Time Performance Guidelines를 보기 바란다.


[편집] 애플리케이션 코드의 메모리 사용

(역자 주: 타이틀이 Application Footprint 였는데 내용에 맞게 의역해서 번역했습니다. 코드에서 malloc처럼 메모리를 사용하는 것에 대한 의미가 아니라, 코드 그 자체가 메모리에 올려지는 것을 의미하는 것입니다.)

프로그램 코드의 크기는 시스템 퍼포먼스에 엄청난 영향을 미칠 수 있다. 프로그램을 위한 더 많은 메모리 페이지가 사용될수록, 시스템과 다른 프로그램이 사용할 메모리 페이지가 줄어든다. 이러한 메모리에 대한 압력은 페이징을 유발시키고 전체 시스템 속도를 저하시킬 수 있다.

코드와 자료구조를 조직화하는 모든 것에 대하여 코드의 메모리 사용을 관리해야 한다. 올바른 내용이 메모리에 올라가 있는지, 메모리 페이지를 불필요하게 읽고 쓰고 있지는 않는지 확인해야 한다. 많은 메모리 사용을 유발하는 문제들은 다음과 같다:

  • 코드 페이지가 사용되지 않은 코드를 포함하고 있다. 컴파일러는 일반적으로 컴파일 모듈에서 코드를 조직화하게 되는데, 이것이 항상 최적의 방법이 될 수는 없다. 대신에, 활성 코드 경로(active code path)에서 함수를 제거하지만 여전히 코드 모듈에는 남아있게 할 수도 있다.
  • Static 이나 constant 데이터가 쓰기가능한 페이지에 저장된다. 페이징 동안에, 이 데이터는 불필요하게 디스크에 써진다.
  • 프로그램이 실제로 필요한 것보다 더 많은 심볼을 익스포트 한다.
  • 코드가 컴파일러와 링커에 의해 제대로 최적화되지 않는다.
  • 프로그램에 너무 많은 프레임워크가 포함되어 있다. 필요한 코드만 로드하도록 하자.

코드의 메모리 사용 문제를 해결할 수 있는 자료가 필요하다면, Code Size Performance Guidelines를 보기 바란다.


[편집] 메모리 할당 코드

영구적이거나 임시의 자료구조를 저장하기위해 프로그램은 메모리를 할당한다. 각각의 메모리 할당은 CPU time과 메모리 소모측면에서 그에 따른 비용이 들어간다. 언제 프로그램이 메모리를 할당하고, 어떻게 사용되는지 이해하여 이러한 비용을 감소시킬 수 있다.

프로그램의 메모리 사용을 이해하는 것은 사용량을 줄일 방법을 결정하는데 도움이 된다. autorelease된 Objective-C 객체가 너무 많은 페이징을 유발하기 전에 dealloc 되었는지 찾아낼 수 있다. 코드의 버그로 인해 메모리 누수(leak)가 일어나는 것을 찾을 수 있다. malloc 호출이 몇번 일어나는지를 확인하여, 새로운 메모리 블럭을 할당하는 대신에 기존의 것을 재활용 하도록 할 수도 있다.

메모리를 할당할때 따라야 할 한가지 중요한 법칙이 처리를 미루는 것이다. 메모리 사용이 실제로 필요할 때까지 메모리 할당을 미뤄라. 메모리 할당에 관하에 처리를 미룰 수 있는 몇가지 추가적인 방법에 대해서 처리를 미룬다에 나와있다.

메모리 할당 패턴을 최적화 하는 자료가 필요하다면, Memory Usage Performance Guidelines를 보기 바란다.

[편집] 기본적인 최적화 팁들

새로운 프로그램의 구현을 시작하기 전에, 여러분이 추가하는 것을 고려해 볼만한 몇가지 퍼포먼스 향상 기법이 있다. 비록 이러한 향상 기법이 모든 경우에 도움이 되지는 않겠지만, 최소한 디자인 단계에서 고려해 볼 필요는 있다.


[편집] 이벤트 기반의 핸들러를 사용한다

요즘의 모든 Mac OS X 애플리케이션은 시스템 이벤트에 응답하기 위해 Carbon Event Manager 또는 다른 이벤트 기반 모델을 사용한다. 시스템 폴링(polling)에 의해 이벤트를 가져오는 옛날의 방식은 엄청나게 비효율적이다. 실제로, 처리할 이벤트가 없어도 폴링 코드는 100퍼센트의 CPU time을 소비한다. 좀더 현대적인 이벤트 기반 API를 사용하는 것은 다음과 같은 잇점이 있다:

  • 프로그램의 사용자 반응성이 향상된다.
  • 애플리케이션의 CPU 사용량을 줄여준다.
  • 애플리케이션의 작업중인 세트(어떤 시점에 메모리에 올라와있는 코드 페이지의 갯수)를 최소화해준다.
  • 시스템이 활발히 파워를 관리할 수 있게 해준다.

Cocoa 프레임워크는 Carbon Event Manager 호출을 프레임워크의 클래스와 메소드에 추가하여 이벤트 주도적인 모델을 구현하고 있다. Cocoa로 작성된 애플리케이션은 자동으로 이러한 기능의 효과를 얻게 되고, 추가적인 변경도 필요로 하지 않는다. Carbon 애플리케이션은 명시적으로 Carbon Event Manager 호출을 지원해야 한다.

이벤트 기반 핸들러는 마우스나 키보드 이벤트같은 사용자 이벤트를 지원하는데 국한되지 않는다. 쓰레드는 각자의 run loop를 갖고 있어서 타이머, 네트워크 이벤트, 다른 외부의 데이터에 대한 요구에 응답한다. 애플리케이션은 Core Foundation(CFRunLoop) 또는 Cocoa (NSRunLoop) 인터페이스를 사용하여 run loop를 지원할 수 있다.


[편집] 프로그램에 쓰레드를 사용한다

다중 쓰레드를 지원하는 것은 체감속도나 실제 퍼포먼스 모두를 향상시키는데 좋은 방법이다. 멀티 프로세서 시스템에세는 멀티쓰레드 프로그램은 단일 쓰레드 프로그램보다 보통 훨씬 더 나은 퍼포먼스를 보여준다. 작업을 사용가능한 모든 프로세서에 분배함으로써 애플리케이션은 여러개의 작업을 한꺼번에 처리할 수 있다. 싱글 프로세서 시스템에서도 메인 쓰레드를 사용자 이벤트를 처리를 전담하도록 하여 체감속도를 향상시키는 효과를 얻을 수 있다.

멀티쓰레드 지원을 추가하기 전에 어떻게 이 쓰레드들을 효과적으로 이용할 수 있을지 고려해봐야 한다. 쓰레드를 생성하는 것은 그에 따른 오버헤드를 수반하기 때문에, 어떤 작업을 쓰레드로 따로 분리하여 처리할 것인지 신중히 고려해야 한다. 프로그램의 모든 작업이 처리할 양이 얼마 안되고, 각자 다른 시간에 수행된다면, 각각의 작업을 쓰레드로 따로 분리하고 싶지 않을 것이다. 대신 오래동안 존재하는 작업 쓰레드를 하나 생성하여 처리하는 것이 더욱 적합하다.

쓰레드에 관한 또다른 고려사항으로는 자료구조를 어떻게 보호하느냐는 것이다. 여러 쓰레드가 먼저 데이터를 고쳐도 안전한지를 확인하지 않고 동시에 그 데이터를 변경하면 문제가 발생한다. 코드에서 lock을 엄격하게 사용하여 데이터를 보호해야 한다. 특정 블럭의 코드를 동기화(synchronize) 시켜서 한번에 여러 쓰레드에 실행되는 것을 막을 수도 있다.

프로그램에 추가적인 쓰레드를 사용하고 싶다면, Multithreading Programming Topics를 보기 바란다.


[편집] Accelerate 프레임워크를 사용한다

애플리케이션이 스칼라 데이터에 대해 많은 수학적인 계산을 수행한다면, Accelerate 프레임워크(Accelerate.framework)를 사용하여 이러한 계산을 가속화할 수 있다. Accelerate 프레임워크는 사용가능한 벡터 처리 유닛(Velocity Engine으로도 알려진 PowerPC의 AltiVec 확장이나 Intel x86의 SSE 확장)을 사용하여 여러개의 계산을 병렬로 처리할 수 있다. 이 프레임워크를 이용하여 코딩하여, 각각의 플랫폼 아키텍쳐를 위한 별도의 코드 경로(path)를 만드는 수고를 덜 수 있다. Accelerate 프레임워크는 Mac OS X 가 지원하는 모든 아키텍쳐에 잘 최적화 되어있다.

Shark와 같은 툴을 이용하여 프로그램에서 Accelerate 프레임워크를 사용하여 이득을 얻을 수 있는 부분을 찾을 수 있다. Shark와 그밖의 툴에 더 많은 정보가 필요하면 Performance Tools를 보기 바란다.


[편집] 처리를 미룬다

애플리케이션이 불필요한 작업을 수행하는지 확인하는 것으로 간단하게 퍼포먼스를 향상시킬 수 있다. 애플리케이션 시간의 매 순간은 앞으로의 사용자 요청을 예상하는게 아니라 현재 요청에 대해 응답하는데 소모된다. 설정창 같은 것을 담고 있는 nib 파일처럼 지금 당장 필요한 자원이 아니라면 로드하지 마라. 그러한 작업은 파일 시스템에 접근하기 때문에 시간이 소모되고, 사용자가 설정창을 한번도 열지 않는다면 nib 파일을 로드하는 것은 시간낭비이다.

기본적인 방침은 사용자가 애플리케이션으로 부터 어떤 것을 요구하기 전까지 기다렸다가 그 요청을 수행하기 위해 필요한 자원을 이용하는 것이다. 오직 어느 정도 퍼포먼스에 이득이 되는 상황에서만 데이터를 캐쉬하도록 하자. 이후에 프로그램이 더 빨리 실행될 것이라는 가정으로 미리 로딩하여 캐쉬해 놓는 것은 실제로 메모리 부족 상황을 발생시켜 퍼포먼스를 떨어뜨릴수도 있다. 그러한 상황에서, 여러분이 캐쉬놓은 데이터는 사용되기 전에 디스크에 페이징되버릴 수 있다. 결과적으로 데이터가 사용되기 전에 디스크로부터 두번 읽혀지기 때문에 데이터를 캐쉬하여 얻고자 하던 이득이 손실이 되어 나타나게 된다. 정말로 데이터를 캐쉬하기를 원한다면, 데이터를 캐쉬하기 전에 주어진 작업이 일단 수행될때까지 기다려라.

처리를 미루는 것에 대해 그 밖에 다음과 같은 내용이 있다:

  • 실제로 메모리를 필요로 할때까지 메모리 할당을 미뤄라.
  • 0으로 초기화된 메모리 블럭을 사용하지 마라. calloc 함수를 이용하여 나중에 그러한 처리를 대신한다.
  • 시스템이 여러분의 코드를 나중에 로드할 수 있도록 하라. 시스템이 현재 처리에 필요한 코드만 로드할 수 있도록 여러분의 코드를 프로파일하고 조직하라.
  • 실제로 그 정도가 필요할때까지 파일의 내용을 읽는 것을 미뤄라.


[편집] 체감 속도를 향상시킨다

대부분의 경우에 체감속도는 실제적인 퍼포먼스만큼이나 중요하다. 많은 프로그램 작업들이 백그라운드 상에서, 분리된 쓰레드에서, 또는 유휴(idle) 시간에 실행될 수 있다. 이렇게 함으로써 프로그램 인터페이스가 사용자에게 더욱 잘 응답할 수 있도록 한다. 물론 이런 식의 체감속도의 향상이 모든 상황에 적절하지는 않다. 예를 들어, 백그라운드에서 처리되는 데이터가 사용자에게 즉시 필요한 것일수도 있다.

프로그램을 디자인 하는 단계에서, 어떤 작업이 백그라운드로 옮겨지기에 효과적인지 생각해보자. 예를 들어, 프로그램이 수많은 파일을 스캔해야 한다면, 이것을 백그라운드에서 돌리자. 비슷하게, 오래걸리는 계산이 있다면 백그라운드에서 실행하여 사용자가 계속 유저 인터페이스를 조작할 수 있도록 하자.

체감속도를 향상시킬 수 있는 다른 방법으로는 애플리케이션이 처음에 빨리 실행되도록 하는 것이다. 처음에 실행할때에 어플리케이션 인터페이스를 띄우는데 당장 필요없는 것들은 처리를 나중으로 미뤄버리자. 예를 들어, 당장 필요하지 않는 큰 자료구조의 생성은 애플리케이션이 처음 시작을 완료한 후로 미뤄버리자. 플러그인의 경우 실제로 사용되기 전까지 그 코드를 로딩하지 말자.


[편집] Mach-O 바이너리 포맷을 사용한다

Code Fragment Manager Preferred Executable Format(PEF)에 기반한 카본 애플리케이션을 갖고 있다면, 여러가지 이유로 Mach-O 실행파일 형태로 바꾸기를 고려해 볼 필요가 있다. 가장 큰 이유는 Mach-O가 Mac OS X의 가상 메모리 시스템에 최적화되어있기 때문이다. 그 밖의 다른 이유는 다음과 같다:

  • PEF 실행파일은 Intel기반의 매킨토시 컴퓨터에서 지원되지 않는다.
  • Mac OS X에서 Carbon 환경을 구현한 라이브러리들이 Mach-O 실행파일 포맷을 사용한다. Mach-O 실행파일은 PEF 실행파일과 다른 함수 호출 규약(calling convention)을 사용한다. PEF 코드로 부터 오거나 나가는 호출은 런타임시에 반드시 변환되어야 한다. 변환에 따른 오버헤드가 크지는 않지만, Mach-O를 사용한다면 불필요한 것이다.
  • Apple의 Mac OS X 개발 환경이 오직 Mach-O만 지원한다. Mac OS X에서 Apple의 개발 환경을 사용하던지 안하던지, Mac OS X의 퍼포먼스 툴은 PEF 실행파일보다 Mach-O 실행파일과 함께 사용하는 것이 훨씬 쉽다.
  • Mach-O 실행파일은 커널안의 다른 Mach-O 공유 라이브러리와 BSD API 루틴을 직접적으로 호출할 수 있다.
  • Mach-O는 just-in-time 바인딩을 지원한다, 함수가 처음 호출될때 함수에 대한 링크가 결정된다. PEF기반의 애플리케이션(그리고 이것이 링크하고 있는 모든 PEF 라이브러리들)에서의 모든 링크는 애플리케이션이 처음 시작될때 결정되어야 한다.

비록 Mach-O가 Mac OS 9에서 지원되지 않지만, Mach-O을 사용한다고 Mac OS 9의 지원을 포기해야 하는 것은 아니다. 애플리케이션 패키지를 Mac OS 9에서 PEF 바이너리로 빌드하고, Mac OS X에서 Mach-O 바이너리로 빌드할 수 있다. 이렇게 함으로써 실행파일을 지원하고자하는 운영체제에 맞게 최적화 시킬 수 있다. 더 많은 정보가 필요하면, Bundle Programming Guide를 보기 바란다.

Mach-O 포맷에 대한 개요와 퍼포먼스 튜닝에 있어서 어떤 이점이 있는지 알고 싶다면, Overview of the Mach-O Executable FormatCode Size Performance Guidelines를 보기 바란다.