2017년 4월 8일 토요일



7.1 상수 참조로 입력 파라미터 전달

객체가 변경되지 않는 상황이라면, 언제든 값보다는 상수 참조로 파라미터를 전달하는 것이 좋다. 그러면 객체를 임시로 복사하고 멤버와 상속된 객체를 생성시킨 후 종료시키는 데 필요한 메모리와 성능상의 비용을 줄일 수 있다.

void SetValue(const std::string &str); //상수 참조로 전달

값으로 객체를 전달하기보다는 상수 참조로 파라미터를 전달해야 복사 손실 문제를 해결할 수 있다.



7.2 #INCLUDE 의존성 최소화

빌드 시간을 최소화하기 위해 헤더 파일에 있는 #include 문의 수를 줄인다.



7.3 "한곳에 몰아두는" 헤더 금지

커다란 공통 파일을 최적화하도록 전처리기를 통해 .pch나 .gch 같은 미리 컴파일된 헤더 파일 형태로 만들어서 성능에 따른 비용을 줄일 수 있다. 하지만 모듈화나 느슨한 관계의 관점에서 보면 각각의 컴포넌트별로 헤더 파일을 따로 나누어 그 크기를 줄이는 방법이 더 효과적이다.



7.2.2 전방 선언

전방 선언이 가능한 경우
1. 클래스의 크기가 필요치 않을 때, 만약 클래스를 멤버 변수나 서브 클래스로 포함시킬 경우, 컴파일러는 그 클래스의 크기를 알아야 한다.
2. 클래스의 어떤 멤버 메서드도 참조할 필요가 없을 때, 만약 메서드를 참조 할 경우 파라미터와 리턴 타입이 같은 메서드의 프로토타입이 필요하다.
3. 다른 클래스의 멤버 변수를 참조할 필요가 없을 때, public(또는 protected)으로 사용하지 않을 때.

Tip. #include 문은 다른 클래스를 데이터 멤버로 사용하거나 그 클래스를 상속받을 경우에만 사용하는 것이 좋다.


7.2.3 #include 중복 방지

클라이언트의 컴파일 시간을 최적화할 수 있도록 헤더 파일에 #include 중복 제거를 하라.

#ifdef BIGFILE_H
#include "bigfile.h"
#endif



7.3 상수 선언

const int MAX_NAME_LENGTH = 128;
const std::string LOG_FILENAME = "filename.log";

여기서 문제점은 아주 간단한 내장 형식의 상수만이 컴파일러에 의해 인라인으로 처리된다. 기본적으로, 위와 같은 방식으로 정의한 모든 변수는 이 코드가 선언된 헤더 파일을 포함하는 모든 모듈 안에서 변수를 위한 공간을 할당하도록 컴파일러에게 요청한다.

//공간할당을 문제를 해결하는 방법
extern const int MAX_NAME_LENGTH;
extern const std::string LOG_FILENAME;

이렇게 상수를 선언하고 난 후, 각 상수 값은 각각의 .cpp 파일에 정의해서 변수를 위한 공간을 한 번 할당한다.

클래스 안에 선언할 수 있다면 상수를 static const로 선언언하자.

//.h
class MyAPI
{
public :
   static const int MAX_NAME_LENGTH;
   static const std::string LOG_FILENAME;
};

//.cpp
const int MyAPI::MAX_NAME_LENGTH = 128;
const int MyAPI::LOG_FILENAME = "filename.log";

Tip. 전역 상수를 extern으로 선언하거나 클래스 내에서 static const로 상수를 선언하라. 그 후에는 .cpp 파일에서 ㅅ아수의 값을 정의한다. 이 방법은 헤더 파일을 포함하는 모듈의 객체 파일 크기를 줄여준다. 이들 상수를 함수 호출 내부로 숨기는 것도 좋은 방법이다.


7.3.1 새로운 constexpr 키워드

class MyAPI
{
public :
   constexpr int GetMaxNameLength() { return 128; }
   constexpr std::string GetLogFilename() { return "filename.log"; }
};


7.4 초기화 리스트

class Avatar
{
public :
   Avatar(const std::string &first, const std::string &last);
}

Avatar::Avatar(const std::string &first, const std::string &last) :
 mFirstName(first),
 mLastName(last)
{
}

Tip. 각각의 데이터 멤버가 자신의 생성자를 호출하면서 발생하는 비용을 줄이기 위해 생성자 초기화 리스트를 사용한다. 대신 멤버 변수의 선언을 .cpp파일 안에서 옮겨 구체적인 코드 구현을 감춘다.



7.5 메모리 최적화

