Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

CPU 최적화

성능

프로그램 속도를 높이는 방법을 알려면 "병목 현상"이 어디에 있는지 알아야 합니다. 병목 현상은 모든 것이 진행될 수 있는 속도를 제한하는 프로그램의 가장 느린 부분입니다. 병목 현상에 초점을 맞추면 작은 성능 향상으로 이어질 기능을 최적화하는 데 많은 시간을 소비하는 대신 가장 큰 속도 향상을 제공할 영역을 최적화하는 데 노력을 집중할 수 있습니다.

CPU의 경우 병목 현상을 식별하는 가장 쉬운 방법은 프로파일러를 사용하는 것입니다.

CPU 프로파일러

프로파일러는 프로그램과 함께 실행되며 타이밍 측정을 통해 각 기능에 소요되는 시간 비율을 계산합니다.

Godot IDE에는 프로파일러가 내장되어 있어 편리합니다. 프로젝트를 시작할 때마다 실행되지는 않습니다. 수동으로 시작하고 중지해야 합니다. 이는 대부분의 프로파일러와 마찬가지로 이러한 타이밍 측정을 기록하면 프로젝트 속도가 크게 느려질 수 있기 때문입니다.

프로파일링 후에는 프레임에 대한 결과를 다시 볼 수 있습니다.

Godot 프로파일러의 스크린샷

데모 프로젝트 중 하나의 프로필 결과.

참고

하단에는 물리, 오디오 등 내장된 프로세스의 비용과 자체 스크립팅 기능의 비용을 확인할 수 있습니다.

다양한 내장 서버를 기다리는 데 소요된 시간은 프로파일러에서 계산되지 않을 수 있습니다. 이것은 알려진 버그입니다.

프로젝트가 느리게 실행되면 다른 기능이나 프로세스보다 훨씬 더 많은 시간이 걸리는 명백한 기능이나 프로세스를 종종 볼 수 있습니다. 이것이 주요 병목 현상이며 일반적으로 이 영역을 최적화하여 속도를 높일 수 있습니다.

Godot의 내장 프로파일러 사용에 대한 자세한 정보는 :디버거을 참조하세요.

외부 프로파일러

Godot IDE 프로파일러는 매우 편리하고 유용하지만 때로는 더 많은 기능과 Godot 엔진 소스 코드 자체를 프로파일링하는 기능이 필요합니다.

이를 위해 :ref:`다양한 타사 C++ 프로파일러 <doc_using_cpp_profilers>`를 사용할 수 있습니다.

Callgrind의 스크린샷

Valgrind의 일부인 Callgrind의 결과 예입니다.

왼쪽부터 Callgrind는 함수 및 해당 자식 노드(포함) 내 시간의 백분율, 하위 함수(자체)를 제외한 함수 자체 내에서 소요된 시간의 백분율, 함수 호출 횟수, 함수 이름, 파일 또는 모듈을 나열합니다.

이 예에서는 거의 모든 시간이 Main::iteration() 함수에서 소비되는 것을 볼 수 있습니다. 이것은 반복적으로 호출되는 Godot 소스 코드의 마스터 함수입니다. 프레임이 그려지고, 물리 틱이 시뮬레이션되고, 노드 및 스크립트가 업데이트됩니다. 이 예제에서는 2D 벤치마크를 사용하기 때문에 캔버스를 렌더링하는 기능에 많은 시간(66%)이 소요됩니다. 아래에서 우리는 거의 50% of 시간이 libglapi``i965_dri``(그래픽 드라이버)의 Godot 코드 외부에서 소비되는 것을 볼 수 있습니다. 이는 그래픽 드라이버에서 CPU 시간의 상당 부분이 소비되고 있음을 나타냅니다.

이상적인 세계에서는 매우 적은 시간만이 그래픽 드라이버에 소요되기 때문에 이는 실제로 훌륭한 예입니다. 이는 그래픽 API에서 너무 많은 통신과 작업이 수행되는 데 문제가 있음을 나타냅니다. 이러한 특정 프로파일링을 통해 2D 일괄 처리가 개발되었으며, 이는 이 영역의 병목 현상을 줄여 2D 렌더링 속도를 크게 향상시켰습니다.

수동으로 함수 타이밍 측정

특히 프로파일러를 사용하여 병목 현상을 확인한 후 또 다른 유용한 기술은 테스트 중인 기능이나 영역의 시간을 수동으로 측정하는 것입니다. 구체적인 내용은 언어에 따라 다르지만 GDScript에서는 다음을 수행합니다.

var time_start = Time.get_ticks_usec()

# Your function you want to time
update_enemies()

var time_end = Time.get_ticks_usec()
print("update_enemies() took %d microseconds" % (time_end - time_start))

