[OpenMP] 이해가 않되는 스레드 흐름

#include "stdafx.h"
#include 
#include 

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 1;
#pragma omp parallel
    {
#pragma omp sections firstprivate(a) lastprivate(a)
        {
#pragma omp section
            {
                Sleep(1000);
                printf("section 1: a 초기값 = %d\n", a);
                a = 2;
                printf("section 1: a 수정값 = %d\n", a);
            }
#pragma omp section
            {
                printf("section 2: a 초기값 = %d\n", a);
                a = 3;
                printf("section 2: a 수정값 = %d\n", a);
            }
        }
    }

    printf("a 최종값 = %d\n", a);

    return 0;
}

이 코드는 스레드를 2개 사용하고 있습니다. 즉 section 지시어를 통해 13~18번 코드를 실행하는 스레드 하나와 20~24번 코드를 실행하는 스레드 하나입니다. 10번 코드에서 lastprivate(a)라는 보조 지시어를 통해 2개의 스레드에서 연산한 결과 a가 복사됩니다. 14번 코드에 Sleep 함수를 호출함으로써 두개의 스레드 중 a에 2를 할당한 스레드가 가장 마지막에 끝나도록 했습니다. a에 2를 할당한 스레드가 마지막에 종료되니.. 이 2개의 스레드가 끝나면 a 값은 항상 2가 될거라 예상됩니다.

사용자 삽입 이미지
근데.. 보는 바와 같이 a의 최종값이 3이라고 합니다.. ㅡOㅡ;; 실행 흐름을 봐도 가장 먼저 a 수정값으로 3을 할당하고 다음 실행으로 a 수정값으로 2를 할당하는 순서입니다. 실행 순서는 예상과 같은데.. 결과는 반대입니다.. ㅡOㅡ;; 아직 OpenMP를 학습하는 단계인지라.. 이해가 떨어져서 그런 것인지.. 아니면 OpenMP의 BUG인지… 모를 일입니다.. 누구 아시는 분 코칭 부탁드립니다.

[C++] STL 함수 객체(functor)

STL에서 제공하는 함수 중 for_each라는 녀석이 있습니다. 컨테이너에 담긴 데이터 하나하나에 대해 처리해야할 연산을 지정해 주는 함수인데.. 이 for_each는 세개의 인자를 갖습니다. 첫번째 인자와 두번째 인자는 컨테이너에 저장된 데이터의 범위이고 세번째가 데이터 하나하나에 대해 수행될 연산인데.. 함수나 함수 객체를 인자로 받습니다. 예를 들어 list 컨테이너에 다음과 같은 데이터를 담아 보는 것으로 시작해 보겠습니다.

 list values;

 values.push_back(100);
 values.push_back(200);
 values.push_back(150);
 values.push_back(250);

총 4개의 데이터가 저장된 상태인데.. 이 4개의 데이터 각각에 값 10을 더해 주는 연산을 for_each 함수를 이용해 만들어 보면…

void add10(int& elem)
{
    elem += 10;
}

int main()
{
    ....

    for_each(values.begin(), values.end(), add10);
}

여기서 한발짝 더 나아가.. 재활용을 고려해 10이라는 값의 증가가 아닌 그때 그때 마다 원하는 값만큼 증가시키고자할때.. 함수 객체를 사용하면 매우 유연하게 활용할수있습니다.

class Add {
private:
    int _v;

public:
    Add(int v):_v(v) {}
    void operator() (int &elem) const {
        elem += _v;
    }
};

int main()
{
    ....

    for_each(values.begin(), values.end(), Add(10));
}

즉, 어떤 함수를 만들어 놓고.. 증가하고자 하는 값을 클래스에 대한 필드로 가지도록 하고 () 연산자의 기능을 재정의해줌으로써 클래스 객체를 마치 함수처럼 사용할수있도록 했습니다.

끝으로 함수 객체말고도.. 가장 처음 언급한 함수를 템플릿으로 정의함으로써 원하는 값만큼 증가시키는 기능의 구현도 가능한데.. 아래와 같습니다.

template void add(int &elem)
{
    elem += v;
}

int main()
{
    ....

    for_each(values.begin(), values.end(), add);
}

STL.. 기본만 알아도 무척 편리한데.. 좀더 알면 알수록.. 재미있습니다. 손맛이라고 해야 하나.. C++ 참.. 손맛 나는 언어입니다..

[C++] STL 알고리즘 기본

