개발/Developing

[C++17] std::execution - 알고리즘의 병렬화

lazykuna 2022. 2. 19. 01:22

어쩌다 보니 재미있는 물건을 새로 보게 되었다. std::execution이라는 녀석인데, 요즘 추세에 조금이라도 발맞춰 보고자(?) 만든 재미있는 기능 같다. 그런데 조금 파 보니 기대와는 다르게, 이대로는 실제로 써먹기에는 좀 애매한 물건 같다. 그래도 시도는 재밌어 보여 글을 쓴다.

Preface

C++17에 들어오면서 std::execution 이라는 물건이 생겼다고 한다. 이게 뭐하는 물건인고 하니 ...

C++17 added support for parallel algorithms to the standard library, to help programs take advantage of parallel execution for improved performance.

설명 자체는 이러한데, 조금 더 상세하게 이야기하면 기존 행위 함수 (map-like e.g. for_each, rotate, ...) 를 병렬화하여 수행할 수 있게 해 준다는 거다. 이거 어디서 많이 본 거 같은데... 미묘하게 Goroutine 닮지 않았나?? 나만 그런가...

Details

단독으로 쓰이는 것은 아니고, 기존 STL 메서드에 인자로 넘겨져서 병렬화를 수행하게 한다. 이를테면, 아래의 예처럼 ...

int x = 0;
std::mutex m;
int a[] = {1,2};
std::for_each(std::execution::par, std::begin(a), std::end(a), [&](int) {
  std::lock_guard<mutex> guard(m);
  ++x;
});

보다 구체적으로는, std::executionparpar_unseq가 있고, 설명은 아래와 같다.

Currently, the standard includes the parallel policy, denoted by std::execution::par, and the parallel unsequenced policy, denoted by ``std::execution::par_unseq```. In addition to the requirements exposed by the parallel policy, the parallel unsequenced policy requires that your element access functions tolerate weaker than concurrent forward progress guarantees. That means that they don’t take locks or otherwise perform operations that require threads to concurrently execute to make progress.

말 그대로 순차 실행을 보장하기 위해 추가적으로 lock을 사용할 것인가 말 것인가의 문제이다. 당연히 후자가 더 빠르다.

Doubts

그래도 막상 사용하기에는 미흡한 점이 있다.

성능상의 문제?

아무래도 시스템 스레드를 매번 생성하고 지우는 만큼 단순한 작업을 수행하는 경우 expensive 하다. std::all_of와 같은 간단한 알고리즘의 경우 parallel execution하면 오히려 실행 시간이 는다고도 한다. #

Thread pooling?

앞선 std::for_each 코드에서, 그러면 몇개의 스레드가 어떻게 수행할 지 알 수 있을까? 멀티스레드를 어떻게 만들고 수행할지는 알 수도 없고 지정할 수도 없다.

정확히는 코드를 까면 알 수는 있겠지만 결국 매 메서드마다의 구현에 따라 달려 있어서 ... 투명하지 못한 점은 여전히 문제

위 점과 관련해서, 시도는 좋았으나 막상 해당 기능을 In-production에서 사용하기는 굉장히 꺼려질 수밖에 없다.
경량 thread를 별도로 제공하거나, Thread pool을 제공하여 thread 사용에 대한 부담을 줄일 수 있어야 될 것 같다.

안 그래도 관련하여 새 제안이 있는 것으로 보인다. 언제 도입될지는 미지수.

executor = get_executor();
sort( std::execution::par.on(executor), vec.begin(), vec.end());

글쎄...

C++ 수요층을 생각하면 이대로는 써먹기 어려운 물건이 아닌가 싶다.

간단하고 high-throughput 필요한 C++ 프로그램을 만들 때는 유용할만 하지만, thread를 과다하게 생성할 가능성도 있을테고 (미연에 방지 했을수도 있으나 이런 구조로는 겉보기만으로는 도저히 알 수가 없다) 오히려 성능이 안 나오는 경우도 있을 수 있고, 보수적인 구조의 프로그램 제작에는 다소 사용하기 어려울 것 같다.

개인적으로는 스레드풀이 먼저 나오고 그것 기반으로 만들어졌다면 훨씬 더 큰 파장을 일으킬 수 있지 않았나 싶다. 차후에 나올지도 모르니 그 때 기대를 ...

출처