함수의 타이밍을 수동으로 측정할 때 일반적으로 함수를 한 번만 실행하는 대신(매우 느린 함수가 아닌 한) 여러 번(1,000회 이상) 실행하는 것이 좋습니다. 이렇게 하는 이유는 타이머의 정확도가 제한적인 경우가 많기 때문입니다. 더욱이 CPU는 무작정 프로세스를 예약합니다. 따라서 일련의 실행에 대한 평균이 단일 측정보다 더 정확합니다.

기능을 최적화하려고 시도할 때 반복적으로 프로파일링하거나 시간을 측정해야 합니다. 이를 통해 최적화가 작동하는지 여부에 대한 중요한 피드백을 얻을 수 있습니다.

캐시

CPU 캐시는 특히 두 가지 다른 버전의 함수에 대한 타이밍 결과를 비교할 때 특히 주의해야 할 사항입니다. 결과는 데이터가 CPU 캐시에 있는지 여부에 따라 크게 달라질 수 있습니다. CPU는 CPU 캐시에 비해 크기가 크더라도(몇 메가바이트가 아닌 몇 기가바이트) 시스템 RAM에서 직접 데이터를 로드하지 않습니다. 이는 시스템 RAM의 액세스 속도가 매우 느리기 때문입니다. 대신, CPU는 캐시라고 불리는 더 작고 빠른 메모리 뱅크에서 데이터를 로드합니다. 캐시에서 데이터를 로드하는 것은 매우 빠르지만 캐시에 저장되지 않은 메모리 주소를 로드하려고 할 때마다 캐시는 주 메모리로 이동하여 일부 데이터를 천천히 로드해야 합니다. 이러한 지연으로 인해 CPU가 오랫동안 유휴 상태로 있을 수 있으며 이를 "캐시 누락"이라고 합니다.

이는 함수를 처음 실행할 때 데이터가 CPU 캐시에 없기 때문에 느리게 실행될 수 있음을 의미합니다. 두 번째 이후에는 데이터가 캐시에 있기 때문에 훨씬 빠르게 실행될 수 있습니다. 따라서 타이밍을 잡을 때 항상 평균을 사용하고 캐시의 영향을 주의하세요.

캐싱을 이해하는 것도 CPU 최적화에 중요합니다. 메인 메모리의 무작위로 분산된 영역에서 작은 비트의 데이터를 로드하는 알고리즘(루틴)이 있는 경우 이로 인해 많은 캐시 누락이 발생할 수 있으며 많은 시간 동안 CPU는 작업을 수행하는 대신 데이터를 기다리게 됩니다. 대신, 데이터 액세스를 지역화하거나 더 나은 방법으로 선형 방식(예: 연속 목록)으로 메모리에 액세스할 수 있다면 캐시가 최적으로 작동하고 CPU가 최대한 빠르게 작동할 수 있습니다.

Godot는 일반적으로 그러한 낮은 수준의 세부 사항을 처리합니다. 예를 들어, 서버 API는 렌더링 및 물리학과 같은 작업에 대해 이미 캐싱에 데이터가 최적화되어 있는지 확인합니다. 하지만 GDExtensions를 작성할 때는 캐싱에 특히 주의해야 합니다.

언어

Godot는 다양한 언어를 지원하며, 관련된 절충안이 있다는 점을 명심할 가치가 있습니다. 일부 언어는 속도를 희생하면서 사용하기 쉽도록 설계되었으며, 다른 언어는 더 빠르지만 작업하기가 더 어렵습니다.

내장된 엔진 기능은 선택한 스크립팅 언어에 관계없이 동일한 속도로 실행됩니다. 프로젝트가 자체 코드에서 많은 계산을 수행하는 경우 해당 계산을 더 빠른 언어로 이동하는 것이 좋습니다.

GDScript

:ref:`GDScript <doc_gdscript>`은 쉽게 사용하고 반복할 수 있도록 설계되었으며 다양한 유형의 게임을 만드는 데 이상적입니다. 그러나 이 언어에서는 성능보다 사용 편의성이 더 중요하게 간주됩니다. 많은 계산을 해야 한다면 프로젝트 일부를 다른 언어 중 하나로 옮기는 것을 고려해 보세요.

C#

:ref:`C# <doc_c_sharp>`는 인기가 높으며 Godot에서 최고 수준의 지원을 제공합니다. 속도와 사용 편의성 사이에서 적절한 절충안을 제공합니다. 하지만 게임 플레이 중에 발생할 수 있는 가비지 수집 일시 중지 및 누출 가능성에 주의하세요. 가비지 수집 문제에 대한 일반적인 해결 방법은 *객체 풀링*을 사용하는 것입니다. 이는 이 가이드의 범위를 벗어납니다.

기타 언어

제3자는 `Rust <https://github.com/godot-rust/gdext>`_을 포함한 여러 다른 언어에 대한 지원을 제공합니다.

