KoreanFoodie's Study

[C++ 게임 서버] 1-24. 연습문제 (소수의 갯수 구하기) 본문

Game Dev/Game Server

[C++ 게임 서버] 1-24. 연습문제 (소수의 갯수 구하기)

GoldGiver 2023. 8. 8. 23:38

Rookiss 님의 '[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 를 들으며 배운 내용을 정리하고 있습니다. 관심이 있으신 분은 꼭 한 번 들어보시기를 추천합니다!

[C++ 게임 서버] 1-24. 연습문제 (소수의 갯수 구하기)

핵심 :

1. 연습문제를 풀어보자... (주어진 숫자까지의 소수의 갯수 구하기)

2. 쓰레드 혹은, future 를 통해 구현할 수 있다.

3. thread::hardware_concurrency() 를 이용해, 실제 코어 갯수만큼 쓰레드를 생성하는 것이 더 효율적일 수 있다(그보다 훨씬 많이 생성하는 것보다).

이제 연습문제를 풀어보자. 주어진 숫자에 대해, 1부터 해당 숫자까지 소수의 갯수를 구하는 문제이다.

 

일단 멀티 쓰레드를 이용할 것이니, 각 쓰레드에 태워 보낼 함수를 다음과 같이 정의하자 :

bool IsPrime(int InInput)
{
	if (InInput <= 1)
		return false;

	for (int i = 2; i < int(sqrt(InInput))+ 1; ++i)
	{
		if (InInput % i == 0)
		{
			return false;
		}
	}
	return true;
}

int CalculatePrime(int InStart, int InEnd)
{
	int sum = 0;
	for (int i = InStart; i <= InEnd; ++i)
	{
		if (IsPrime(i))
		{
			++sum;
		}
	}
	
	return sum;
}

 

이제 main 함수에서 위 함수들을 사용할 것이다. 두 가지 방법이 있는데, 일단 강의에서는 아래 방식을 제시한다 :

int main()
{
	const int MAX_NUMBER = 1'000'000;

	vector<thread> threads;

	int coreCount = thread::hardware_concurrency();
	int jobCount = (MAX_NUMBER / coreCount) + 1;

	atomic<int> primeCount = 0;

	for (int i = 0; i < coreCount; ++i)
	{
		int start = (i * jobCount) + 1;
		int end = min(MAX_NUMBER, (i+1) * jobCount);

		threads.push_back(thread([start, end, &primeCount]()
		{
			primeCount += CalculatePrime(start, end);
		}));
	}

	for (auto& t : threads)
	{
		t.join();
	}

	cout << "The number of Prime for " << MAX_NUMBER << " : " << primeCount << endl;
}

 

하지만 간단한 계산의 경우, future 를 사용하는 것이 더 간단할 때도 있다.

future 를 사용하면 다음과 같이 표현할 수 있다!

int main()
{
	const int MAX_NUMBER = 1'000000;

	const int MAX_THREAD = thread::hardware_concurrency();

	int stepVal = MAX_NUMBER / MAX_THREAD;

	// 무식하게 100등분하기
	int sum = 0;
	std::vector<shared_future<int>> futureVec;
	for (int i = 1; i <= MAX_THREAD; ++i)
	{
		shared_future<int> f =std::async(std::launch::async ,CalculatePrime, stepVal * (i-1), stepVal * i);
		futureVec.push_back(f);
	}

	if (stepVal * MAX_THREAD < MAX_NUMBER)
	{
		sum += CalculatePrime(stepVal * MAX_THREAD + 1, MAX_NUMBER);
	}
	
	// shared_future 을 이용하면 vector 를 이용해서 계산 가능 + for_each 로 안전하게 복사 없이 사용
	// 그냥 future 을 사용하면 오류 발생 "삭제된 함수를 참조하려고 합니다"
	for (shared_future<int>& f : futureVec)
	{
		sum += f.get();
	}
	
	// algo:: 네임스페이스를 이용한 계산
	//for_each(futureVec.begin(), futureVec.end(), [&sum](shared_future<int> f) { sum += f.get(); });
	
	cout << "The number of Prime+1 for " << MAX_NUMBER << " : " << sum << endl;
}

 

두 경우, 모두 다음과 같이 결과가 동일하게 나온다!

 

물론, 뒤의 그룹이 숫자가 더 커서 부하가 더 크겠지만... 일단 그런 건 넘어가도록 하자 😅

 

그렇다면 속도는 어떨까?

첫번째 녀석의 경우,  10'000'000 에 대해, 다음만큼 걸렸다 :

두번째 녀석의 경우, 10'000'000 에 대해, 다음만큼 걸렸다 :

 

future 보다, 그냥 쓰레드를 생성해서 하는 편이 더 빨랐다 😅

아무래도 쓰레드를 그냥 생성하는 것이 훨씬 가벼운 것이 아닌가 싶다!

Comments