Concurrency runtime::parallel_for_each 성능측정

VC++10 에 들어있는 Concurrency Runtime 라이브러리가 얼마나 쓸만한 녀석인지 궁금해서 한번 테스트를 해봤습니다. 우선 데이터 set의 사이즈만 다르게 하여 (하나는 천 개의 데이터셋, 다른 하나는 십 만개의 데이터 셋) 두 가지 종류의 테스트를 실행했는데, 결론은 역시 멀티코어 CPU 를 충분히, 그리고 잘 활용하면 어플리케이션의 많은 성능 이득을 얻을 수 있다는 점을 느낄 수 있었습니다. 일단 제가 작성한 코드는 아래와 같습니다.

/*
ConcurrencyRT_parallel_for_each
*/

#include <ppl.h> //parallel_for_each()
#include <iostream>
#include <vector>
#include <algorithm> //for_each()
#include <time.h> //clock()
#include <random> //uniform_int_distribution, mt19937

#define _CASE1_

#ifdef _CASE1_
const int DATA_SET_SIZE = 1000;
#elif //_CASE2_
const int DATA_SET_SIZE = 100000;
#endif

//Generate random number

std::vector<int> getRandomArr()
{
	std::vector<int> v;
	std::uniform_int_distribution<int> dis(0, DATA_SET_SIZE);
	std::mt19937 engine;
	auto generator = std::bind(dis, engine);

	for(int i = 0; i < DATA_SET_SIZE; i++)
	{
		v.push_back(generator());
	}
	return v;
}

int main()
{ 
	//1. Prepare dataset
	auto v = getRandomArr(); 
	
	//2. Test performance of sequential process
	std::cout<<"Seqential process ------------ "<<std::endl;
	clock_t start = clock(); //start 
	std::for_each(v.begin(), v.end(), [&](double n) {
		for(int i = 0; i < n; i++)
		{ 
			double k = n*n;
		}
	});
	clock_t finish = clock(); //end
	double time = (double)(finish - start) / CLOCKS_PER_SEC;
	std::cout<<"Elapsed time for Sequential : "<<time<<std::endl;
	
	//3. Test performance of parallel process	
	std::cout<<"\n\nparallel process ------------ "<<std::endl;
	start = clock();//start 
	Concurrency::parallel_for_each(v.begin(), v.end(), [&](double n){
		for(int i = 0; i < n; i++)
		{ 
			double k = n*n;
		}
	});
	finish = clock();//end
	time = (double)(finish - start) / CLOCKS_PER_SEC;
	std::cout<<"Elapsed time for PPL : "<<time<<std::endl;
	
	return 0;
}

테스트 코드라 대충대충 작성해서 그런지, 지금 보니 그닥 이쁘진 않은데요. 테스트가 목적이니까, 대충 넘어갑시다 ㅎㅎ. getRandomArr 함수는 지정된 크기만큼의 벡터를 만들어서 그 안을 랜덤 넘버로 꽉꽉 채워넣는 부분입니다. C++11에 도입된 random 헤더와 관련 api를 사용했고요.

main함수가 테스트코드인데, 먼저 우리가 흔히 사용하는 방법인 sequential 한 for문을 사용하여 간단한 연산을 수행하는 코드를 작성후 그 시간을 측정했고, 두 번째로 concurrency runtime 라이브러리에 들어있는 parallel_for_each 문을 사용하여 동일한 연산을 수행하는 코드를 작성후 그 시간을 측정했습니다.

테스트 환경은 Intel Core i7 CPU 인데, core가 여덟개 짜리입니다.

첫 번째로 데이터 set의 사이즈를 1000 으로 했을때 실행결과입니다.

Sequential process ------------
Elapsed time for Serial : 0.001

parallel process ------------
Elapsed time for PPL : 0.003
Press any key to continue . . .

이런 테스트와 같이 데이터 set의 크기가 그렇게 크지 않은 경우엔 오히려 기존의 for문이 더 나은 성능을 보여줍니다. MSDN 에서도 조언하는대로 데이터 set의 사이즈가 작은 경우에 병렬 프로그래밍은 오히려 독이 될 수 있습니다. 그 이유는 다른 CPU 간의 context switching 에 많은 시간을 할당해야 하기 때문에, 오히려 실행시간이 길어지기 때문입니다.

두번째로 data set의 사이즈를 십만개로 늘려 잡았을때의 실행결과 입니다.

Sequential process ------------
Elapsed time for Serial : 10.036

parallel process ------------
Elapsed time for PPL : 1.987
Press any key to continue . . .

이제야 병렬프로그래밍이 제 힘을 쓰는 것 같습니다. 2초대 10초. 대략 5배의 성능을 보여주고 있습니다. Intel i7 플랫폼의 위용이 느껴집니다.

Concurrency Runtime library는 병렬 프로그래밍용 라이브러리이기 때문에, 결과는 하드웨어의 성능(cpu 코어갯수) 에 따라 다른 성능을 냅니다. 이번에는 제 오래된 랩탑인 Intel Core 2 duo 플랫폼(2 core)에서 돌려봤습니다.

첫 번째로 데이터 set의 사이즈를 1000 으로 했을때 실행결과입니다.

Seqential process ------------
Elapsed time for Sequential : 0.002

parallel process ------------
Elapsed time for PPL : 0.006

역시 기존 for문이 차라리 낫습니다.

두번째로 data set의 사이즈를 십만개로 늘려 잡았을때의 실행결과 입니다.

Sequential process ------------
Elapsed time for Sequential : 18.378

parallel process ------------
Elapsed time for PPL : 9.718

코아 2개짜리 옛날 CPU 라도 좋은 성능 개선 효과를 얻을 수 있었습니다.

하지만, 무턱대고 사용하면 오히려 독이 될 수 있으니, 어떤 상황에서 사용하고, 어떻게 사용해야 좋은지를 충분히 알고 있어야 하겠습니다.
http://msdn.microsoft.com/en-us/library/ff601930.aspx 를 보시면 PPL (Parallel Programming Library)을 위한 Best Practice 에 어떤게 있는지 잘 나와있네요. 충분히 이해하고, 타겟머신이 윈도우즈 플랫폼일때, 어플리케이션에서 대용량의 데이터를 처리해야 할때, PPL을 고려해보는 것도 나쁘지 않은 선택인것 같습니다.

Advertisements

About sydlife

Lazy programmer, Dad and Husband.
%d bloggers like this: