몇 개의 스레드를 가져야합니까?


81

렌더링 및 로직 또는 그 이상을위한 별도의 스레드가 있어야합니까?

데이터 동기화로 인한 엄청난 성능 저하 (모든 뮤텍스 잠금은 제외)를 알고 있습니다.

나는 이것을 극단적으로 생각하고 생각할 수있는 모든 하위 시스템을 위해 스레드를 수행하려고 생각했습니다. 그러나 나는 그것이 너무 느려질 수도 있습니다. (예를 들어, 입력 스레드와 렌더링 또는 게임 논리 스레드를 분리하는 것이 정상입니까?) 필요한 데이터 동기화로 인해 무의미하거나 더 느려 집니까?


6
어떤 플랫폼? PC, NextGen 콘솔, 스마트 폰?
엘리스

멀티 스레딩이 필요할 것이라고 생각할 수있는 것이 있습니다. 네트워킹.
Soapy

확장을 종료하면 잠금이 관련 될 때 "강력한"속도 저하가 없습니다. 이것은 도시의 전설이며 편견입니다.
v.oddou

답변:


61

여러 코어를 활용하는 일반적인 방법은 솔직히 잘못 안내 된 것입니다. 서브 시스템을 다른 스레드로 분리하면 실제로 일부 작업이 여러 코어로 분할되지만 몇 가지 중요한 문제가 있습니다. 첫째, 작업하기가 매우 어렵습니다. 렌더링이나 물리 코드를 곧바로 작성할 수있을 때 누가 잠금, 동기화 및 통신 및 물건을 다루려고합니까? 둘째,이 접근법은 실제로 확장되지 않습니다. 기껏해야 이렇게함으로써 3 개 또는 4 개의 코어를 활용할 수있게되며, 실제로 수행중인 작업을 알고있는 것입니다. 게임에는 서브 시스템이 너무 많고 CPU 시간을 많이 차지하는 서브 시스템이 훨씬 적습니다. 내가 아는 몇 가지 좋은 대안이 있습니다.

하나는 추가 CPU마다 작업자 스레드와 함께 메인 스레드를 갖는 것입니다. 서브 시스템에 관계없이, 메인 스레드는 분리 된 작업을 일종의 대기열을 통해 작업자 스레드에 위임합니다. 이러한 작업 자체가 다른 작업을 생성 할 수도 있습니다. 작업자 스레드의 유일한 목적은 대기열에서 각 작업을 한 번에 하나씩 잡아서 수행하는 것입니다. 그러나 가장 중요한 것은 스레드가 작업 결과를 필요로하자마자 작업이 완료되면 결과를 얻을 수 있으며 그렇지 않은 경우 대기열에서 작업을 안전하게 제거하고 계속 수행 할 수 있다는 것입니다. 작업 자체. 즉, 모든 작업이 서로 동시에 예약되는 것은 아닙니다. 병렬로 실행할 수있는 것보다 많은 작업을 갖는 것이 좋습니다이 경우의 것; 코어를 추가할수록 확장 될 가능성이 있음을 의미합니다. 이것의 한 가지 단점은 이미 이것을 제공하는 라이브러리 또는 언어 런타임에 액세스 할 수 없다면 괜찮은 큐와 작업자 루프를 디자인하기 위해 많은 작업이 필요하다는 것입니다. 가장 어려운 부분은 작업이 진정으로 격리되고 스레드 안전을 유지하고 작업이 거칠고 세밀한 중간에 행복한 중간 위치에 있는지 확인하는 것입니다.

서브 시스템 스레드의 다른 대안은 각 서브 시스템을 분리하여 병렬화하는 것입니다. 즉, 자체 스레드에서 렌더링 및 물리를 실행하는 대신 물리 코어를 한 번에 모든 코어를 사용하도록 작성하고 렌더링 서브 시스템을 작성하여 모든 코어를 한 번에 사용하도록 한 다음 두 시스템을 순차적으로 실행 (또는 인터리브, 게임 아키텍처의 다른 측면에 따라). 예를 들어 물리 하위 시스템에서 게임의 모든 포인트 질량을 코어로 나누고 모든 코어가 한 번에 업데이트하도록 할 수 있습니다. 그런 다음 각 코어는 우수한 로컬 리티를 통해 타이트한 루프로 데이터를 처리 할 수 ​​있습니다. 이 잠금 단계 스타일의 병렬 처리는 GPU와 유사합니다. 여기서 가장 어려운 부분은 작업을 균등하게 나눌 수 있도록 세밀한 덩어리로 나누는 것입니다.실제로 모든 프로세서에서 동일한 양의 작업이 발생합니다.

