C++에 Verse 트랜잭션 메모리 시맨틱 구현하기

Phil Pizlo

포트나이트 버전 28.10부터 언리얼 엔진 기반의 대규모 C++ 서버 중 일부는 새로운 컴파일러로 컴파일됩니다. 새로운 컴파일러는 Verse와 호환되는 트랜잭션 메모리 시맨틱을 C++에 부여합니다. 이는 Verse 시맨틱을 보존하면서 Verse에 더 많은 언리얼 엔진 기능을 노출하기 위한 첫 단계입니다.

Verse 프로그래밍 언어에서 트랜잭션 메모리는 최고급 기능입니다. 예시는 다음과 같습니다. 

var X = 0
if:
    set X = 42      # 이 줄은 다음 줄 때문에 롤백됩니다
                    # 다음 줄
    X > 100         # 이것은 실패하여 롤백을 유발합니다
X = 0               # X는 롤백으로 인해 0으로 돌아갑니다

이 동작 방식은 Verse의 set 명령뿐만 아니라 모든 이펙트에 적용됩니다. 예를 들면, 변경 가능한 어떤 변수를 수정하여(즉, 포인터에 저장)하는 C++ 함수를 호출하고 다른 C++ 함수를 사용하여 해당 변경 가능 함수를 읽어들일 수 있습니다.

SetSomething(0)            # C++ 구현, C++ 변수를 설정합니다
                           # 변수
if:
    SetSomething(42)       # 이 줄은 다음 줄 때문에 롤백됩니다
                           # 다음 줄
    GetSomething() > 100   # 이것은 실패하여 롤백을 유발합니다
GetSomething() = 0         # 롤백으로 인해 0으로 돌아갑니다

따라서 적절한 Verse 시맨틱을 지원하기 위해 롤백 가능하도록 모든 이펙트를 기록하여 C++가 Verse의 트랜잭션 규칙을 따르게 해야 합니다.

Verse 코드는 항상 트랜잭션으로 실행되며 트랜잭션은 성공적으로 완료되거나 중단됩니다. 중단되는 트랜잭션은 '보이지 않으며' 항상 아무 일도 없었던 것처럼 모든 이펙트를 실행 취소합니다. Verse 트랜잭션 모델은 동시성에 영향을 줄 뿐 아니라, 다음과 같은 특징을 갖습니다.
  • Verse의 런타임 오류는 전체 트랜잭션을 중단시키므로 Verse는 성공하지 못한 트랜잭션의 커밋을 허용하지 않습니다.
  • if 문의 조건과 같은 Verse 실패 컨텍스트는 트랙잭션에 중첩되어, 조건이 성공하면 커밋되고 아니면 중단됩니다. 조건부 표현식은 이펙트를 가질 수 있습니다. 조건이 실패하면 마치 이펙트가 전혀 발생하지 않은 것과 같습니다.
  • 네이티브 코드에서 발생한 것을 포함하여, 모든 이펙트는 트랜잭션화됩니다. Verse 프로그램이 C++에 뭔가를 실행하도록 요청했는데 동일한 트랜잭션에서 런타임 오류가 발생한다면, C++가 실행한 것은 모두 실행 취소되어야 합니다. Verse 프로그램이 실패 컨텍스트에서 C++에 뭔가를 실행하도록 요청했는데 조건이 실패한다면, C++가 실행한 것은 모두 실행 취소되어야 합니다.
  • Verse 트랜잭션은 큰 범위로 묶입니다. Verse 코드는 기본적으로 항상 트랜잭션되며 해당 트랜잭션의 범위는 Verse 내 가장 외부의 호출자에 의해 결정됩니다. 대부분의 Verse API는 C++에서 구현되므로, Verse 트랜잭션을 중단한다는 것은 거의 항상 C++ 상태 변경 일부를 실행 취소해야 한다는 뜻입니다.

초기 Verse 구현은 다음 방법을 통해 이를 해결합니다.
  • Verse 런타임 오류 시맨틱을 지연시킵니다. 현재 Verse 런타임 오류는 중단된 트랜잭션을 올바르게 롤백하지 않습니다. 이는 고쳐야 할 부분입니다.
  • Verse 시맨틱과 달리, 실패 컨텍스트에서 많은 API의 호출을 허용하지 않습니다. 이는 언어에서 <no_rollback> 이펙트로 표시되며, 이 문제가 해결되는 즉시 폐기될 것입니다.
  • 트랜잭션을 지원하는 C++ 코드는 수행하는 모든 이펙트에 대해 실행 취소 핸들러를 수동으로 등록합니다. 이는 까다로운 부분이라 스케일되지 않으며(<no_rollback> 이펙트) 오류가 발생하기 쉬워, 삭제되는 항목에 실행 취소 핸들러가 등록되는 등 디버깅이 어려운 크래시를 유발합니다.

