우리팀 워크샾
2006년 11월 30일(목) ~ 12월 1일(금) 1박 2일, 솔루션개발팀 워크샾 사진

1. 불꽃놀이

2. 산행전
3. 울산바위
워크샾 갔다와서 바쁜 관계로 이제야 사진 올림~~
by 워니 | 2006/12/13 17:30 | Daily life | 트랙백 | 덧글(0)
Duff's Device
 루프 최적화란 루프의 반복 회수를 줄이는 것을 말할 것입니다. 왜냐하면, 루프와 같은 분기 명령(JMP)이 많이 발생하면 할 수록 시스템의 성능을 저하될 것이기 때문입니다. 제 경우 현재 개발중인 프로그램에서 검색, 삽입, 수정, 삭제 처리를 위해 루프가 많은 구조다 보니, 이 루프들을 어떻게 효율적으로 줄일 수 있을까? 고민하다가 Duff's Device를 발견하게되었습니다.

1. Duff's Device
Duff's Device는 1983년 루카스필름에서 일하던 Tom Duff가 애니메이션 재생시 생기는 병목현상을 해결하기 위해 만들었다고 합니다. 그 당시에도 많은 논란이 있었다고 합니다만, 이 코드를 처음 본 제 느낌은 '우앗!' 이었습니다. 그리고 가까운 시일 내에 좀 더 테스트를 해보겠지만, 적용된다면 일반 루프에 비해 훨씬 효율적일것이라 생각합니다.

1.1 일반적인 복사
아래의 함수는 nArrSrc int형 배열의 값을 nArrDest로 복사하는 함수입니다.

const int MAX_ARRAY_SIZE = 10;

void CopyArray(int* nArrSrc, int* nArrDest, int nCount)
{
    do
    {
        *nArrDest++ = *nArrSrc++;
    } while( --nCount > 0 );
}

...
int nArrSrc[MAX_ARRAY_SIZE];
int nArrDest[MAX_ARRAY_SIZE];
...
CopyArray(nArrSrc, nArrDest, MAX_ARRAY_SIZE);

위의 예는 정상적으로 작동합니다. 그런데 만약 nCount가 1000번 혹은 10000 번이라면 얘기는 조금 달라지게 될 것입니다. 즉, 앞서 말한바와 같이 루프는 성능을 저하시키는 한 요소이기 때문입니다

1.2 Duff's Device 적용
앞서 구현한 CopyArray 함수는 Duff's Device에 의해 다음과 같이 수정될 수 있습니다.

const int REPEAT_COUNT = 8;

void CopyArrayEx(int* nArrSrc, int* nArrDest, int nCount)
{
    switch( nCount % REPEAT_COUNT )
    {
    case 0 :
        do {
        *nArrDest++ = *nArrSrc++;
    case 7 :
        *nArrDest++ = *nArrSrc++;
    case 6 :
        *nArrDest++ = *nArrSrc++;
    case 5 :
        *nArrDest++ = *nArrSrc++;
    case 4 :
        *nArrDest++ = *nArrSrc++;
    case 3 :
        *nArrDest++ = *nArrSrc++;
    case 2 :
        *nArrDest++ = *nArrSrc++;
    case 1 :
        *nArrDest++ = *nArrSrc++;
        } while ( (nCount -= REPEAT_COUNT) > 0 );
    }
}

위의 코드를 보면, switch-case문을 마치 goto문 처럼 사용한 것을 보실 수 있는데, 이를 보고 개인적으로 놀랐습니다. 세상엔 참 머리 좋은 사람이 많구나 하고요. 또한 REPEAT_COUNT로 8을 사용하였는데, 이는 캐쉬 크기에 맞춘 작업으로 보입니다. 일반적으로 위와 같이 loop unrolling 기법을 사용할 때에는 루프를 무한정 늘리는 것이 아닌 캐쉬 크기에 맞춰 8 혹은 16배의 loop unrolling을 사용한다고 합니다.