C++:

Godot는 C++로 작성되었습니다. C++를 사용하면 일반적으로 가장 빠른 코드가 생성됩니다. 그러나 실제 수준에서는 다양한 플랫폼의 최종 사용자 컴퓨터에 배포하는 것이 가장 어렵습니다. C++ 사용 옵션에는 GDExtensions 및 :ref:`custom 모듈 <doc_custom_modules_in_cpp>`이 포함됩니다.

스레드

서로 병렬로 실행할 수 있는 많은 계산을 수행하는 경우 스레드 사용을 고려하십시오. 최신 CPU에는 여러 개의 코어가 있으며 각 코어는 제한된 양의 작업을 수행할 수 있습니다. 작업을 여러 스레드에 분산함으로써 최대 CPU 효율성을 향해 더 나아갈 수 있습니다.

스레드의 단점은 엄청나게 조심해야 한다는 것입니다. 각 CPU 코어는 독립적으로 작동하므로 동시에 동일한 메모리에 액세스하려고 시도할 수 있습니다. 한 스레드가 변수를 읽는 동안 다른 스레드는 변수를 읽을 수 있습니다. 이를 *경합 조건*이라고 합니다. 스레드를 사용하기 전에 위험과 이러한 경쟁 조건을 방지하는 방법을 이해했는지 확인하십시오. 스레드는 디버깅을 훨씬 더 어렵게 만들 수 있습니다.

스레드에 대한 자세한 정보는 멀티스레드 사용하기를 참조하세요.

씬트리

노드는 믿을 수 없을 정도로 강력하고 다재다능한 개념이지만 모든 노드에는 비용이 있다는 점에 유의하세요. _process()``_physics_process()``와 같은 내장 기능은 트리를 통해 전파됩니다. 이러한 관리는 노드 수가 매우 많을 때 성능을 저하시킬 수 있습니다(정확히 그 수는 대상 플랫폼에 따라 다르며 수천에서 수만까지 다양할 수 있으므로 개발 중에 모든 대상 플랫폼에서 성능을 프로파일링해야 합니다).

각 노드는 Godot 렌더러에서 개별적으로 처리됩니다. 따라서 각각에 더 많은 노드 수를 줄이면 더 나은 성능을 얻을 수 있습니다.

SceneTree. 예를 들어, 이는 게임에서 영역을 추가하고 제거하는 데 매우 유용할 수 있습니다.

서버 API를 사용하면 SceneTree를 완전히 피할 수 있습니다. 자세한 내용은 :ref:`doc_using_servers`를 참조하세요.

물리

어떤 상황에서는 물리학이 병목 현상을 일으킬 수 있습니다. 이는 특히 복잡한 세계와 많은 수의 물리 개체의 경우에 해당됩니다.

적어 놓을 만한 사항들이 있습니다:

  • 충돌 모양에 대해 렌더링된 형상의 단순화된 버전을 사용해 보십시오. 종종 이는 최종 사용자에게 눈에 띄지 않지만 성능을 크게 향상시킬 수 있습니다.

  • 물체가 시야에서 벗어나거나 현재 영역 밖에 있을 때 물리에서 개체를 제거하거나 물리 개체를 재사용해 보십시오(예를 들어 영역당 8마리의 몬스터를 허용하고 이를 재사용할 수도 있음).

물리학의 또 다른 중요한 측면은 물리학 틱 속도입니다. 일부 게임에서는 틱 속도를 크게 줄일 수 있으며, 예를 들어 물리학을 초당 60회 업데이트하는 대신 초당 30회 또는 심지어 20회만 업데이트할 수도 있습니다. 이렇게 하면 CPU 부하를 크게 줄일 수 있습니다.

물리 틱 속도 변경의 단점은 물리 업데이트 속도가 렌더링된 초당 프레임 수와 일치하지 않을 때 갑작스러운 움직임이나 지터가 발생할 수 있다는 것입니다. 또한 물리 틱 속도를 줄이면 입력 지연이 증가합니다. 실시간 플레이어 움직임이 특징인 대부분의 게임에서는 기본 물리 틱 속도(60Hz)를 고수하는 것이 좋습니다.

지터에 대한 해결책은 *고정 시간 단계 보간*을 사용하는 것입니다. 여기에는 물리학과 일치하도록 여러 프레임에 걸쳐 렌더링된 위치와 회전을 부드럽게 만드는 작업이 포함됩니다. Godot에는 :ref:`here<doc_physics_interpolation>`에 대해 읽을 수 있는 물리 보간법이 내장되어 있습니다. 성능 측면에서 보간은 물리 틱을 실행하는 것에 비해 매우 저렴한 작업입니다. 이는 훨씬 더 빠르므로 지터를 줄이는 동시에 상당한 성능 향상을 가져올 수 있습니다.