그러나 때때로 정치, 기존 코드 또는 기타 좌절 환경으로 인해 각 서브 시스템에 스레드를 제공하는 것이 가장 쉬운 경우가 있습니다. 이 경우 CPU가 많은 워크로드에 대해 코어보다 많은 OS 스레드를 만들지 않는 것이 가장 좋습니다 (코어간에 균형을 맞추기 위해 가벼운 스레드가있는 런타임이있는 경우에는 큰 문제가되지 않습니다). 또한 과도한 의사 소통을 피하십시오. 한 가지 좋은 트릭은 파이프 라이닝을 시도하는 것입니다. 각 주요 하위 시스템은 한 번에 다른 게임 상태에서 작업 할 수 있습니다. 파이프 라이닝은 서브 시스템에서 동시에 동일한 데이터에 액세스 할 필요가 없기 때문에 서브 시스템간에 필요한 통신량을 줄이며 병목 현상으로 인한 일부 손상을 무효화 할 수도 있습니다. 예를 들어 물리 하위 시스템을 완료하는 데 시간이 오래 걸리고 렌더링 하위 시스템이 항상 대기하는 경우 렌더링 하위 시스템이 이전 프레임에서 계속 작동하는 동안 다음 프레임에 대해 물리 하위 시스템을 실행하면 절대 프레임 속도가 높아질 수 있습니다 틀. 실제로 이러한 병목 현상이 발생하여 다른 방법으로 제거 할 수없는 경우 파이프 라이닝이 하위 시스템 스레드를 방해하는 가장 합법적 인 이유 일 수 있습니다.


"스레드가 작업 결과를 필요로하는 즉시, 작업이 완료되면 결과를 얻을 수 있으며, 그렇지 않으면 대기열에서 작업을 안전하게 제거하고 계속해서 해당 작업을 수행 할 수 있습니다." 같은 스레드에서 생성 된 작업에 대해 이야기하고 있습니까? 그렇다면 해당 작업이 작업 자체를 생성 한 스레드에 의해 실행된다면 더 이해가되지 않습니까?
jmp97

즉, 스레드는 작업을 예약하지 않고도 해당 작업을 즉시 실행할 수 있습니다.
jmp97

3
요점은 스레드가 작업을 병렬로 실행하는 것이 더 나은지 여부를 미리 알 필요는 없다는 것입니다. 아이디어는 추후에 수행해야 할 작업을 추측 적으로 발산하는 것이며, 다른 스레드가 유휴 상태 인 경우 계속 진행하여이 작업을 수행 할 수 있습니다. 결과가 필요할 때까지이 문제가 발생하지 않으면 대기열에서 직접 작업을 가져 오기만하면됩니다. 이 체계는 정적이 아닌 여러 코어에서 워크로드 를 동적으로 균형 조정하기 위한 것입니다.
Jake McArthur

이 글타래로 돌아 가기까지 시간이 오래 걸리 서 죄송합니다. 최근 gamedev에 관심을 기울이지 않습니다. 이것은 아마도 가장 좋은 대답 일 것입니다.
j riv

1
I / O가 많은 워크로드에 대해서는 언급하지 않은 것이 맞습니다. 이 질문에 대한 나의 해석은 그것이 CPU가 많은 작업량에 관한 것이라는 것이었다.
Jake McArthur

30

고려해야 할 몇 가지가 있습니다. 서브 시스템 당 스레드 경로는 쉽게 구분할 수 있으므로 코드 분리가 명확하기 때문에 생각하기 쉽습니다. 그러나 서브 시스템에 필요한 상호 통신량에 따라 스레드 간 통신이 실제로 성능을 저하시킬 수 있습니다. 또한 이것은 N 코어로만 확장되며, 여기서 N은 스레드로 추상화 한 서브 시스템의 수입니다.

기존 게임을 멀티 스레딩하려는 경우 이것이 가장 저항이 적은 경로 일 수 있습니다. 그러나 여러 게임이나 프로젝트간에 공유 될 수있는 일부 저수준 엔진 시스템에서 작업하는 경우 다른 접근법을 고려할 것입니다.

약간의 왜곡이 필요할 수 있지만 작업자 스레드 세트를 사용하여 작업 대기열로 분류 할 수 있다면 장기적으로 훨씬 더 잘 확장됩니다. 최신 칩과 가질 리언 코어가 나오면 게임 성능도 함께 확장되고 더 많은 작업자 스레드가 발생합니다.