참고 자료
http://www.lysator.liu.se/c/duffs-device.html
http://en.wikipedia.org/wiki/Duff's_device
• 여인춘, 프로그래밍 고수의 알고리즘 , 마이크로 소프트웨어 2006년 10월호, pp.298-pp.300, Loop unrolling
 
by 워니 | 2006/11/28 01:00 | C++ | 트랙백(1) | 덧글(1)
멤버 초기화 목록(The member initialization list)
객체를 사용할 때는, 반드시 해당 객체를 초기화하는 습관을 갖자.

1. 멤버 초기화 목록
객체를 사용할 때는 멤버 초기화 목록(The member initialization list)을 통해 객체의 멤버 변수들을 초기화해야 한다. 왜냐하면, C++ 규칙은 '객체의 데이터 멤버는 해당 객체의 생성자 본문에 들어가기 전에 초기화 되어야 한다'는 조건을 요구하고 있기 때문이다. 또한 멤버 초기화 목록은 효율성과도 관련이 있는데, 이에대해 다음의 클래스를 통해 알아보고자 한다.

class CPerson
{
public:
    CPerson(const CPerson& objPerson);
    CPerson(const CString& strName, const CString& strAddress);
    ~CPerson(void);

private:
    CString  m_strName;
    CString  m_strAddress;
...
};

CPerson::CPerson(const CString& strName, const CString& strAddress)
    : m_strName(strName)
{
    m_strAddress = strAddress;
}
                                        [예제 참고 : The C++ Programming Language]

2. 멤버 초기화 목록 사용법
멤버 초기화 목록은 콜론(:)으로 시작
각 멤버의 초기화는 쉼표(,)로 구분

3. 대입과 멤버 초기화 목록
 위의 코드에서 생성자를 보면, m_strName은 객체 초기화 목록으로 넘어온 strName에 의해 바로 초기화된 반면, m_strAddress는 우선 빈 문자열로 초기화 되고 나서, strAddress의 사본이 대입되고 있는 형태이다.
여기서 m_strAddress에 대해 좀더 자세히 들여다보면, 대입을 사용하므로써

기본 생성자를 통해 m_strAddress 초기화
 m_strAddress에 새로운 값 strAddress를 대입

그러므로 에서 수행된 기본 생성자에 의한 초기화는 전혀 쓸모없는 일이 되어버리는 상황이다. 반면, 초기화 멤버 목록을 사용한 m_strName의 경우에는

m_strName은 strName으로부터 복사 생성자에 의해 바로 초기화

멤버 초기화 목록은 대입을 통한 수행보다 효율성을 갖는다.

4. 생성자의 호출 순서
클래스의 멤버가 선언된 순서(즉, Person.h에서 멤버 변수가 선언된 순서로 호출됨)로 멤버들이 초기화 되므로, 초기화 목록을 만들 때에는 멤버가 선언된 순서로 작성해야 한다.

5. 소멸자의 호출 순서
각 멤버의 소멸자는 소멸자가 수행 된 후, 생성자 호출 순서와 반대로 호출된다.

6. 멤버 초기화 리스트가 필요한 경우
기본 생성자가 없는 클래스가 멤버로 들어온 경우
상수 멤버
참조자 멤버

상수 멤버와 참조자 멤버는  대입 자체가 불가능 하므로 꼭 초기화 목록에 기록 해야 한다.

Scott Meyers도 그의 책에서 이야기 하고 있지만, 되도록 정책적으로 클래스 데이터 멤버를 모두 초기화 리스트에 올리는 습관을 들이는게 좋다는 생각이다. 원래 습관을 들이면, 그렇게 안하면 이상해지는 법이고, 잊어버리거나 실수하지 않게 되니까 말이다.


참고자료
Bjarne Stroustrup, The C++ Programming Language, (주)피어슨에듀케이션코리아, pp.349-pp.350
Scott Meyers, Effective C++, Addison-Wesley, pp.26-pp.33, Item 4
 
