The safety system in the C# Job system
Race conditions
멀티스레드 코드를 작성할때, 항상 Race Conditions 에 빠지는 것을 주의해야 한다. 이는 하나의 작업 결과값이 다른 스레드 영역에 있는 처리 결과가 완료될때까지 한없이 기다리게 되는 현상을 말한다. 이를 Race Conditions or Race Hazard라 한다.
이는 디버깅하기가 매우 힘들게 하고, 원인을 찾는 데 매우 어렵다. 그러므로 이런 코드를 작성하면 안된다.
Safety system
Job system은 이런 잠재적인 Race Conditions들을 탐지하고, 그들이 일으킬수도 있는 버그들을 막는다.
예를 들어, 메인스레드에 있는 데이타의 reference를 Job System이 Job으로 전송했다면, 그 Job이 그 참조 데이타를 변경하려고 할때, 메인스레드에서 동시에 데이타를 읽을수도 있다. 이게 Race Condition 이다.
C# Job System은 데이타의 reference가 아닌 복사본을 만들어서 Job에게 주는 것으로 이 문제를 해결한다.
데이타 복사본은 Bittable data types 이어야 한다. 이는 Managed( Unity 로 작업한 사용자 코드) 와 Native Code (Unity Job System) 간의 컨버젼이 필요없는 호환 데이타 타입이다. 이 타입은 memcpy로 복사가 되고, 이 두 시스템간에 송수신이 가능하다.
NativeContainer
데이타 복사처리의 단점은 이들 데이터들이 각 쓰레드 영역에서 분리되어 있다는 것이다. 이를 보완하기위해 나온것이 NativeContainer라 불리는 공유 메모리 타입이다.
What is a NativeContainer?
이는 Managed value type 이다. Native 메모리를 안전하게 c# Wrapper를 가지고 있다. unmanaged 메모리 할당영역에 대한 포인터를 가지고 있다. Unity C# Job System 사용시, 이는 Job이 메인스레드와 함께 접근할수 있도록 한다.
What types of NativeContainer are available?
Unity는 NativeArray라 불리는 NativeContainer를 사용한다. You can also manipulate a NativeArray with NativeSlice to get a subset of the NativeArray from a particular position to a certain length.
Note: ECS 패키지는 다음 타입들이 있는 Unity.Collections namespace 가지고 있다.
- NativeList - a resizable NativeArray
- NativeHashMap - key and value pairs
- NativeMultiHashMap - multiple values per key
- NativeQueue - a first in , first out (FIFO) queue
NativeContainer and the safety system
The safety system은 모두 NativeContainer 타입으로 이루어져 있다. 이것은 무엇이 읽고 있고, 어느 NativeContainer에 쓰고 있는지 추적한다.
Note: NativeContainer 타입의 안전 체크( out of bounds check, deallocation check, race condition check)는 UnityEditor, PlayMode 상에서만 작동합니다.
이 시스템은 DisposeSentinel, AtomicSafetyHandle 기술을 포함하고 있다.
DisposeSentinel은 메모리 누수를 감지하고, 사용자의 메모리를 잘 해제안시키면, 에러를 발생시킨다.
NativeContainer의 ownership을 코드상에서 전달해주고 싶을때는 AtomicSafetyHandle을 사용해라. 예를 들어, 두개의 Jobs들이 동시에 같은 NativeArray에 쓰기를 할때, 이 시스템은 에러를 출력하고 어떻게 해결할지를 알려준다. 이 경우에는 jobs들간의 의존성을 만들어서 해결 할수 있다. 첫번째 Job이 먼저 NativeContainer에게 쓰게하고 그 Job이 끝나면, 다음 Job이 안전하게 그것들을 읽고 같은 NativeContainer에게 쓰게 한다. 이런 쓰기 , 읽기 제한은 메인스레드에 있는 데이타를 접근할때도 적용된다. 이는 다수의 Jobs 들이 병렬로( 어떻게 이게 병렬이지? 병렬처럼 보이게 한다는 것인가?) 같은 데이타를 읽게 해준다.
초기에, Job은 NativeContainer에 접근을 하면, 읽기, 쓰기 권한을 모두 갖는다. 이 설정은 성능에 않좋다. 이 시스템은 어떤 Job이 쓰고 있는 데이타에 동시에 또 다른 Job 이 쓰도록 권한을 주지 않는다.
만약 Job이 NativeContainer에게 쓰기권힌이 필요 없을때는 [ReadOnly] 를 선언시 붙여주자.
[ReadOnly]
public NativeArray<int> input;
Note: Job안에 있는 static 선언 데이타에 대해서는 이 안전시스템이 작동되지 않는다. Unity 가 죽는 요소일수 있으니 주의하기 바란다.
NativeContainer Allocator
NativeContainer를 만들때 반드시 필요한 메모리 할당 타입을 지정해야 한다. 이는 Job 을 수행하는 시간에 영향을 미친다. 각각의 상황별로 최고의 성능향상을 위해서는 이를 잘 지정해야한다. 아래와 같은 3가지 할당타입이 있다.
- Allocator.Temp
- 가장 빠른 할당.
- 1 frame 혹은 그보다 적은 수명을 갖는다.
- you should not pass NativeContainer allocations using Temp to Jobs.
- Dispose 메서드를 호출해야 한다. ( Native 에서 Managed로 복귀할때)
- Allocator.TempJob
- Temp보다는 느리지만, Persistent 보다 빠른 할당.
- 4 frame 의 수명. thread-safe.
- 4 frame내에서 dispose를 호출하지 않으면, Warning이 뜬다.
- 대부분의 작은 Job들이 사용하는 타입이다.
- Allocator.Persistent
- 가장 느린 할당. 필요하다면 앱이 실행중인 내내 지속된다.
- 작업량이 많은 Job일 경우 사용.
- 성능이 필수적이라면 이 타입의 사용을 자제한다.
사용예)
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);
1은 NativeArray의 사이즈를 말한다. 이 경우 하나의 element를 갖는 array를 말한다.
댓글 없음:
댓글 쓰기