포트나이트 버전 28.10부터 일부 서버는 새 Clang 기반 C++ 컴파일러로 컴파일되며, 이를 AutoRTFM(실패 메모리의 자동 롤백 트랜잭션, Automatic Rollback Transactions for Failure Memory)이라고 합니다. 이 컴파일러는 C++ 코드를 자동으로 변환하여 모든 저장 인스트럭션(store instructions)을 포함한 각 이펙트에 실행 취소 핸들러를 정확하게 등록함으로써 Verse 롤백 시 실행 취소 가능하게 합니다. AutoRTFM은 트랜잭션 내에서 실행되지 않는 모든 C++ 코드에 대한 제로 퍼포먼스 오버헤드로 이를 달성합니다. 동일한 C++ 함수가 트랜잭션 내부와 외부에서 모두 사용되더라도 동일합니다. 오버헤드는 실행되는 스레드가 트랜잭션 범위 내에 있을 때만 추가됩니다. 또한 일부 C++ 코드는 AutoRTFM 내에서 작동하도록 수정이 필요하지만, 포트나이트 코드베이스에는 총 94개의 코드만 변경하여 AutoRTFM에 맞춰 조정했습니다. 앞으로 이 블로그에서 살펴보겠지만 그 변경사항조차 놀라울 정도로 간단합니다.

이 블로그에서는 이 컴파일러와 런타임 작동 방식을 자세히 살펴보면서 Verse 에코시스템에 어떤 영향을 미칠지 예상해 보겠습니다.

AutoRTFM 컴파일러 및 런타임

AutoRTFM은 대체 컴파일러를 활용하여 트랜잭션 시맨틱을 전혀 고려하지 않고 설계된, 기존 C++ 코드를 유지하며 트랜잭션에 적합하게 만들기 쉽도록 제작되었습니다. 예를 들어, AutoRTFM을 통해 다음과 같은 코드를 작성할 수 있습니다.

void Foo()
{
    TArray
<int> Array = {1, 2, 3};
    AutoRTFM::Transact([&] ()
    {
        
Array.Push(42);
        // Array is {1, 2, 3, 42} here.
        AutoRTFM::Abort();
    });
    // 중단으로 인해 배열이 {1, 2, 3}이 됩니다.
}

이렇게 하면 TArray가 변경되지 않고 작동합니다. 심지어 TArrayPush 내부의 백업 저장공간에 다시 할당해야 하더라도 작동합니다. TArray가 특별한 것이 아닙니다. AutoRTFM은 std::vector/std::map 및 언리얼 엔진과 C++ 표준 라이브러리의 수많은 기타 데이터 구조와 함수에 대해서도 이 작업을 수행할 수 있습니다. 이렇게 작동하도록 만들려면 AutoRTFM을 로우 레벨 메모리 할당자 및 가비지 콜렉터(Garbage Collector, GC)와 통합하기만 하면 됩니다. 이 섹션에서 보여드리겠지만 이러한 변경사항도 대단히 사소한 것입니다. 단순한 코드 추가이며, 그것도 단 몇 줄밖에 되지 않습니다. 언리얼 엔진의 많은 malloc, 언리얼 엔진의 GC, 시스템 malloc/new 모두에 해당 코드를 추가했습니다. 뿐만 아니라 AutoRTFM은 트랜잭션 외부(Transact 호출의 동적 범위 외부)에서 실행되는 코드 경로에 대해 이 모든 것을 제로 퍼포먼스 리그레션으로 달성합니다. 중요한 것은 TArray 외 다수의 코드 경로가 트랜잭션 내에서 사용되더라도 외부 트랜잭션을 사용하는 항목들이 비용을 초래하지 않는다는 것입니다. 코드가 트랜잭션 내에서 실행되는 경우 오버헤드가 높지만(내부 벤치마크에서 4배까지 확인된 바 있음), 포트나이트 서버의 틱 속도를 저해할 만큼 높지는 않습니다.