STL을 사용함에 있어서 STL을 자료구조로써만 바라봤고 자료구조로써만 사용했습니다. 견고하면서 빠르며 융통성이 뛰어난 자료구조로써 말입니다. 이 자료구조에 대한 연산, 즉 알고리즘에 대해서는 간과했고.. 필요한 연산은 STL에서 제공해주고 있다는 것을 알고 있음에도 그냥 제 스스로 코드를 만들 써 왔습니다.

예를 들어서 자료구조로써 링크드 리스트(Linked List)는 STL에서 list 입니다. 자료구조로써 아래와 같이.. 저에겐 매우 익숙한 코드로 데이터를 저정합니다.

#include 
#include 

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    list values;
 
    values.push_back(100);
    values.push_back(200);
    values.push_back(150);
    values.push_back(250);

    ... // values의 사용

    return 0;
}

링크드 리스트라는 자료구조가 필요했고.. 매우 효율적이면서도 매우 견고한 링크드 리스트의 구현체로써 STL을 사용해 데이터를 저장합니다. 여기서 저장된 데이터 중 가장 큰 값이 필요할 경우 저는 의례 다음처럼 코딩을 해왔습니다.

list::iterator it;
double minValue = DBL_MAX;
for(it=values.begin(); it!=values.end(); ++it) {
    if(*it < minValue) minValue = *it;
}

printf("%lf\n", minValue);

실제로.. 이렇게 사용해 왔는데요. 아마도 STL을 저보다 더 잘 사용하시는, 대부분 그러하시겠지만.. 정말 이렇게 코딩을 했다고? 하며 실소를 금치 못하실거라 생각됩니다. 네.. 반성중입니다. 다시 초심으로 돌아가서.. 이러한 무지를 버리고 STL에서 제공하는 본연의 기본 알고리즘을 활용하려기에 이 글을 작성해 봅니다.

그렇다면 위의 최소값을 얻기 위한 STL의 방식은 무엇일까요? 아래와 같이 최적화되었으며 빠르며 명확한 방법을 제공합니다.

    list::iterator it;

    it = min_element(values.begin(), values.end());
    printf("%lf\n", *it);

최소값이 아닌 최대값을 구하는 함수는 max_element입니다.

이외에도 어떤 값을 갖는지를 판단하는 함수의 경우는 다음 코드와 같습니다.

    list::iterator it;

    it = find(values.begin(), values.end(), 200);
    if(it != values.end())
    {
        printf("Got it!");
    }

STL에서는 이처럼 매우 효율적인 방식의 알고리즘 함수를 많이 제공하지만 끝으로 정렬 함수를 소개하면서 마무리 하고자 합니다.

sort(values.begin(), values.end());

이제.. 이렇게 블로그를 통해 정리를 해 놓았으니 향후 STL을 이용해 자료구조로써 뿐만 아니라 알고리즘 연산에 대해서도 최대한 STL을 사용해 봐야겠습니다.

VC++로 개발된 32비트 어플리케이션을 64비트로 포팅하기