따라서 기본적으로 기존 프로젝트와의 병렬 처리를 원한다면 하위 시스템간에 병렬화를 수행합니다. 병렬 확장 성을 염두에두고 처음부터 새 엔진을 구축하는 경우 작업 대기열을 살펴 보겠습니다.


언급 한 시스템은 Other James가 제공 한 답변에 언급 된 일정 시스템과 매우 유사합니다.이 영역에서는 여전히 세부 사항이 좋으므로 토론에 추가 할 때 +1입니다.
James

3
작업 대기열 및 작업자 스레드를 설정하는 방법에 대한 커뮤니티 위키가 좋습니다.
bot_bot

23

그 질문은 당신이 성취하려는 것에 달려 있기 때문에 최선의 대답은 없습니다.

xbox에는 3 개의 코어가 있으며 컨텍스트 전환 오버 헤드가 문제가되기 전에 몇 개의 스레드를 처리 할 수 ​​있습니다. PC는 훨씬 더 많은 것을 다룰 수 있습니다.

많은 게임은 일반적으로 프로그래밍하기 쉽도록 단일 스레드입니다. 이것은 대부분의 개인 게임에 좋습니다. 다른 스레드가 필요할 수있는 유일한 것은 네트워킹 및 오디오입니다.

언리얼에는 게임 스레드, 렌더 스레드, 네트워크 스레드 및 오디오 스레드가 있습니다 (정확하게 기억하는 경우). 별도의 렌더링 스레드를 지원할 수 있기는 쉽지만 많은 토대가 필요하지만 이것은 현재의 많은 엔진에 표준입니다.

Rage 용으로 개발중인 idTech5 엔진은 실제로 임의의 수의 스레드를 사용하며 게임 작업을 작업 시스템으로 처리되는 '작업'으로 분류하여 수행합니다. 그들의 명백한 목표는 평균 게임 시스템의 코어 수가 점프 할 때 게임 엔진의 확장 성을 좋게하는 것입니다.

내가 사용하고 작성한 기술에는 네트워킹, 입력, 오디오, 렌더링 및 예약을위한 별도의 스레드가 있습니다. 그런 다음 게임 작업을 수행하는 데 사용할 수있는 스레드 수에 제한이 없으며 일정 스레드에 의해 관리됩니다. 많은 작업은 서로 잘 재생하는 모든 스레드를 얻기에 갔다,하지만 아주 좋은 사용 밖으로 멀티 코어 시스템을 점점 잘 작동하는 것 같군, 그래서 아마도 지금은 (수행의 임무는, 나는 오디오 / 네트워킹을 분해 할 수 / 입력은 작업자 스레드가 업데이트 할 수있는 '작업'으로 만 작동합니다.

그것은 실제로 당신의 최종 목표에 달려 있습니다.


스케줄링 시스템의 언급에 +1. 일반적으로 스레드 / 시스템 통신을 중심으로하는 좋은 장소 :)
James

왜 다운 투표, 다운 투표자?
jcora

12

서브 시스템 당 스레드는 잘못된 방법입니다. 갑자기 일부 하위 시스템이 다른 하위 시스템보다 더 많이 요구하기 때문에 앱이 확장되지 않습니다. 이것은 최고 사령관이 취한 스레딩 접근 방식으로, 16 개의 스레드가 있지만 다른 스레드에는 상당한 양의 CPU 렌더링 및 물리 / 게임 논리를 차지하는 2 개의 하위 시스템 만 있기 때문에 2 개의 코어를 넘어 확장되지 않았습니다. 거의 모든 작업에 그치지 않았으며 결과적으로 게임은 두 개의 코어로만 확장되었습니다.

당신이해야 할 일은 스레드 풀이라는 것을 사용하는 것입니다. 이것은 GPU에서 취한 접근 방식을 반영합니다. 즉, 작업을 게시하면 사용 가능한 스레드가 단순히 와서 수행 한 다음 링 대기 버퍼와 같은 스레드의 작업으로 돌아갑니다. 이 방법은 N 코어 스케일링의 이점을 가지며 코어 수가 적거나 많을 때 스케일링에 매우 적합합니다. 단점은 주어진 시간에 어떤 스레드가 어떤 작업을 수행하고 있는지 알 수 없으므로 소유권 문제를 매우 엄격하게 고정해야하기 때문에이 접근법에 대한 스레드 소유권을 처리하는 것이 매우 어렵다는 것입니다. 또한 다중 스레드를 지원하지 않는 Direct3D9와 같은 기술을 사용하기가 매우 어렵습니다.

