본문 바로가기

언리얼 개발자

[Unreal] Time-Slicing

* 이 게시물에 대한 비판, 조언 모두 환영합니다.

 

 작업을 하다 보면 한 틱에 이렇게 많은 연산을 해도 될까? 라는 고민을 할 때가 있다.

예를 들면 수십만 개의 Cell을 받아 Update를 해주어야 하는 경우이다. 이 Update는 굳이 한 번에 완료될 필요는 없고,

여러 틱에 걸쳐 해도 되는 작업이라도 가정해보자.

 

한 틱에 수십만 개의 Cell을 Update하면 게임이 멈춰버릴 것이며, 여기에 스레드를 할당하기는 싫다.

그렇다고 해서 임의로 한 틱에 Update할 Cell의 개수를 임의로 정하는 것도 말이 안된다. 몇 개가 적당하며, 모든 Cell의 Update가 동일한 연산일 보장도 없을 것이다.

 

이런 상황에서 Time-Slicing이 유용했다.

간략히 말하자면 한 틱에 작업을 수행할 시간을 정하고 그 시간 동안만 작업을 수행하도록 처리하는 것이다.

 

아래의 ATimeSlicingCounter Class는 테스트를 위해 짠 Actor를 상속한 클래스이다.

// Called every frame
void ATimeSlicingCounter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (m_IsProcessing == true)
	{
		// 틱 증가
		++m_TickCounter;

		// 이전 작업량 캐싱
		int32 PrevWorkCount = m_CurrentWorkCount;

		// one tick, use 0.1ms
		double RemainingTime = 0.0001f;

		while (RemainingTime > 0.0f)
		{
			// 작업 시작 전 Time
			const double StartTime = FPlatformTime::Seconds();

			// Do work begin
			++m_CurrentWorkCount;
			// Do work end

			// 작업 완료 후 Time
			const double EndTime = FPlatformTime::Seconds();

			// 남은 시간 감소
			RemainingTime -= (EndTime - StartTime);

			// 작업 완료
			if (m_CurrentWorkCount >= m_TotalWorkCount)
			{
				break;
			}
		}

		// 이번 틱에 수행한 작업량
		const int32 ProcessedCount = m_CurrentWorkCount - PrevWorkCount;

		// 지금까지의 작업 진행률
		const float WorkPercent = (m_CurrentWorkCount / (float)m_TotalWorkCount) * 100.0f;

		// 작업 완료
		if (m_CurrentWorkCount >= m_TotalWorkCount)
		{
			m_IsProcessing = false;
			UE_LOG(LogTemp, Log, TEXT("[TimeSlicing] Complete ! WorkPercent[%d], ProcessedCount[%d], TickCounter[%d]"), (int32)WorkPercent, m_CurrentWorkCount, m_TickCounter);
		}

		else
		{
			UE_LOG(LogTemp, Log, TEXT("[TimeSlicing] Continue.. WorkPercent[%d], Processed Count in tick[%d], RemainWorkCount[%d]")
				, (int32)WorkPercent, ProcessedCount, m_TotalWorkCount - m_CurrentWorkCount);
		}
	}
}

 

위 코드의 실행 결과는 아래의 사진처럼 한 틱에 약 7500개씩, 27번의 틱에 걸쳐 작업을 완수했다.

코드 수행 결과

 

 

 

[ 주의 ]

 만약에 작업 수행 구간에 DrawDebug를 해주거나 Log를 심게 되면 성능이 현저히 낮아지기 때문에, 한 틱의 작업을 완수한 후에 디버깅 및 로그를 심는 것이 좋다.

 불가피 하게 작업 수행 구간에 디버깅 용 코드가 들어가야 한다면 디버깅 용 코드 작업 수행 시간을 따로 계산하여 Remaining Time에 적용해 볼만 하겠지만 게임 자체가 버벅이거나 본래의 양만큼 작업이 수행되지 않는다.