마이크로 소프트 운영체제에서 32비트 어플리케이션을 64비트 운영체제로 포팅하는 절차는 16비트 어플리케이션을 32비트 운영체제로 포팅하는 것에 비해 다소 간단합니다.그러나 포팅 절차는 다소 신중하게 계획하여 좀더 부드럽게 진행하는 것이 좋습니다. 다음은 일반적인 가이드 라인에 대한 제시입니다. (출처: http://msdn.microsoft.com/en-us/library/aa384190.aspx)

계획

포팅하는데 요구되는 노력의 양을 가늠해 봐야 합니다. 다음 항목을 확인해 보면서 얼만큼의 작업이 필요한지 측정해 볼 수 있습니다.

  • 32비트 코드 문제: 32비트 코드를 64비트 컴파일러로 컴파일하여 에러와 경고를 살펴보고 그 범위를 살펴봅니다.
  • 공유되는 구성 컴포넌트와 종속성: 어플리케이션에서 사용하는 컴포넌트가 다른 개발회사로부터 제작되었는지와 이 회사에서 해당 컴포넌트를 64비트로 개발할 계획을 가지고 있는지 문의해 봅니다.
  • 레거시 또는 어셈블리 코드: 16비트 윈도우즈에 기반한 어플리케이션은 64비트 윈도우즈에서는 기동되지 않으며 반드시 코드를 다시 작성해야 합니다. x86 어셈블리 코드는 WOW64에서 실행되지만 코드를 다시 작성함으로써 인텔 Itanium 아키텍쳐의 향상된 속도을 얻을 수 있습니다.
  • 일부가 아닌 전체 어플리케이션의 포팅하기: 비록 어플리케이션의 일부분을 포팅하거나 /LARGEADDRESSWARE:NO 옵션을 사용하여 2G까지 코드를 제한함으로써 문제를 간단히 해결할 수 있지만, 전체 어플리케이션을 포팅하는 방법이 향후에 지속적으로 수반되는 문제점을 근본적으로 해결할 수 있습니다.
  • 포팅될 수 없는 기술에 대해 다른 대체할 수 있는 기술 찾기: DAO(Data Access Object)와 Jet Red 데이터베이스 엔진 등과 같은 몇몇 기술은 64비트 윈도우즈로 포팅할 수 없습니다. (2010년 4월 기준)
  • 64비트용과 32비트용으로 구분된 어플리케이션인지 파악하기: 비록 64비트 제품과 32비트 제품이 많은 부분에서 동일한 코드를 공유한다고 할지라도, 64비트와 32비트로 구분되어 배포된 경우에 대해서는 각기 서로 다른 부가적인 시험과 고려 사항이 필요할 수 있습니다.

개발

  • 호환되는 코드 작성: 개발자는 최신의 윈도우즈 헤더 파일과 32비트 뿐 아니라 64비트에서도 공통으로 사용되는 데이터 타입을 사용하여 호환되는 코드 작성합니다(http://msdn.microsoft.com/en-us/library/aa384198.aspx).
  • 코드를 32비트와 64비트 윈도우즈 모두에서 컴파일될 수 있는지 확인: 새로운 데이터모델은 거의 수정하지 않고도 하나의 코드로 32비트와 64비트 모두에서 실행될 수 있도록 설계되었습니다. SQL 서버와 윈도우즈 개발 팀은 제품을 동일한 코드 기반으로 32비트와 64비트 버전을 개발하고 있으며 이런 개발이 가장 이상적입니다.
  • 최상의 퍼포먼스를 위한 컴파일러의 새로운 최적화 기능 사용: 인텔 Itanium 프로세서에 대한 코드 최적화는 과거 x86에서보다 훨씬 더 중요합니다. 컴파일러는 마이크로 프로세서에 의해 이전에 처리되는 많은 최적화 기능이 제공된다고 가정합니다. 64 비트 어플리케이션의 퍼포먼스를 최대화 하기 위해 컴파일러의 2가지 최신 최적화 기능을 제공합니다(최적화에 대한 프로파일과 전체 프로그램 최적화). 이 두 기능으로 인해 빌드 시간이 더 길어지고 초기 개발에서 테스트 시나리오를 고려할 필요가 있습니다. 최적화를 위한 프로파일은 2단계의 컴파일 절차를 거치게 됩니다. 첫번째 컴파일 과정에서는 코드가 실행 동작을 잡아내기 위해 실행됩니다. 여기서 얻어진 정보는 두번째 컴파일 과정에서 사용되며 모든 최적화 기능에 대한 가이드 라인이 됩니다. 전체 프로그램 최적화는 어플리케이션의 전체 파일에 대한 코드를 분석합니다. 더 효율적인 호출방식 및 개선된 코드로의 변환 뿐만 아니라 더 나은 인라이닝 등과 같은 다양한 방법으로 퍼포먼스를 향상시키게 됩니다.

테스트

  • WOW64에서 실행되는 64비트 또는 32비트에서 코드를 테스트할지를 결정하기: 어떤 어플리케이션은 WOW64에서 실행되는 네이티브 64비트 코드와 32비트 코드 모두를 포함합니다. 테스트 계획을 수립하는 동안 이를 면밀하게 조사하기 바랍니다. 그리고 테스트 도구가 64비트인지 32비트 아니면 이 둘의 조합인지 결정해야 합니다. 64비트 윈도우즈 상에서는 어플리케이션의 64비트와 32비트 모두를 필요로 할 수 있습니다.
  • 사용된 32비트 컴포넌트를 자주 테스트하기: 먼저 64비트로 코드를 컴파일하고 테스트합니다. 제시된 문제를 고치고 32비트에서 다시 컴파일하고 또 테스트합니다. 문제가 있다면 수정합니다. 세번째로 다시 64비트로 컴파일하고 테스트합니다. 이런 절차를 문제가 없어질때까지 반복합니다.
  • COM와 RPC 컴포넌트 테스트: 32비트와 64비트 COM과 RPC 컴포넌트가 정확하게 통신하는지 확인합니다. 간혹 네트워크를 통해 16비트 컴포넌트와의 통신을 테스트해 볼 필요가 있습니다.
  • 메모리 구성이 다른 구성에서 테스트: 서버에 추가적인 메모리를 장착하면 이전에는 발생하지 않은 문제가 어플리케이션이나 운영체제에서 발생할 수 있습니다.

참고할 글: http://www.gamedev.net/reference/programming/features/20issues64bit/