이 섹션에서는 이것이 어떻게 가능한지 설명합니다. 우선 전반적인 아키텍처를 알아본 다음 신규 LLVM 컴파일러 패스를 설명합니다. 이는 코드를 직접 검사하지 않고 트랜잭션에 참여한 모든 코드의 트랜잭션 클론을 생성한 후 이 클론에 대해서만 코드를 검사하는 컴파일러 패스입니다. 다음으로 AutoRTFM을 기존 C++ 코드에 통합하기 쉽게 만드는 런타임 및 API를 살펴보며 할당자 등에 대해서도 이야기합니다. 그 다음 두 섹션에서는 이 두 컴포넌트를 각각 설명합니다.
 

AutoRTFM 아키텍처

장기적으로는 트랜잭션 메모리를 사용하여 병렬로 작업하는 것이 목표입니다. 트랜잭션 메모리는 읽기-쓰기 세트를 유지하여 병렬성을 제공하려는 경향이 있습니다. 이는 트랜잭션이 실행되는 힙의 상태 스냅샷을 트래킹합니다. 따라서 쓰기와 읽기 모두 검사가 필요합니다. 그렇지 않으면 일부 위치를 읽기만 하는 트랜잭션이 동시 커밋으로 인해 경합하는 상황이 생겨도 알 수 없게 됩니다.

그러나 단기적으로는 다음 두 가지 목표를 달성해야 합니다.
  • Verse 코드에 대한 트랜잭션 시맨틱(Verse 코드가 C++를 호출하는 경우 포함). 여기에 병렬 작업은 필요하지 않지만 트랜잭션 메모리는 필요합니다.
  • 포트나이트 서버(아주 크고 발전된 C++ 코드) 컴파일을 트랜잭션 메모리 시맨틱을 추가하는 새 컴파일러로 정규화.

따라서 AutoSTM(소프트웨어 트랜잭션 메모리, software transactional memory)이 아니라 단일 스레드 트랜잭션만 구현하는 AutoRTFM이 우리의 첫 단계입니다. AutoRTFM은 엄격하게 더 단순한 문제만 처리합니다. 동시성이 없기 때문에(Verse에서 호출하는 게임플레이 코드는 이미 메인 스레드 전용으로 예상됨) 쓰기만 기록하면 됩니다. 읽기를 무시하면 힙만 읽는 모든 코드가 AutoRTFM에서 작동하므로 초기 구현에서 오버헤드가 낮아져 버그가 적어집니다.

AutoRTFM의 아키텍처는 개념적으로 간단합니다. AutoRTFM 런타임은 메모리 위치와 그 이전 값들의 로그를 유지합니다. 컴파일러는 코드 검사를 통해 모든 메모리 쓰기에 AutoRTFM 런타임의 함수를 호출합니다. 다음 두 섹션에서는 컴파일러와 런타임의 세부적인 사항을 살펴봅니다.
 

LLVM 컴파일러 패스

AutoRTFMPass의 역할은 모든 코드를 검사하여 Verse의 트랜잭션 시맨틱을 따르게 하는 것입니다. 그러나 포트나이트의 동일한 코드가 어떨때는 스택에 Verse 코드가 없고 Verse 시맨틱이 필요하지 않은 위치에서 사용될 수도 있고 어떨때는 스택에 Verse가 있고 롤백이 가능한 위치에서 사용될 수 있습니다.

"만약 현재 스레드가 트랜잭션에 있다면 쓰기 로깅" 같은 코드를 방출하는 대신 AutoRTFTM 컴파일러는 클론을 중심으로 빌드됩니다. 트랜잭션에 있다는 것은 전적으로 트랜잭션 클론에서 실행되어 표현된다는 뜻입니다. 사실상 bCurrentThreadIsInTransaction 비트가 프로그램 카운터에 인코딩됩니다. 컴파일러의 관점에서 보면 간단합니다. 모든 코드를 복제하고 복제된 함수에 맹글링된 이름을 부여한 다음 클론만 검사하는 것입니다. 원본 코드는 검사되지 않은 상태이며 로깅이나 기타 작업을 하지 않기 때문에 코드 실행에 따른 비용은 0입니다. 복제된 코드는 검사를 통해 트랜잭션에 있음을 분명히 알게 됩니다. 따라서 대체 호출 규칙 등 VM 같은 트릭을 더욱 원활하게 활용할 수 있습니다.