스레드 풀은 사용하기가 매우 어렵지만 최상의 결과를 제공합니다. 아주 좋은 확장이 필요하거나 작업 할 시간이 충분하면 스레드 풀을 사용하십시오. 알려지지 않은 종속성 문제와 단일 스레드 기술을 사용하여 기존 프로젝트에 병렬 처리를 도입하려는 경우 이것이 해결책이 아닙니다.


좀 더 정확하게 말하면 GPU는 스레드 풀을 사용하지 않고 스레드 스케줄러가 하드웨어로 구현되므로 스레드 생성 및 컨텍스트 스위치가 비싼 CPU와 달리 새로운 스레드 및 스위치 스레드를 만드는 것이 매우 저렴합니다. 예를 들어 Nvidias CUDA 프로그래머 안내서를 참조하십시오.
Nils

2
+1 : 최고의 답변입니다. 프레임 워크에서 허용하는 경우 스레드 풀 (예 : 작업 큐 및 작업자)보다 더 추상적 인 구성을 사용하려고합니다. 순수한 쓰레드 / 락 등보다이 용어로 생각 / 프로그래밍하는 것이 훨씬 쉽습니다. 또한 렌더링, 논리 등으로 게임을 분할하는 것은 말이되지 않습니다. 렌더링은 논리가 완료 될 때까지 기다려야하기 때문입니다. 오히려 실제로 병렬로 실행될 수있는 작업을 작성하십시오 (예 : 다음 프레임의 경우 한 npc의 AI 계산).
Dave O.

@DaveO. 당신의 "플러스"요점은 사실입니다.
엔지니어

11

가장 중요한 부분은 가능한 한 동기화를 피하는 것입니다. 이를 달성하는 몇 가지 방법이 있습니다.

  1. 처리 요구에 따라 데이터를 알고 메모리에 저장하십시오. 이를 통해 동기화 할 필요없이 병렬 계산을 계획 할 수 있습니다. 불행히도 데이터는 종종 다른 시스템에서 예측할 수없는 시간에 액세스하기 때문에 달성하기가 매우 어렵습니다.

  2. 데이터에 대한 명확한 액세스 시간을 정의하십시오. 메인 틱을 x 단계로 분리 할 수 ​​있습니다. 스레드 X가 특정 단계에서만 데이터를 읽는 것이 확실한 경우 다른 단계의 다른 스레드가이 데이터를 수정할 수 있다는 것도 알고 있습니다.

  3. 데이터를 이중 버퍼링하십시오. 이것이 가장 간단한 접근 방법이지만 스레드 X가 마지막 프레임의 데이터를 처리하는 동안 스레드 Y는 다음 프레임의 데이터를 준비하므로 대기 시간이 늘어납니다.

저의 개인적인 경험에 따르면 세분화 된 계산이 서브 시스템 기반 솔루션보다 훨씬 더 잘 확장 될 수 있으므로 가장 효과적인 방법이됩니다. 서브 시스템을 스레드하면 프레임 시간이 가장 비싼 서브 시스템에 바인드됩니다. 이로 인해 값 비싼 서브 시스템이 마침내 작업을 완료 할 때까지 모든 스레드가 유휴 상태가 될 수 있습니다. 게임의 많은 부분을 작은 작업으로 분리 할 수 ​​있으면 코어를 유휴 상태로 피하기 위해 이러한 작업을 예약 할 수 있습니다. 그러나 이것은 이미 큰 코드 기반을 가지고 있다면 달성하기 어려운 것입니다.

일부 하드웨어 제약 조건을 고려하려면 하드웨어를 과도하게 구독하지 마십시오. 초과 가입을 사용하면 플랫폼 하드웨어 스레드보다 소프트웨어 스레드가 더 많습니다. 특히 PPC 아키텍처 (Xbox360, PS3)에서 작업 스위치는 실제로 비쌉니다. 초과 구독을 한 적은 수의 스레드 만있는 경우 (예를 들어 한 번 프레임) PC를 ​​대상으로하는 경우 코어 수 (또는 더 나은 HW)를 명심해야합니다. -Threads)가 지속적으로 증가하고 있으므로 추가 CPU-Power를 활용하는 확장 가능한 솔루션을 찾고 싶을 것입니다. 따라서이 영역에서는 코드를 가능한 한 작업 기반으로 디자인해야합니다.