by 워니 | 2006/11/21 01:27 | C++ | 트랙백(1) | 덧글(2)
11월 18일(토) 제2회 구세군자선냄비 마라톤대회
11월 18일 토요일, 서선임님과 함께 '제2회 구세군자선냄비 마라톤대회'에 참가했다. 개인적으로 올해 세번째 참가하는 10Km 마라톤 대회였고, 1시간 안에 들어와서 기분이 좋았다. 또한 나와의 내기로 인해 마라톤 첫 참가임에도 열심히 달리시고, 기분 좋아하신 서선임을 보니 다음번에도 함께 참가하면 좋겠다 싶었다. 서선임님 역시 봄이 오면 다른 팀원들도 함께 참가하면 좋겠다고 하시니, 내년 봄에는 좀더 많은 인원이 참가해서 같이 뛰면 좋을 것 같다. 박전임을 좀더 꼬셔야 하는데.. 히히 아. 글구 내기는 내가 이겼다. 그래서 서선임님이 사우나와 식사를 내주셨다. ^^

                                                        [10Km 완주후 서선임님과 함께]
여의도 63빌딩 앞 수변마당에서 시작한 코스는 한강변이 바로 옆이라 달릴 때에도 기분이 좋았다.
                                                              [마라톤 코스]
이번 대회 기록은 그렇게나 돌파하고 싶었던 1시간 벽을 돌파!!.
                                                       [워니 기록]
내년에는 운동 좀 열심히 해서 하프에 출전해보고 싶다. ^^

올해 워니의 마라톤 기록 정리
2006년 10월 1일 제4회 하이서울 마라톤
2006년 5월 1일 제1회 노동절 마라톤
중한이와 함께 참가한 내 첫 마라톤 대회

by 워니 | 2006/11/20 01:17 | Daily life | 트랙백 | 덧글(2)
The most Important C++ ... Ever
다음의 글들은 Artima developer(The C++ Source)에 기고된 Scott Meyers의 글들인데 C++을 공부하시는 분들이 한번쯤 읽어보시면 좋을 것 같단 생각입니다.

[1] The Most Important C++ Books...Ever
[2] The Most Important C++ Non-Book Publications...Ever
[3] The Most Important C++ Software...Ever
[4] The Most Important C++ People...Ever
[5] My Most Important C++ Aha! Moments...Ever


출처 : http://www.artima.com/cppsource/
by 워니 | 2006/11/17 16:27 | Articles | 트랙백 | 덧글(0)
const 참조를 전달인자로 사용
객체를 전달인자로 하는 함수 선언 시, 매개변수(Prameter)를 const 참조로 선언하면 인자(Argument)의 값을 수정하지 않는다는 것을 보증한다. 또한가지 장점으로는 인자의 복사본이 아닌 원본을 가지고 처리를 하게 되므로, 복사 생성자 호출로 인해 발생하는 생성자, 소멸자 호출과 그 객체를 저장하는 임시 공간을 절약 할 수 있어 비용이 감소된다.
다음은 왜 const 참조로 객체를 전달해야 하는지에 대한 설명이다.

1. 객체를 전달인자를 사용(Pass-by-Value)
값에 의한 전달 방식은 원본 인자를 사용하지 않고, 임시 복사본을 만들어 사용 한 후, 함수 처리가 끝나면 사라진다.
객체를 값에 의해 전달하게 되면 임시 복사본이 생성되므로 복사 생성자를 호출한 후, 처리 후 소멸자를 호출한다.

                                             [그림 1] Pass-by-Value (C++ 기초 플러스 참조)


2. 참조를 전달인자로 사용(Pass-by-reference)
실제 데이터 원본을 갖고 작업하게 되므로, 복사 생성자 호출로 인한 비용이 없다.
데이터 원본을 갖고 작업하므로 함수 내부에서 값이 변경될 수 있다.
                                          [그림 2] Pass-by-reference (C++ 기초 플러스 참조)

