멀티코어지원을 안해서 게임이 느려…?

게임 관련 사이트나 하드웨어 사이트의 좇문가들은 멀티코어를 지원하지 않아서 게임 성능이 오르지 않는다고 주장한다.
실제로 개발사가 멀티코어를 제대로 지원하지 못하는 경우도 있다. 하지만 열심히 멀티코어를 활용하려고 노력한다해도 대부분의 경우 별로 안빨라진다. 그에 비해 버그는 100배쯤 는다. 그것도 잡기 힘든 멀티 스레드 버그가.

우선 게임에서 병렬처리할 요소라고 해봐야 픽셀단위, 버텍스 단위 억세스다. 즉 삼각형에 텍스처 입혀서 쉐이더에서 가공하여 렌더링하는 작업이다. 이미 GPU가 잘 하고 있다. CPU가 하는 일에선 병렬처리할만한게 많지않다.

가장 큰 문제는 게임에서 중요한건 응답성이지 처리량이 아니라는 점이다.
멀티코어를 잘 활용하면 1시간 걸릴 작업이 20분으로 줄어든다. 1초(1000ms) 걸릴 작업을 200ms정도로 줄이는것까지도 가능하다. 그런데 게임에서 60fps를 보장하려면 한 프레임 처리하는데 16ms를 넘기면 안된다.

예를 들어 게임 루프 안에서 병렬 처리할만한 작업이 있다 치자. 동기화 객체를 덕지덕지 붙여서 성능은 떨어뜨리고 버그를 양산하는 대신, 일반적으로 작업 단위를 쪼개서 스레드풀을 활용한다.
메인 스레드는 스레드풀에서 대기중이던 워커스레드들을 깨우고 워커스레드들이 처리를 완료할때까지 대기한다. 스레드를 깨우려면 시스템콜을 호출해야한다. 커널은 스레드를 깨운다. 워커스레드가 작업을 완료하고 이벤트를 시그널 시켜서 메인스레드를 깨운다. 또 시스템콜을 호출한다. 깨우고 대기하고 하는데만 해도 상당히 큰 지연시간이 생긴다.
프레임당 몇번 정도는 괜찮다. 하지만 프레임당 몇 백번 정도로만 올라가도 이것만으로 몇ms가 그냥 날라갈 수 있다.
그러니까 멀티코어를 잘 활용한다는 것은 바꿔말하면 다수의 워커스레드를 깨우고 작업이 완료되기
를 기다리는 오버헤드가 증가한다는 뜻도 된다.
동영상 인코딩이야 4시간 걸리던 작업을 1시간으로 줄이면 좋은것이다. 여기서 스레드간 통신을 위해서 1000ms정도 추가적인 오버헤드가 붙는다 해도 전혀 문제될 것이 없다. 하지만 게임에선 5ms만 더 붙여도 치명적이다.

그리고 다수의 스레드가 활성화되면(결과적으로 다수의 코어가 활성화되면) 최대 클럭은 떨어진다. 스레드간 동기화 문제와 스레드 컨텍스트 스위칭의 비용의 문제가 있지만 그래도 모든 코어가 항상 최대 클럭으로 작동할 수 있다면 다수의 스레드가 활성화된 상태를 유지하는 편이 나을 수 있다. 하지만 현존하는 거의 모든 CPU는 활성화된 코어수에 따라서 클럭이 변화한다. 당연히 1코어만 활성화 되었을때가 가장 클럭이 높다. 확실하게 성능을 높여주지 못할 바엔 활성화된 스레드(코어)가 1개인 상태가 낫다.

또한 0번 코어에서 돌아가던 태스크(스레드 퀀텀)가 다음번 스케쥴링에서서 1번 코어로 넘어갔다면 대량의 캐시 미스가 발생한다. L3캐시가 있긴 하지만 L1, L2 캐시의 내용은 아무짝에 쓸모가 없어진다.

워커스레드를 깨우고 대기하는데 걸리는 시간 + 스레드간 컨텍스트 스위칭 비용 + 최대 클럭 저하 + 추가적인 캐시미스
이 정도 오버헤드면 멀티코어로 얻을 수 있는 성능향상을 상쇄하고도 남는다.

이러한 이유로 게임에서 열심히 다양한 멀티스레딩 기법을 도입해도 큰 성과를 보기 어렵다. (아주 불가능하다고는 안했다. 어렵다고 했다.)

결론 : 2코어 머신으로 돌리던 게임을 4코어에서 돌려도 성능이 크게 오르지 않는 것은 개발사가 낡고 태만해서가 아니다.

p.s:
시도조차 안하는 개발사가 더 많은건 사실이다:)


