오늘은 무얼 소개해볼까 하다가..
회사에서 획기적으로 성능향상을 경험한 Parallel (병렬처리) 메서드 사용법에 대해 이야기해보고자 한다.
사건의 발단은 이렇다.
- 유니티 내에서 에셋번들을 다운로드 받아서 정해진 위치에 생성시키는 컨텐츠가 있다.
- 이 에셋번들이 30개를 넘어가자 하나의 쓰레드에서는 (코루틴도 아니었었다 처음엔)
- 15초가 넘게 걸리는 불상사가 걸리고 만것이다.
- <다운로드 -> 에셋생성 -> 에셋위치시키기 > (반복)
병렬처리는 많은 반복작업에 있어서 효율적이게 처리할 수 있다는 특징이 있다.
유니티에서는 Job시스템이나.. 매우 다수의 오브젝트를 처리하는 뭐 였는지는 기억 갑자기 안나는데 다른 시스템이
별개로 존재하지만
간단한 부분에서 사용하기에는 Parallel 메서드도 충분히 경쟁력있다.
일단 따로 공부를 안해도된다는 점이 어떻게 보면 장점이랄까.
물론 나는 결국에는 코루틴으로 처리하긴 했지만, 조금 더 다수의 반복호출이었다면 아마 병렬처리를 썼을 것이다.
Parallel.ForEach를 사용하여 단순 병렬 프로그램 작성 - .NET
이 문서에서는 .NET에서 데이터 병렬 처리를 사용하도록 설정하는 방법을 알아봅니다. IEnumerable 또는 IEnumerable 데이터 소스에 대한 Parallel.ForEach 루프를 작성합니다.
learn.microsoft.com
Parallel은 for와 forEach 두개로 사용할 수 있다.
두개의 차이는 말그대로 for는 index라는 특정 변수로 훑을 것이냐와 foreach 로 IEnumrable한 객체를 훑을 것이냐의 차이이다.
유니티에서 for와 forEach의 차이랑 비슷하다고 보면 되겠다.
기존코드와 개선된 코드를 봐보자
//기존코드
private IEnumerator LoadDataCo(SpaceData datas)
{
int cnt = datas.Length;
for(int i = 0; i< cnt; i++)
{
yield return 다운받고 생성시키는 함수 호출
}
}
yield에 계속 걸리다보니 하나의 에셋이 다운받아지고 생성되고 위치시키는 것이 모두 완료된 후에야
다음 작업으로 가다보니 갯수가 많아질수록 병목현상이 눈에 띄게 체감;
그러고나서 나온 두가지 솔루션..
private void ParallelLoad(MySpaceData spaceData) //1번 코드
{
_mySpaceData = spaceData;
Parallel.ForEach(_mySpaceData.spaceObjects,data =>
{
UnityMainThreadDispatcher.Instance().Enqueue(() => StartCoroutine(ParallelLoadDataCo(data)));
//이 부분은 코루틴 돌리는 부분인데 밑에서 설명..
});
}
private IEnumerator LoadAssetsParallel(MySpaceData spaceData) //2번 코드
{
_mySpaceData = spaceData;
List<Coroutine> loadCo = new List<Coroutine>();
yield return new WaitUntil(() => _mySpaceUIManager.GetBundleInfoList != null);
foreach (var item in _mySpaceData.spaceObjects)
{
loadCo.Add(StartCoroutine(ParallelLoadDataCo(item)));
}
foreach(var item in loadCo)
{
yield return item;
}
}
private IEnumerator ParallelLoadDataCo(SpaceObjectData spaceData)
{
...생략...대충 에셋 다운받고 위치시키는곳
}
1번코드는 Parallel 클래스를 사용하는 것이다.
2번 코드는 코루틴시작을 한번에 여러개를 돌리는 것이다.
StopWatch를 재서 해보면 3~40개정도론 체감이 안되지만
0.03초와 0.3초 정도 10배가량의 속도차이를 보여준다.
하지만 유니티의 작업 예를 들면 게임오브젝트 생성이라든지
메인스레드에서만 실행되야 한다는 점을 고려하면
결국에는
UnityMainThreadDispatcher.Instance().Enqueue(() 이 부분은 밑의 코드와 유사하다고 보면 되는데..
public class CoManager : MonoBehaviour
{
private Queue<IEnumerator> _coroutine = new Queue<IEnumerator>();
public void AddCoroutine(IEnumerator co)
{
_coroutine.Enqueue(co);
}
private void Update()
{
if (_coroutine.Count > 0)
{
StartCoroutine(_coroutine.Dequeue());
}
}
}
이런 코루틴 전용 코드에서 돌아간다는 점이 같다고 볼 수 있다.
속도의 차이는 많은 오브젝트의 다운로드를 얼마나 효율적이게 시간을 활용하는가이다.
여러개의 스레드에서 다운받는것과
비동기 비스무리한 코루틴에서 (결국 한 스레드에서) 다운 받는 것의 차이.
당시 누군가는 Parallel을 모를 수도 있다고 판단하여 (유지보수할 사람들도 고려하여)
큰 차이가 없었어서 코루틴을 사용하였지만,
Parallel 병렬 처리를 하였어도 괜찮았을 법한 예시였었다.
'프로그래밍 > Unity' 카테고리의 다른 글
[유니티] Unity - ScriptableObject 잘 사용하기 (feat: json Editor) (0) | 2024.12.27 |
---|---|
[유니티] Unity - 코루틴 리턴값 받기와 Action에 대하여. (6) | 2024.12.13 |
[유니티] Unity WebGL - React.js와 쌍방향 통신하기 (2) | 2024.12.11 |
[유니티] Unity - NewInputSystem 새로운 인풋시스템 적용하기. (0) | 2024.12.10 |
Unity - Rest API 만들고 적용하기 (0) | 2024.12.09 |