3

응용 프로그램 스레딩에 대한 일반적인 경험 규칙 : CPU 코어 당 1 개의 스레드. 4를 의미하는 쿼드 코어 PC에서 언급했듯이 XBox 360에는 3 개의 코어가 있지만 각각 2 개의 하드웨어 스레드가 있으므로이 경우 6 개의 스레드가 있습니다. PS3와 같은 시스템에서 ... 행운을 빌어 :) 사람들은 여전히 ​​그것을 알아 내려고 노력하고 있습니다.

각 시스템을 원하는 경우 스레드 할 수있는 자체 포함 모듈로 설계하는 것이 좋습니다. 이것은 일반적으로 모듈과 나머지 엔진 사이에 매우 명확하게 정의 된 통신 경로를 갖는 것을 의미합니다. 특히 렌더링 및 오디오와 같은 읽기 전용 프로세스는 물론 스레드 할 항목에 대한 플레이어 입력 읽기와 같은 '우리는 아직 있습니다'프로세스를 좋아합니다. AttackingHobo가 제공하는 답변을 터치하기 위해 30-60fps를 렌더링 할 때 데이터가 오래된 1/30 ~ 1/60 초이면 실제로 게임의 반응을 방해하지 않습니다. 응용 프로그램 소프트웨어와 비디오 게임의 주요 차이점은 초당 30-60 회 모든 작업을 수행한다는 점을 항상 기억하십시오. 그러나 같은 메모에서

엔진 시스템을 충분히 설계하면 게임마다 엔진을보다 적절하게로드 밸런싱하기 위해 시스템을 스레드에서 스레드로 충분히 이동할 수 있습니다. 이론적으로는 완전히 별도의 컴퓨터 시스템이 각 구성 요소를 실행하는 곳에 분산 시스템에서 엔진을 사용할 수도 있습니다.


2
X 박스 360은 코어 당 2 hardwarethreads이 있으므로 최적의 스레드 수는 6입니다
DarthCoder

Ah, +1 :) 나는 항상 360과 ps3의 네트워킹 영역으로 제한되었다. hehe :)
James

0

논리적 코어 당 하나의 스레드를 생성합니다 (마이너스 스레드는 메인 스레드를 설명하기 위해 우연히 렌더링을 담당하지만 작업자 스레드로도 작동 함).

프레임 전체에서 입력 장치 이벤트를 실시간으로 수집하지만 프레임이 끝날 때까지 적용하지 마십시오. 다음 프레임에 영향을 미칩니다. 그리고 렌더링 (오래된 상태) 대 업데이트 (새로운 상태)에 유사한 논리를 사용합니다.

원자 이벤트를 사용하여 나중에 같은 프레임에서 안전하지 않은 작업을 연기하고 잠금 또는 대기하지 않고 작업 순서에 대해 철저한 보증을 제공하는 메모리 장벽을 구현하기 위해 둘 이상의 이벤트 큐 (작업 큐)를 사용합니다. (작업 우선 순위에 따라 사용 가능한 동시 큐 잠금).

모든 작업이 동일한 우선 순위 큐 또는 상위 (프레임에서 나중에 제공됨)의 하위 작업 (더 세밀하고 원자성에 접근)을 발행 할 수 있다는 점에 주목할 필요가 있습니다.

이러한 대기열이 세 개인 경우 하나를 제외한 모든 스레드가 프레임 당 정확히 세 번 정지 될 수 있습니다 (다른 스레드가 현재 우선 순위 수준에서 발행 된 모든 미해결 작업을 완료하기를 기다리는 동안).

이것은 허용 가능한 수준의 스레드 비 활동으로 보입니다!


내 프레임은 MAIN으로 시작하여 이전 프레임의 업데이트 패스에서 OLD STATE를 렌더링하지만 다른 모든 스레드는 즉시 NEXT 프레임 상태를 계산하기 시작합니다. 이벤트를 사용하여 아무도 더 이상 읽지 않는 프레임의 지점까지 버퍼 상태 변경을 두 배로 늘립니다. .
호머

0

나는 보통 하나의 메인 스레드를 사용하고 (약간) 성능이 약 10-20 % 감소 할 때마다 스레드를 추가합니다. 이러한 하락을 막기 위해 Visual Studio의 성능 도구를 사용합니다. 일반적인 이벤트는지도의 일부 영역을로드 (언로드)하거나 계산을 많이하는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.