3. const 참조를 전달인자로 사용(Pass-by-reference-to-const)
const를 사용하므로써, 실수로 함수 내부에서 값이 변경되는 프로그래밍 에러를 막을 수 있다.
실제 데이터 원본을 사용하므로 복사 생성자 호출로 인한 비용이 발생하지 않는다.
const와 const가 아닌 전달인자를 함께 처리할 수 있다.
다른 사람이 코드를 읽을 때, 파라미터의 역할을 명확히 알 수 있다.

개인적인으로 사소해 보여 넘겨버린 처음 코딩 시의 습관이 오래 간다고 보기 때문에 각각의 특징들을 익혀 습관화 하는 것이 중요하다는 생각이다. 아무리 인자를 값에 의해 전달하는 비용이 작다고 하여 지나쳐 버리게 되면, 알게 모르게 그것이 습관으로 남을 수 있기에 1의 경우에는 가급적 3번을 통해 작업을 처리 하도록 하여 비용을 아끼는 것이 좋다고 본다. 또한 2의 경우에는 함수 안에서 값이 변경될 수 있으므로 함수 이름에 이를 명확히 나타내 주어 다른 사람이 코드를 읽을 때, 이를 인지하도록 하는 것이 필요하다고 본다.

※ 참고 자료
Stephen Prata, C++ 기초 플러스, 성안당, pp.382-383
Scott Meyers, Effective C++, Addison-Wesley, pp.86-pp.90, Item 20
by 워니 | 2006/11/15 10:04 | C++ | 트랙백 | 덧글(0)
list의 모든 데이터를 다른 list로 복사
stl의 list는 시퀀스의 중간에 빈번한 삽입, 삭제가 수행될 때 사용하는 시퀀스 컨테이너이다. 이러한 특징에 맞춰 list를 사용하곤 하는데, 종종 저장된 list의 원소들을 복사해서 사용할 필요가 있을 때가 있다. list의 모든 원소를 복사하는 방법에는 여러가지가 있지만, 다음은 assign 멤버 함수와 알고리즘 함수 copy를 사용하여 복사하는 방법에 대한 설명이다. 결론부터 이야기하자면, 단일 원소 버전인 copy 보다는 범위 버전인 assign 함수가 효율성에서 더 좋으므로 사용이 권고되고 있다.

1. assign 멤버 함수를 사용하는 방법

#include <list>

typedef std::list<int> LIST_ID;

LIST_ID lstSrcID;       // 소스 list
LIST_ID lstDestID;     // 복사할 목적지 list

// lstSrcID는 1~4의 값을 갖는다.
lstSrcID.push_back(1);
lstSrcID.push_back(2);
lstSrcID.push_back(3);
lstSrcID.push_back(4);


//위의 lstSrcID의 내용을 assign 멤버 함수를 사용하여 lstDestID로 복사
lstDestID.assign(lstSrcID.begin(), lstSrcID.end());


// lstDestID의 내용 출력 및 삭제
while( !lstDestID.empty() )
{
    TRACE("%d\n", lstDestID.front());
    lstDestID.erase(lstDestID.begin());
}

2. 알고리즘의 copy 함수를 사용하는 방법

list에 있는 원소를 복사하는 다른 방법으로는 알고리즘 함수 copy 를 이용하는 것이다. copy 에 사용되는 back_inserter 함수는 컨테이너의 끝에 원소를 하나씩 추가하는 기능을 갖고 있는데, 이는 copy 함수 내부에서 루프 처리를 하고 있다는 것을 말한다.

#include <list>
#include <algorithm>
...

// lstSrcID 에 원소 추가

//위의 lstSrcID의 내용을 알고리즘 함수 copy를 사용하여 lstDestID로 복사
std::copy(lstSrcID.begin(), lstSrcID.end(),
std::back_inserter(lstDestID));


// lstDestID의 내용 출력 및 삭제
while( !lstDestID.empty() )
{   
    TRACE("%d\n", lstDestID.front());
    lstDestID.erase(lstDestID.begin());
}

다음은 일반적인 copy 함수의 구현입니다.

