프로그래밍/Parallel Programming

병렬 프로그래밍 Parallel Programming - concurrent_vector

nanze 2021. 12. 30. 18:02
반응형

이전 정리글은

2021.12.28 - [프로그래밍/Parallel Programming] - 병렬 프로그래밍 Parallel Programming - parallel_sort

 

오늘은 7번째 정리시간이다. 오늘은 무엇에 대해서 정리해볼까.? 오늘은 PPL 에서 제공하는 컨테이너에 대해서 정리해보자. 먼저 알아볼 것은 vector 에 대해서 알아보자.

concurrency::concurrent_vector

우리가 알고 있는 STL vector 와 동일한 것이다. 하지만 차이가 있다.

병행 프로그래밍에서 사용할 수 있는 컨테이너이기에 제공하는 몇개의 함수에 대해서 thread-safe 를 보장한다. 그럼 어떤 함수들이 thread-safe 한지 알아보자. 

함수 thread-safe 함수 thread-safe 함수 thread-safe 함수 thread-safe
resize FALSE assign FALSE reserve FALSE clear FALSE
operator= FALSE empty TRUE size TRUE rend TRUE
max_size TRUE grow_to_at_least TRUE grow_by TRUE rbegin TRUE
at TRUE push_back TRUE back TRUE end TRUE
front TRUE operator[] TRUE capacity TRUE    

위의 표를 보면 thread-safe 한 함수를 알 수 있다. 랜덤 액세스 후 대입 연산이 안되는 것은 아쉽긴 하다.

또 STL vector 와 다른 점이 있는데 원소 추가 작업 시 move 가 적용되지 않는 것이다. 그리고 원소를 연속된 공간에 저장하지 않으므로 메모리 번지 후 가감 연산을 통해 접근 할 수 없다. 

자 코드를 보자.

#include <ppl.h>
#include <concurrent_vector.h>
#include <iostream>

using namespace std;

int _tmain()
{
    concurrency::concurrent_vector<int> vecConcurrent;
    
    concurrency::parallel_invoke(
        [&]{
            for(int i=0;i<100;i++)
            {
                vecConcurrent.push_back(i);
            }
        },
        [&]{
            for(int i=100;i<200;i++){
                vecConcurrent.push_back(i);
            }
        }
    );
    
    for(int i=0;i<200;i++){
        wcout << L"index[" << i << L"] : " << vecConcurrent.at(i) << endl;
    }
    
    return 0;
}

위 코드를 실행해보면 vector 에 값이 잘 들어가지는 것을 확인할 수 있다.

 

concurrency::concurrent_queue

역시 vector 와 마찬가지로 STL queue 와 같은 것이다. 차이점도 thread-safe 한 함수 제공과 특성이 조금 다르다는 것이다.

thread-safe 한 함수는 다음과 같다.

함수 thread-safe 함수 thread-safe 함수 thread-safe 함수 thread-safe
operator-> FALSE operator++ FALSE operator* FALSE rend FALSE
unsafe_begin FALSE unsafe_end FALSE clear FALSE empty TRUE
push TRUE try_pop TRUE        

concurrent_queue 는 iterator 를 제공하지만 thread-safe 하지는 않다.  코드를 보자 다음 코드는 오브젝트 풀의 push를 발췌한 것이다. 

template<typename T>
class NzOPool {
public:
	....
    
    T* Pop() {
        T* pRet = nullptr;
        if (m_queObjects.try_pop(pRet) == false) {
            Trace(_T("obj pool try pop error new return..\n"));
            pRet = new T();
            ++m_nMaxSize;
        }
        return pRet;
    }  
    
    void Push(T* pObj) {
	    m_queObjects.push(pObj);
    }
    
private:
    concurrency::concurrent_queue<T*> m_queObjects;    
};

다음은 병렬 객체에 대해서 정리해보자. 병렬 객체의 쓰임새는 여러 스레드에서 객체를 공유하고자 할때 유용하다. 

concurrency::combinable

역시 코드를 보자.!!!!

#include <ppl.h>
#include <array>
#include <numeric>
#include <iostream>

using namespace std;

bool isPrime(int value)
{
    if( value < 2) return false;
    for(int i=2;i < value; i++){
        if( (value % i) == 0) return false;
    }
    return true;
}

int main(){
    int totalSum = 0;
    array<int, 100000> nums;
    concurrency::combinable<int> combine;
    
    iota(begin(nums), end(nums), 0);
    concurrency::parallel_for_each(begin(nums), end(nums), [&](int i){
            combine.local() += (isPrime(i) ? i : 0 );
        }
    );
    
    totalSum = combine.combine(plus<int>());
    /*
    아래와 같이도 가능
        totalSum = 0;
        combine.combine_each(
            [&](int n){
                totalSum += n;
            }
        );
    */
    
    wcout << L"Total value : " << totalSum << endl;
    
    return 0;
}

위 코드를 보게되면 combinable local() 함수를 사용하고 있는데 해당 함수는 스레드별로 독립적인 객체를 반환한다. 스레드별 독립된 변수에 각 스레드 별 값을 더하고 parallel_for_each 함수가 완료되면 combinable 의 combine 함수를 통하여 각각의 값에 대한 연산을 처리할 수 있다. 해당 객체를 활용하면 추가적으로 우리가 동기화 객체를 사용하지 않아도 스레드간 공유할 수 있는 객체를 생성할 수 있다. 

막간으로 기본 타입이 아닌 클래스를 사용할 경우도 한번 보자.

    concurrency::combinable<Base::BoundBox3d> bbs;
   
    concurrency::parallel_for_each(_Points.begin(), _Points.end(), 
        [this, &bbs](const value_type& value) {
            Base::Vector3d vertd(value.x, value.y, value.z);
            bbs.local().Add(this->_Mtrx * vertd);
        }
    );
    
    bbs.combine_each([&bnd](const Base::BoundBox3d& lbb) {
        bnd.Add(lbb);
    });

짜잔~! 클래스를 사용할 때도 크게 다르지 않다. 범위가 크고 복잡한 연산을 대상으로 할 때 동기화 객체를 따로 작성하여 수행하는 것보다 성능이 좋다고 한다.

 

다음 정리글은

2022.01.03 - [프로그래밍/Parallel Programming] - 병렬 프로그래밍 Parallel Programming - concurrent_unordered_map

반응형