코드가 복제되기 때문에 클론 내의 간접 함수 코드도 검사가 필요합니다. 함수가 간접 호출을 수행하면 호출하려는 포인터는 호출 대상의 원본 버전에 대한 포인터일 것입니다. 따라서 복제된 코드 내에서 모든 간접 호출은 우선 AutoRTFM 런타임에 해당 함수의 복제된 버전을 요청합니다. 모든 AutoRTFM 검사와 마찬가지로 이는 복제된 코드의 비용을 높이지만 원본 코드에는 영향을 주지 않습니다.

AutoRTFMPass는 LLVM 파이프라인에서 새니타이저와 동일한 시점, 즉 모든 IR 최적화가 이뤄지고 코드가 백엔드로 전송되기 직전에 실행됩니다. 이는 중요한 의미를 담고 있습니다. 최적화를 거치고 살아 남은 불러오기 및 저장만 검사를 받는 대상이 됩니다. 따라서 C++에서 비탈출(non-escaping) 로컬 변수에 할당하는 것은 추상적인 의미로 '저장'이지만(Clang은 해당 프로그램에 대해 최적화되지 않은 LLVM IR 생성 시 이 방식으로 표현), 이 저장은 AutoRTFMPass 기능이 동작하면 완전히 사라집니다. 이는 높은 확률로 SSA 데이터플로에 의해 대체되므로 AutoRTFM가 수행될 필요가 없습니다. 또한 기존 LLVM 통계 분석을 기반으로 몇 가지 간단한 최적화를 구현하여, 저장의 검사가 불필요함을 입증 가능한 경우 해당 단계를 건너뛰게 만들었습니다.

함수 클로닝의 경우, 새로운 컴파일러로 C++ 코드를 컴파일하면서 AutoRTFM 런타임 API를 호출하지 않으면 클론이 호출되지 않습니다. 따라서 다수의 불필요한 코드가 있는 대규모 바이너리의 비용(0은 아니지만 0에 가까움) 외에 다른 오버헤드를 겪지 않게 됩니다.

다음 섹션에서는 AutoRTFM 런타임이 검사받은 클론을 동작하게 만드는 방법을 알아봅니다.
 

AutoRTFM 런타임

AutoRTFM의 런타임은 언리얼 엔진 Core에 통합되어 있어, 포트나이트 같은 언리얼 엔진 프로그램의 관점에서는 항상 이용 가능합니다. 이토록 획기적인 기능을 선사하는 AutoRTFM의 핵심 함수는 AutoRTFM::Transact입니다. 이 함수를 람다로 호출하면 런타임이 로그 데이터 구조 및 기타 트랜잭션 상태를 포함하는 트랜잭션 오브젝트를 구성합니다. 그런 다음 Transact가 람다의 클론을 호출합니다. 이는 람다와 완전히 동일하지만 트랜잭션 검사를 거치며 AutoRTFM ABI를 따릅니다.

현재는 Transact를 if 조건문 등 실패 컨텍스트에 들어가는 Verse에 응답하여 호출합니다. 향후에는 Verse 실행에 들어가기 전에 Transact를 호출할 것입니다.

런타임은 다음과 같은 상황을 처리해야 하며 이를 처리하기 위한 API를 제공해야 합니다.
  • Transact 또는 일시적으로 호출하는 함수로 전달된 람다에 클론이 없는 경우. 이는 AutoRTFM으로 컴파일되지 않은 모듈에 속한 함수 또는 시스템 라이브러리 함수에서 발생합니다.
  • 트랜잭션화된 코드가 트랜잭션에 안전하지 않은 뭔가를 수행하려는 경우(예: 애터믹(Atomic)을 사용하여 물리 커맨드 버퍼에 잠금 없 명령 추가). AutoRTFM은 트랜잭션의 수동 제어를 위해 여러 API를 제공합니다. 이 문제가 가장 흔히 발생하는 인스턴스는 malloc/free 및 관련 함수(커스텀 할당자 등)입니다.