template <typename InIt, typename OutIt>
OutIt copy(InIt first, InIt last, OutIt result)
{
    for( ; first != last; ++first, ++result )
        *result = *first;

    return restult;
}

위의 1과 2를 통해 list 원소들을 복사하는 방법에 대해 알아봤다. 물론 또다른 방법으로는 직접 루프를 작성하거나 list의 insert 멤버 함수를 사용하는 방법이 있을 것이다. 하지만 효용성의 측면에서 assign 멤버 함수를 사용하여, 원소를 복사하는 것이 좋다고 하는데, 이는 이중 링크트 리스트인 list의 next, prev 포인터가 계속되는 원소 추가에 대해 불필요한 포인터 세팅을 하지 않도록 하고, 한번의 대입 만으로 각 포인터가 삽입 후의 노드 주소 값을 가지도록 세팅해주기 때문이다.

3. stl 사용시 유의할 점

stl 사용시 잘 확인해야 할 점은 'Effective STL'에 설명된 것 처럼 다음과 같다.
단일 요소를 단위로 동작하는 멤버 함수보다 요소의 범위를 단위로 동작하는 멤버 함수가 더 낫다 (항목 5, pp.57)
어설프게 손으로 작성한 루프보다 알고리즘이 낫다 (항목 43, pp.272)
같은 이름을 가진 것이 있다면 일반 알고리즘 함수보다 멤버 함수가 더 낫다 (항목 44, pp.282)


※ 참고자료
Scott Meyers, Effective STL, 인포북, pp.57, pp.272, pp.282
Alexandrescu Andrei, Modern C++ Design, 인포북, pp.90, copy 함수
 
by 워니 | 2006/11/13 23:00 | C++ | 트랙백 | 덧글(0)
인라인(inline) 함수
다음의 글은 C++ 책에 나오는 인라인 함수 대한 글을 요약한 자료로 일반 함수와 인라인 함수의 차이점, 특징, 사용법을 이야기 한다.

1. 일반 함수의 수행
 프로그램이 함수 호출 명령에 도달하면, 그 프로그램은 다음의 ①-⑥의 흐름을 갖는다.

① 함수 호출 명령 다음의 명령 주소를 메모리에 저장
② 스택에 전달인자를 복사
③ 해당 함수의 시작의 메모리 위치로 점프
④ 함수 수행
⑤ 함수 리턴 값을 레지스터에 저장
⑥ ①에서 저장해둔 주소의 명령으로 복귀


2. 인라인 함수
 인라인 함수는 프로그램의 코드들 가운데 컴파일된 함수 코드가 삽입된다. 이는 컴파일러에 의해 해당 인라인 함수가 함수 코드로 대체됨을 뜻한다. 이렇듯 인라인 함수를 사용하면, 프로그램은 해당 코드를 수행하기 위해 위의 일반 함수 수행처럼 메모리에 있는 함수의 주소를 찾아 점프할 필요가 없어지게 되어, 일반 함수보다 약간이나마 빠른 수행 속도를 갖을 수 있다. 그러나 만약 크기가 큰 코드를 가진 함수를 인라인 함수로 사용하고, 10번을 호출게 된다면, 해당 프로그램 코드 사이에 10개의 복사본을 갖게 됨으로써, 메모리 사용면에서 좋지 않을 수 있다. 또한 함수 수행의 속도에서도 일반 함수 크기가 작다면, 함수 수행을 위해 점프하고, 돌아오는 시간이 적으므로 속도로 얻을 수 있는 이익이 작을 수 있다. 그러므로 인라인 함수 사용 시에는 다음과 같은 특징을 꼭 기억하고 있어야 한다.

짧은 함수를 인라인 함수로 사용해야 한다.
함수 호출 비용을 절약한다.
짧은 인라인 함수는 함수 호출문에 대해 만들어지는 코드보다 목적 코드가 작아질 수 있다.
 캐시 적중율이 높아진다.

