Microsoft PPL 병렬컨테이너
nhnnext 김현우.
비동기 방식의 필요성.
• 멀티 스레드 프로그래밍은 기본(코어가 여러 개니까.)
• 하나의 프로그램이 이제 네트워크로 연결된 별도의 프로세서에
서 동작하는 경우도 너무 많다(쓸 만한 서비스는 거의 대부분).
• 락을 이용한 동기화 방식으로 멀티 스레드 프로그램을 만든다
는 건, 결국 어느 시점에서는 다른 스레드를 기다리기 위해 블럭
상태에 있어야 한다는 거다.
• 결국 성능향상을 위해서 멀티 스레드 프로그램을 만들었는데
그 노력이 허사가 돼 버린다.
• 결국 비동기 방식의 멀티 스레드 프로그램을 만들어야 한다.
Appendix : std::future,std::promise
• 기존에 락을 이용한 동기화 방식으로 하던 멀티 스레드 프로그
램이 아닌 테스크 방식(?) 멀티 스레드 프로그램을 위한 라이브
러리
Appendix : std::future,std::promise(2)
• std::promis<dataType> promis1; //promis객체를 생성하고
• auto future1 = promis1.get_future();//promis1에 해당하는 future객체를 받고.
• std::thread th1(
[&promis1, inFile](){ promis1.set_value(readFile(inFile));}
);// 값을 생산하는 스레드에 promis1을 전달하고 값을 promis1에 저장한다
• size_t result = fut2.get();
//원래 스레드에서는 다른 작업을 하다가. 값이 필요할 때 future1객체를 이용해서 값을 받을 수 있다. 이때 블럭이 된다.
Appendix : std::async
• 하지만 위의 std::future와 std::promis는 사용하기 불편하다.
• 코드도 복잡하고.
• 그때 사용할 수 있는 것이 std::async
• 처음인자로 함수자나 람다를 넣고 그 다음 그 함수자나 람다의
인자를 순서대로 매개 변수에 넣어 주면 그 리턴 값의
std::future 객체를 반환한다.
• 스레드도 알아서 만들어 준다(많이 추상화 되어있음.)
Appendix : std::async(2)
auto fut1 = async(readFile, inFile); //함수자 와 매개변수
auto fut2 = async(
[&future1](const string& path){ return writeFile(future1.get(),
path);},//람다
outFile); //매개변수
예제 코드: 소수 구하기
• 소수를 구하는 함수들.
https://gist.github.com/LethalSun/d74cfe458196eb23518242c33
2fc6597
• 정수 범위가 주어 졌을 때 스레드 마다 범위를 나눠주는 함수.
https://gist.github.com/LethalSun/2131d3601f7431a4810b8defd
d3efb1e
• 실제 스레드를 만들어서 테스트 하는 코드.
https://gist.github.com/LethalSun/3b6452d4dd0514f9e8b4e7e0
e4d4c2e3
비동기 방식에서의 std::vector
• 멀티 스레드 간에 공유를 하는 경우
->이상하게 동작한다.
• 동기화 객체를 이용해서 락을 거는 경우
-> 잘 동작하지만 여러 개의 스레드를 사용한 것에 비해서 싱글
스레드로 하는 것과 차이가 없다…
• PPL 병렬 컨테이너를 사용했을 때.
->잘 동작한다. 동기화같은걸 고민하지 않았는데…
비동기 방식에서의 std::vector(2)
• 동기화 객체를 이용하지 않았을 때 문제가 발생한 이유.
• 각각의 스레드가 이터레이터를 이용해서 공유 컨테이너의 값을 읽고
있을 때 다른 스레드에서 컨테이너의 값을 삭제 하거나 이동하면 이터
레이터는 무효가 된다.
• 또 값을 추가 할 때 현재 컨테이너가 수용할 수 있는 개수를 초과 하면
새로운 공간에 가지고 있는 요소를 전부 복사하게 되는데 이때 그냥 동
일한 공간다음의 위치를 추가적으로 확보하는게 아니라 새로운 공간에
새로운 크기만큼 할당하게 된다(아마 물리적 공간에서의 연속성을 가
져야 하기 때문이 아닐까?)
• 즉 삭제 뿐만 아니라, 삽입도 문제가 된다…
Microsoft PPL(Parallel Patterns Library)
• 이러한 컨테이너의 문제점을 해결하기위해서 마이크로 소프트
에서 만든 PPL컨테이너가 존재함.
• concurrent_vector
• concurrent_que
• concurrent_unordered_map
• concurrent_unordered_multimap
• concurrent_unordered_set
• concurrent_unordered_multiset
• Linux 에서는 Intel TBB:Threading Building Blocks이 존재한다.
• 추가로concurrent_bounded_queue/concurrent_priority_queue도 지원
예)concurrency::concurrent_vector
• 포함 헤더:concurrent_vector.h
• 문법적 사용법은 단순함
concurrency::concurrent_vector<int>primes;
• 하지만 위에서 말한 벡터의 문제점을 해결하기 위한 설계때문
에 다른 성질을 가지므로 사용에 주의 해야 한다.
예)concurrency::concurrent_vector (cont.)
• 당연히 락을 사용하지 않고 동시에 접근해서 조작 가능하다.
• 끝에 요소를 넣는 push_back으로만 요소를 넣어야 한다.
• Push_back에 move시멘틱을 지원하지 않는다.
• erase, pop_back이 없고 전체를 지우는 clear는 동시실행을 지
원하지 않는다->요소의 삭제는 동시 실행이 불가능 하다.
• 각 요소는 메모리 상에 연속적으로 존재하지 않는다.
• 하지만 배열처럼 임의 접근은 가능하다.
• 요소를 추가하거나 벡터를 확장할 때 요소를 재배치 하지 않는
다.
예)concurrency::concurrent_vector(cont.)
• 벡터를 확장하는 grow_by, grow_at_least는 동시실행 가능하지
만 resize는 존재 하지 않는다.
• 런타임시에 Bool데이터 타입에 대한 특수화는 존재 하지 않는
다.
예)concurrency::concurrent_queue
• 포함 헤더:concurrent_ queue.h
• 문법적 사용법은 단순함 :
concurrency::concurrent_ queue <int>primes;
• Enqueue와 Dequeue가 동시실행 가능하다.
예)concurrency::concurrent_queue (cont.)
• push(Enqueue) 와 try_pop(Dequeue)은 동시 실행 가능하다.
• front 와 pop메소드는 없다.
• 이터레이터를 사용할 수 있지만 동시 실행 가능하지 않다.
• Back메소드는 존재하지 않고 끝 값을 참조할 수도 없다.
• size메소드 대신 동시 실행 가능하지 않은 unsafe_size메소드를
제공한다.
• clear와 empty로 concurrent_que를 비울 수 있지만 empty만 동
시실행 가능하다.