AutoRTFM이 클론된 함수 탐색을 요청받았으나 클론이 없는 경우, AutoRTFM은 오류를 발생시키고 트랜잭션을 중단합니다. 이렇게 하면 트랜잭션에 안전하지 않은 코드를 실수로 호출할 수 없으므로 안전이 보장됩니다. 호출되는 대상이 트랜잭션화되어 클론이 존재하여 호출되거나, 호출되는 대상이 트랜잭션화되지 않아서 함수 누락때문에 중단됩니다. 따라서 트랜잭션에서 WriteFile 같은 것을 호출하면 기본적으로 오류가 발생합니다. 이는 바람직한 기본 동작입니다. WriteFile은 안정적으로 실행 취소될 수 없기 때문입니다. 설령 나중에 쓰기를 실행 취소한다고 해도 현재 프로세스나 다른 프로세스의 어떤 스레드에서 쓰기 완료된 상태에 복구 불가능한 무언가를 수행할 수도 있습니다. 이로 인해 기존 C++ 프로그램을 AutoRTFM으로 컴파일해서 Transact를 시도하는 것이 가능해집니다. 올바르게 작동하거나, 아니면 트랜잭션에 안전하지 않은 함수를 호출했다는 오류와 함께 해당 함수와 CallSite를 찾는 방법에 대한 메시지가 표시됩니다.

제대로 하기 가장 어려운 부분은 malloc 및 관련 함수입니다. AutoRTFM은 malloc의 구현을 트랜잭션화하지 않는 접근법을 취하지만, 호출자에게 malloc 호출을 AutoRTFM API로 래핑하도록 강제합니다. 이는 실제 malloc 구현을 둘러싸는 파사드 API가 이미 있는 언리얼 엔진 같은 프로그램에서 특히 간단합니다. 코드를 직접 살펴보면 가장 이해하기 좋습니다. AutoRTFM 이전 FMemory::Malloc 은 다음과 같았습니다.
 
void* FMemory::Malloc(SIZE_T Count, uint32 Alignment)
{
    
return GMalloc->Malloc(Count, Alignment);
}


중요한 부분만 보여드리기 위해 세부적인 내용은 상당 부분 생략했습니다.

AutoRTFM을 사용할 때는 다음과 같이 합니다.
 
void* FMemory::Malloc(SIZE_T Count, uint32 Alignment)
{
    
void* Ptr;
  
  UE_AUTORTFM_OPEN(
    {
        Ptr =
GMalloc->Malloc(Count, Alignment);
    });
    AutoRTFM::
OnAbort([Ptr]
    {
        
Free(Ptr);
    });
    
return AutoRTFM::DidAllocate(Ptr, Count);
}


어떻게 되는지 단계별로 살펴보겠습니다.
  1. UE_AUTORTFM_OPEN을 사용하여 람다로 변환된 괄호 블록이 트랜잭션 검사 없이 실행되어야 함을 나타냅니다. 이는 런타임 및 컴파일러에서 간단하게 구현할 수 있습니다. 람다의 클론이 아닌 원본 버전을 호출해야 한다는 뜻입니다. 즉, GMalloc->Malloc이 트랜잭션 내에서 실행되지만 트랜잭션 검사는 수행되지 않습니다. 이를 '열린 실행'이라고 합니다. 그 반대인 닫힌 실행은 트랜잭션화된 클론을 실행하는 것을 뜻합니다. 따라서 이 UE_AUTORTFM_OPEN의 반환 이후 malloc 호출이 실행되어 롤백 등록 없이 즉시 완료됩니다. 이렇게 하면 malloc이 이미 스레드 세이프이므로 트랜잭션 시맨틱을 유지합니다. 따라서 malloc 처리된 오브젝트에 대한 포인터가 트랜잭션에서 나오기 전까지는 이 스레드가 malloc을 수행했다는 사실을 다른 스레드에서 파악할 수 없습니다. 포인터는 커밋 전까지 트랜잭션에서 유출되지 않습니다. Ptr 이 저장될 수 있는 위치는 다른 신규 할당된 메모리 또는 AutoRTFM이 쓰기 로깅을 통해 트랜잭션화할 메모리뿐이기 때문입니다.
  2. 다음으로 AutoRTFM::OnAbort을 사용하여 중단 시 핸들러를 등록하고 오브젝트 메모리를 해제합니다. 트랜잭션이 커밋되면 이 핸들러가 실행되지 않으므로 malloc 처리된 오브젝트가 유지됩니다. 그러나 트랜잭션이 중단되면 오브젝트가 해제됩니다. 이는 중단으로 인한 메모리 누수를 방지합니다.
  3. 마지막으로 AutoRTFM에 이 메모리 슬랩이 새로 할당됐음을 알립니다. 그러면 런타임이 이 메모리에 쓰기를 로깅하지 않아도 됩니다. 쓰기를 로깅하지 않으면 AutoRTFM이 이미 해제한 오브젝트의 콘텐츠를 이전 상태로 덮어쓸까 봐 걱정할 필요가 없으므로 OnAbort 실행이 쉬워집니다. 새로 할당된 메모리에 쓰기를 로깅하지 않으면 최적화에도 유용합니다.