코드 길이가 긴 인라인 함수는 사용하지 말자!
인라인 함수를 남용하게 되면, 컴파일 시에 코드 대체로 인한 목적 코드의 크기가 증가할 수 있다.

③ 가상 함수를 인라인 함수로 만들지 말자!
가상 함수는 컴파일 시가 아닌, 프로그램 수행시 결정되므로 컴파일 시에 코드 대체되는 인라인 함수로는 사용될 수 없다.


3. 인라인과 매크로
간단한 처리를 위해 사용되어온 C의 잔재인 매크로들은 문제점을 안고 있으므로 인라인 함수로 고쳐 사용한다.
다음은 숫자를 제곱하는 매크로 함수이다.

#define SQUARE(X) (X*X)

위의 매크로 함수는 X를 전달인자로 보지 않고, 전달인자에 대한 심벌 레이블처럼 행동하게 하여 문자 대치에 의해 작동되어 다음의 문제점을 유발시킨다.

a = SQUARE(4.5+7.5);
 SQUARE 매크로가 호출되게 되면 다음과 같이 실행된다.
 4.5 + 7.5 * 7.5 + 4.5
 이는 연산자 우선순위에 의해(*은 + 보다 우선순위가 높다) 전혀 다른 결과를 얻게 된다.

b = SQUARE(a++);
 a++ * a++
 SQUARE 매크로가 호출되어 단순 문자 대치로 작동되어 위와 같은 결과를 초래한다. 그러므로 가급적 위와 같은 매크로는 사용을 지양하고, 다음과 같이 인라인 함수를 사용하도록 한다.

// Calculator.h
class CCalculator
{
...

public:
    double CalcSquare(const double& dblVaule);
};

inline double CCalculator::CalcSquare(const double& dblVaule)
{
    return (dblVaule*dblVaule);
}

[예제 1] 명시적 inline 함수 (개인적으로 선호)


4. 인라인 사용 예
(1) 정의

inline 한정자를 사용하여 함수를 선언한다.
inline 함수의 정의는 그것을 호출하는 어떠한 함수보다도 앞에 있어야 한다.

(2) 구현
위의 CCalcuator 클래스 처럼 inline 한정자를 주어 명시적으로 인라인 함수를 구현할 수 있다. 그러나 암시적으로 다음과 같이 설정해도 컴파일러는 이를 인라인 함수로 처리한다.

// Calculator.h
class CCalculator
{
...

public:
    double CalcSquare(const double& dblVaule) { return (dblVaule*dblVaule); }
};

[예제 2] 암시적 inline 함수


(3) 사용
위의 [예제 1]과 [예제 2] 방법 중에서 개인적인 취향에 따라 구현 방법이 다를 것이라 생각된다. 하지만 개인적인으로는 [예제 1]의 방법을 사용하는데, 이는 선언부에는 선언만!하는게 좋다고 생각하고, 코드가 깔끔해 보이는 이유 때문이다.


참고 자료
1. Stephen Prata, C++  기초 플러스, 성안당, pp.366-369
2. Scott Meyers, Effective C++, Addison-Wesley, Item 30 pp.134-139
by 워니 | 2006/11/12 21:42 | C++ | 트랙백(40) | 덧글(0)
치열함




























 파일을 정리하다 사진을 하나 발견했다.
지난 5월 제 1회 노동절 마라톤 때 찍힌 사진인데, 내가 뛰었다라는 것이 중요하다 생각했기 때문에 사진에 대해서는 별 생각이 없었다. 그런데 문득 다시 보니, 나 보다도 내 뒤의 아저씨 얼굴이 너무나 치열한(?) 모습을 하고 있었다.
(6506번 아저씨, 함부로 사진을 올려서 죄송합니다.)

문득 '아! 이리도 치열하게 살아야 하는구나! 열심히 뛰어야 하는구나' 라는 생각이 들었다.
열심히 살자!!
by 워니 | 2006/11/09 16:17 | Daily life | 트랙백 | 덧글(0)
< 이전페이지 다음페이지 >
rss

skin by 이글루스