Microsoft pp lpdf

  • 1.
  • 2.
    비동기 방식의 필요성. •멀티 스레드 프로그래밍은 기본(코어가 여러 개니까.) • 하나의 프로그램이 이제 네트워크로 연결된 별도의 프로세서에 서 동작하는 경우도 너무 많다(쓸 만한 서비스는 거의 대부분). • 락을 이용한 동기화 방식으로 멀티 스레드 프로그램을 만든다 는 건, 결국 어느 시점에서는 다른 스레드를 기다리기 위해 블럭 상태에 있어야 한다는 거다. • 결국 성능향상을 위해서 멀티 스레드 프로그램을 만들었는데 그 노력이 허사가 돼 버린다. • 결국 비동기 방식의 멀티 스레드 프로그램을 만들어야 한다.
  • 3.
    Appendix : std::future,std::promise •기존에 락을 이용한 동기화 방식으로 하던 멀티 스레드 프로그 램이 아닌 테스크 방식(?) 멀티 스레드 프로그램을 위한 라이브 러리
  • 4.
    Appendix : std::future,std::promise(2) •std::promis<dataType> promis1; //promis객체를 생성하고 • auto future1 = promis1.get_future();//promis1에 해당하는 future객체를 받고. • std::thread th1( [&promis1, inFile](){ promis1.set_value(readFile(inFile));} );// 값을 생산하는 스레드에 promis1을 전달하고 값을 promis1에 저장한다 • size_t result = fut2.get(); //원래 스레드에서는 다른 작업을 하다가. 값이 필요할 때 future1객체를 이용해서 값을 받을 수 있다. 이때 블럭이 된다.
  • 5.
    Appendix : std::async •하지만 위의 std::future와 std::promis는 사용하기 불편하다. • 코드도 복잡하고. • 그때 사용할 수 있는 것이 std::async • 처음인자로 함수자나 람다를 넣고 그 다음 그 함수자나 람다의 인자를 순서대로 매개 변수에 넣어 주면 그 리턴 값의 std::future 객체를 반환한다. • 스레드도 알아서 만들어 준다(많이 추상화 되어있음.)
  • 6.
    Appendix : std::async(2) autofut1 = async(readFile, inFile); //함수자 와 매개변수 auto fut2 = async( [&future1](const string& path){ return writeFile(future1.get(), path);},//람다 outFile); //매개변수
  • 7.
    예제 코드: 소수구하기 • 소수를 구하는 함수들. https://gist.github.com/LethalSun/d74cfe458196eb23518242c33 2fc6597 • 정수 범위가 주어 졌을 때 스레드 마다 범위를 나눠주는 함수. https://gist.github.com/LethalSun/2131d3601f7431a4810b8defd d3efb1e • 실제 스레드를 만들어서 테스트 하는 코드. https://gist.github.com/LethalSun/3b6452d4dd0514f9e8b4e7e0 e4d4c2e3
  • 8.
    비동기 방식에서의 std::vector •멀티 스레드 간에 공유를 하는 경우 ->이상하게 동작한다. • 동기화 객체를 이용해서 락을 거는 경우 -> 잘 동작하지만 여러 개의 스레드를 사용한 것에 비해서 싱글 스레드로 하는 것과 차이가 없다… • PPL 병렬 컨테이너를 사용했을 때. ->잘 동작한다. 동기화같은걸 고민하지 않았는데…
  • 9.
    비동기 방식에서의 std::vector(2) •동기화 객체를 이용하지 않았을 때 문제가 발생한 이유. • 각각의 스레드가 이터레이터를 이용해서 공유 컨테이너의 값을 읽고 있을 때 다른 스레드에서 컨테이너의 값을 삭제 하거나 이동하면 이터 레이터는 무효가 된다. • 또 값을 추가 할 때 현재 컨테이너가 수용할 수 있는 개수를 초과 하면 새로운 공간에 가지고 있는 요소를 전부 복사하게 되는데 이때 그냥 동 일한 공간다음의 위치를 추가적으로 확보하는게 아니라 새로운 공간에 새로운 크기만큼 할당하게 된다(아마 물리적 공간에서의 연속성을 가 져야 하기 때문이 아닐까?) • 즉 삭제 뿐만 아니라, 삽입도 문제가 된다…
  • 10.
    Microsoft PPL(Parallel PatternsLibrary) • 이러한 컨테이너의 문제점을 해결하기위해서 마이크로 소프트 에서 만든 PPL컨테이너가 존재함. • concurrent_vector • concurrent_que • concurrent_unordered_map • concurrent_unordered_multimap • concurrent_unordered_set • concurrent_unordered_multiset • Linux 에서는 Intel TBB:Threading Building Blocks이 존재한다. • 추가로concurrent_bounded_queue/concurrent_priority_queue도 지원
  • 11.
    예)concurrency::concurrent_vector • 포함 헤더:concurrent_vector.h •문법적 사용법은 단순함 concurrency::concurrent_vector<int>primes; • 하지만 위에서 말한 벡터의 문제점을 해결하기 위한 설계때문 에 다른 성질을 가지므로 사용에 주의 해야 한다.
  • 12.
    예)concurrency::concurrent_vector (cont.) • 당연히락을 사용하지 않고 동시에 접근해서 조작 가능하다. • 끝에 요소를 넣는 push_back으로만 요소를 넣어야 한다. • Push_back에 move시멘틱을 지원하지 않는다. • erase, pop_back이 없고 전체를 지우는 clear는 동시실행을 지 원하지 않는다->요소의 삭제는 동시 실행이 불가능 하다. • 각 요소는 메모리 상에 연속적으로 존재하지 않는다. • 하지만 배열처럼 임의 접근은 가능하다. • 요소를 추가하거나 벡터를 확장할 때 요소를 재배치 하지 않는 다.
  • 13.
    예)concurrency::concurrent_vector(cont.) • 벡터를 확장하는grow_by, grow_at_least는 동시실행 가능하지 만 resize는 존재 하지 않는다. • 런타임시에 Bool데이터 타입에 대한 특수화는 존재 하지 않는 다.
  • 14.
    예)concurrency::concurrent_queue • 포함 헤더:concurrent_queue.h • 문법적 사용법은 단순함 : concurrency::concurrent_ queue <int>primes; • Enqueue와 Dequeue가 동시실행 가능하다.
  • 15.
    예)concurrency::concurrent_queue (cont.) • push(Enqueue)와 try_pop(Dequeue)은 동시 실행 가능하다. • front 와 pop메소드는 없다. • 이터레이터를 사용할 수 있지만 동시 실행 가능하지 않다. • Back메소드는 존재하지 않고 끝 값을 참조할 수도 없다. • size메소드 대신 동시 실행 가능하지 않은 unsafe_size메소드를 제공한다. • clear와 empty로 concurrent_que를 비울 수 있지만 empty만 동 시실행 가능하다.