멀티코어지원을 안해서 게임이 느려…?”에 대한 답글 5개

  1. 안녕하세요, 최근 게임 프로그래밍을 공부하면서 이것저것 접하고 있는 잉여입니다.
    잘 모르고 있어서 궁금해서 여쭙고 싶은 부분이 있습니다.
    요즘 외국 포럼이나 윈도우 공인으로 나오는 멀티 쓰레딩 관련 서적을 찾아보면 Lock-Free라고 하는 동기화 객체 없이 멀티 쓰레딩을 거는 방법이라던가 굳이 동기화 객체의 부하를 고려하지 않아도 괜찮을 방법이라던가
    여러가지 최적화에 대한 제시들이 있는데,
    이런 부분에 대해서도 마찬가지로 어차피 응답성은 문제가 있다고 생각하시는지 궁금합니다.

    좋아요

    1. Lock-Free라고 해봐야 atomic operation을 이용해서 만드는데요. 이거 상황에 따라선 동기화 객체로 lock걸고 대기하는것보다 더 성능이 떨어질 수 있습니다. atomic operation은 공짜가 아니고요. 자체로 생기는 캐시미스 + memory flushing에 걸리는 시간 + 다른 프로세서를 대기하게 만듦 등의 비용이 따릅니다. lock-free알고리즘을 사용해서 더 응답성이 좋게 만들수도 있겠죠. 그런데 그건 일부 자료구조에 한정된 얘기입니다.
      일반적으로 멀티스레드 프로그래밍을 할때는 모든 코드에 모든 스레드가 접근하는 식으로 작성하지 않습니다. 따라서 정상적으로 프로그래밍했다면 락을 걸고 대기하는 시간 때문에 심각하게 성능이 떨어지진 않습니다. 일반적으로 lock-free알고리즘을 사용하면 동기화 객체를 사용하는것보단 응답성이 좋을 가능성이 높습니다만 이것만으로 어플리케이션의 성능이 비약적으로 향상되진 않습니다.
      가장 중요한점인데 몇번이나 언급했지만 게임에선 멀티스레드로 병렬처리할만한 영역이 적습니다.
      동영상 인코딩이 아니니까요. 병렬처리할만한 작업은 이미 GPU가 다 하고 있습니다.
      입력을 받고 캐릭터들을 움직이고 충돌처리를 해서 최종 위치를 구하는 작업은 동시에 할 수 없습니다. 순차적으로 이루어집니다. 제 프로젝트의 경우는 서버에서 충돌처리를 멀티스레드로 돌립니다만 이건 캐릭터가 4000마리씩 되니까 의미가 있는거고 클라이언트에서 십수마리 정도 가지곤 티도 안납니다.
      이 정도면 제가 무슨 얘기하는지 이해하셨을걸로 생각합니다.

      좋아요

  2. 하지만 시뮬레이션 게임에서도 그럴까요
    플래닛 코스터같은 경우 사람 수천명, 시티즈 스카이라인 같은 경우에는 도시 크기에 따라서 수십만명의 인구와 차량의 경로를 일일이 계산해야하는데, 이걸 코어 1개로 커버해버리니…;;
    오버워치나 배그류라면 본인의 이야기가 맞겠지만, 과연 수십만 오브젝트들의 이동경로를 실시간으로 계산해야하는 게임들 또한 단일코어를 조지는게 차라리 나을지는 의문

    좋아요

    1. 개별 유닛들의 AI(길찾기 포함)때문에 느려지는걸텐데요. 그런 경우는 코어 수만큼의 스레드를 만들고 각 스레드에 균일하게 작업을 분배하는 것만으로도 성능이 올라가겠죠. 그런데 그건 병렬화하기 좋은 케이스고 대개는 병렬화할 건덕지가 없습니다.
      특히 대부분의 경우 렌더링할때 병목이 걸리는데 렌더링에 멀티스레드를 써봐야 거의 성능 향상이 되질 않습니다. 벌칸이나 DX12를 써도 마찬가지에요.

      좋아요

  3. 팩토리오 멀티스레딩 관련 포스트가 떠오르네요.
    공장 시뮬레이션 게임인데, 각각 독립적으로 보이는 기차 객체 업데이트, 전기 네트워크 전송, 운송 벨트 업데이트를 순차적으로 처리했던걸 각각 독립적으로 작동하게 스레드를 쪼갰는데… 오히려 느려졌고.. 이 포스트에서는 대량의 캐시 미스를 원인으로 생각하고 있네요.
    독립적으로 보인다고는 작성했지만 실제로는 프레임 단위로 업데이트하기 위해 동기화를 해야 하고, 결국 멀티스레드 오버헤드가 너무 커져서 쓰레드를 쪼갠게 의미가 없어지는 상황이 발생하고.. 그나마 (직관적으로) 멀티스레드 활용하기 쉬울 것 같은 시뮬레이션 게임이 이 모양이면, 다른 게임에서는 멀티스레드를 활용하는 것이 더욱 절망적이겠죠..

    https://factorio.com/blog/post/fff-215

    좋아요

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중