이 코드가 트랜잭션 외부에서 실행될 경우, 즉 이 모든 코드가 오픈에서 호출되면 다음과 같이 됩니다.
  • UE_AUTORTFM_OPEN은 코드 블록만 실행합니다.
  • AutoRTFM::OnAbort는 동작하지 않습니다(no-op). 전달된 람다는 무시됩니다.
  • AutoRTFM::DidAllocate는 동작하지 않습니다(no-op).

다음으로 해제 시 어떻게 되는지 보겠습니다. 이번에도 이번 주제와 무관한 세부 사항은 생략했습니다.

void FMemory::Free(void* Original)
{
  
  UE_AUTORTFM_ONCOMMIT(
    {
        
GMalloc->Free(Original);
    });
}

 
UE_AUTORTFM_ONCOMMIT 함수는 다음과 같이 동작합니다.
  • 트랜잭션화된 코드: 커밋 시 핸들러를 등록하여 오픈 시 주어진 람다를 호출할 수 있도록 합니다. 따라서 트랜잭션이 커밋될 때 오브젝트가 해제됩니다. 즉시 해제되지는 않습니다. 트랜잭션이 중단되어도 오브젝트가 해제되지 않습니다.
  • 오픈 코드: 람다가 즉시 실행되므로 오브젝트가 즉시 해제됩니다.

이런 검사 방식은 모든 메모리 할당자에 적용됩니다. malloc은 즉시 동작하지만 중단 시 메모리 해제하며, free는 커밋 시 해제합니다. AutoRTFM 런타임은 함수 탐색 및 컴파일러 수정을 통해 malloc/free/new/delete용 자체 핸들러를 설치하므로, 시스템 할당자를 사용할 때도 올바른 동작을 얻을 수 있습니다. 언리얼 엔진 GC도 유사하게 작동하지만 free 함수가 없으며 레벨 틱의 종점, 즉 트랜잭션 외부에서 증분 GC를 실행합니다.

코드가 애터믹, 잠금, 시스템 호출 또는 언리얼 엔진 코드베이스에서 트랜잭션화하지 않기로 선택한 부분(현재 렌더링, 물리 엔진 등)에 대해 호출을 사용하지 않는 한, 컴파일러, 런타임, Open/OnCommit/OnAbort API를 사용한 malloc/GC 검사의 조합은 트랜잭션에서 C++ 코드를 실행하는 데 필요한 모든 것을 제공합니다. 이렇게 하여 포트나이트 전체에 총 94번의 Open API 사용이 있었습니다. 이러한 사용에는 알고리즘이나 데이터 구조를 바꾸지 않고 Open 및 관련 API를 활용하여 기존 코드를 적당히 감싸는 것을 포함합니다.

Verse 에코시스템 영향

AutoRTFM은 더 많은 Verse 시맨틱을 UEFN에 노출하기 위한 첫 단계입니다. AutoRTFM은 다음과 같이 활용할 수 있을 것입니다.
  • 오류가 발생한 이후에도 게임이 계속 실행되도록 더 분명한 런타임 오류 동작을 구현합니다. AutoRTFM은 C++ 코드 심층부에서 런타임 오류가 탐지되더라도 미리 알고 있던 상태로 복원하도록 지원합니다.
  • <no_rollback> 이펙트를 제거하면 if for 구문으로부터 더 많은 코드를 호출할 수 있습니다. 이 이펙트가 발생하여 많은 함수 시그너처를 오염시키는 이유는 대부분의 C++ 코드가 트랜잭션화될 수 없기 때문입니다. AutoRTFM이 그 문제를 해결합니다.
  • 글로벌 트랜잭션 시맨틱을 노출하면 Verse 코드 및 글로벌 변수로 데이터베이스 액세스를 작성할 수 있습니다. AutoRTFM을 활용하면 Verse 코드의 추측 실행이 가능합니다. 이를 통해 원격 데이터 액세스의 실제적인 비용을 추측할 수 있으므로 Verse를 네트워크 전반에 걸친 트랙잭션에 적용하는 등 흥미로운 시도를 해볼 수 있습니다.

지금 언리얼 엔진을 다운로드하세요!

세계에서 가장 개방적이고 진보된 제작 툴을 받아보세요.
모든 기능과 풀 소스 코드 액세스를 제공하는 언리얼 엔진은 제작에 바로 사용할 수 있습니다.