1. 타입별로 멤버 변수 묶기 : cpp 컴파일러는 word 크기의 영역에 메모리 주소를 할당하도록 특정 데이터 멤버별로 정렬시킬 것이다.
2. 비트 필드 사용 : 비트 필드는 멤버 변수를 위한 데코레이터로 int tinyInt:4와 같이 얼마만큼의 비트를 변수가 사용해야 하는지 명시한다.
3. 유니온 사용 : 같은 메모리 공간을 공유하는 데이터 멤버가 모인 구조체
union {
   float floatValue;
   int intValue;
   FloatOrIntValue;
}
4. 필요하기 전까지는 가상 메서드를 추가하지 않기 : 가상 메서드를 클래스에 추가하고 나면 그 클래스는 vtalbe을 갖는다.
5. 명시적 크기 기반 타입 사용
class Firworks_D
{
   bool mIsActive:1;
   bool mVaryColor:1;
   bool mRepeatCycle:1;
   bool mFadeParticles:1;
   char mRed;
   char mGreen;
   char mBlue;
   char mRedVariance;
   char mGreenVariance;
   char mBlueVariance;
   int mTotalParticles;
   short mOriginX;
   short mOriginY;
};
Tip. 1.객체의 크기를 최적화하기 위해서 객체가 가진 변수의 타입별로 변수들을 묶어라.
2. 객체의 크기를 좀 더 압축하고 싶다면 비트 필드를 검토하자.
3. 변수가 필요한 최대 비트 수를 명시하기 위해 int32_t나 uint16_t 같은 크기가 명시된 타입을 사용하자.


7.6 필요시까지 인라인 코드 사용 금지

코드가 성능 문제를 가지고 있고 인라인 코드를 통해 그 문제를 해결할 수 있다는 것을 확인하기 전까지는 public 헤더 파일에 인라인 코드를 사용하지 않는 것이 좋다.

만약 사용해야 한다면 inline문을 아예 분리된 헤더 파일로 옮긴다.

class Vector
{
public :
   double GetX() const;
   double GetY() const;
};

#include "detail/Vector.h"

//detail/vector.h
inline double Vector::GetX() const { return mX }
inline double Vector::GetY() const { return mY }



7.7 카피-온-라이트

메모리의 사용을 줄이는 가장 좋은 방법은 정말 필요할 때가 아니면 메모리를 할당하지 않는 것이다. 이것이 카피-온-라이트의 근본적인 목표이다.

카피-온-라이트를 구현하는 몇 가지 방법이 있는데, 그 중 가장 많이 사용하는 방법은 클래스 템플릿의 선언이다. 클래스 템플릿을 선언하면 공유 템플릿이나 약한 포인터를 생성하는 것과 같은 방법으로 카피-온-라이트 기법으로 객체의 포인터를 생성한다.

#include <boost/shared_ptr.hpp>
template<class T>a class CowPtr
{
public :
typedef boost::shared_ptr<T> RefPtr;

inline CowPtr() : mPtr(0) {}
inline _CowPtr() {}
inline explicit CowPtr(T *other) : mPtr(other) {}
inline CowPtr(const CowPtr<T> &other) : mPtr(other.mPtr) {}

inline T&operator*()
{
   Detach();
   return *mPtr.get();
}
inline const T &operator*() const { return *mPtr.get(); }
inline T *operator->()
{
   Detach();
   return mPtr.get();
}
inline const T *operator-> const { return mPtr.get(); }
inline operator T *()
{
   Detach();
   return mPtr.get();
}
inline operator const T *() const { return mPtr.get(); }
inline T *data()
{
   Detach();
   return mPtr.get();
}
inline const T *data() const { return mPtr.get(); }
inline const T* constData() const { return mPtr.get(); }
inline bool operator==(const CowPtr<T> &other) const
{
   return mPtr.get() == other.mPtr.get();
}
inline bool operator !=(const CowPtr<T> &other) const
{
   return mPtr.get() != other.mPtr.get();
}
inline bool operator!() const { return !mPtr.get(); }
inline CowPtr<T> &operator=(const CowPtr<T> &other)
{
   if(other.mPtr != mPtr)
      mptr = other.mPtr;
   return *this;
}
inline CowPtr &operator=(T *other)
{
   mPtr = RefPtr(other);
   return *this;
}
private:
   inline void Detach()
   {
      T* temp = mPtr.get();
      if( temp && !mPtr.unique())
         mPtr = RefPtr(new T(*temp));
   }
   RefPtr mPtr;
};


7.8 요소 반복

7.8.2 임의 접근
[] 연산자, at() 메서드

Tip. 데이터 구조를 단순히 순차적으로 탐색하기 위해서는 반복자를 사용하는 것이 좋다. 만약 연결 리스트나 트리 구조를 사용해야 한다면, 탐색 속도가 중요한 상황에서는 배열 참조를 사용해야 할 것이다.



7.9 성능 분석

7.9.1 시간 기반 분석
1. 자체 실행 : 스스로 측정 코드를 작성하는 것
2. 바이너리 성능 측정 : 각 함수 호출에 대한 구체적인 정보를 기록하는 프로그램 또는 공유 라이브러리를 추가해서 성능을 측정한다.
3. 샘플링 : 측정하고자 하는 애플리케이션의 어느 부분에서 성능 누수가 발생하는지를 밝혀내기 위해 지속적으로 애플리케이션의 샘플을 채취하는 별도로 실행되는 프로그램
4. 카운터 모니터링 : 사용자에게 시스템 동작 내용을 보여주는 성능 카운터를 제공

인텔 VTune, AMD CodeAnalyst, CodeProphet Profiler, DTrace

7.9.2 메모리 기반 분석
IBM Rational Purify, Parasoft insure++, Coverity, MALLOC_CHECK

7.9.3 멀티스레드 분석
Intel Thread Checker, Intel Thread Profiler, Intel Parallel Studio, Acumem ThreadSpotter, Helgrind 와 DRD(http://valgrind.org)
Next
This is the most recent post.
Previous
이전 게시물

0 개의 댓글:

댓글 쓰기