Trawling

x86 에뮬레이션의 재앙 — ARM 위에서 x86-TSO 메모리 모델을 흉내 내는 문제

x86→ARM 에뮬레이터 FEX 팀이 x86의 강한 메모리 모델(x86-TSO)을 ARM의 약한 메모리 모델 위에서 재현할 때 겪는 성능 문제를 정리한 장문 기술 기고입니다. ARMv8.0의 load-acquire/store-release 방식은 큰 오버헤드를 만들고, LRCPC 확장과 Apple Silicon의 하드웨어 TSO 모드가 해법으로 제시됩니다. 비정렬 접근과 RMW 아토믹까지 다루며 실측 벤치마크 수치를 함께 제공합니다.

본문

FEX-Emu 팀이 자사 사이트 첫 장문 기고로, ARM에서 x86 코드를 에뮬레이션할 때 모든 애플리케이션에 영향을 미치는 근본 문제 — x86 Total Store Ordering(x86-TSO) 메모리 모델의 재현 — 을 다룹니다. 메모리 모델이란 로드와 스토어가 단일·멀티스레드 환경에서 서로 어떻게 보여야 하는지를 정하는 규칙 집합인데, ARM의 relaxed(weak) 모델과 x86의 TSO는 스펙트럼 양 끝에 위치합니다. x86에서는 스토어가 발생하면 모든 코어에 일관되게 보이는 것이 보장되어 프로그래머의 직관과 일치하지만, ARM은 캐시라인 무효화와 스누핑 비용을 아끼기 위해 기본 로드/스토어의 코어 간 가시성을 보장하지 않습니다. 글은 일관성(consistency)과 원자성(atomicity)이 별개 개념임을 전제로 논의를 전개합니다.

ARMv8.0-a의 원시적 해법과 그 비용

ARMv8.0-a에서 FEX가 택한 방법은 모든 x86 로드를 ARM의 load-acquire로, 모든 스토어를 store-release로 바꾸는 것입니다. 이는 RCsc(Release Consistency sequentially consistent) 모델을 이용해 x86보다 오히려 더 엄격한 의미를 강제하는 방식으로, 마이크로벤치마크에서 그 비용이 드러납니다. 다섯 개 CPU(AmpereOne, Cortex-X4, Cortex-X925, Oryon-3, Apple M1) 중 세 개에서 acquire-load 성능이 크게 저하됐고, AmpereOne은 release-store가 일반 스토어 대비 극단적으로 낮게 측정됐습니다. acquire 의미를 모든 로드에 강제하면 로드끼리 재정렬이 불가능해져 초당 수백만 개가 처리되는 상황에서 성능이 무너진다는 설명입니다.

LRCPC와 Apple의 하드웨어 TSO

ARMv8.3부터 필수가 된 FEAT_LRCPC는 RCpc(Release Consistency processor consistent) 모델을 추가하며, x86 에뮬레이션 요구사항을 겨냥해 설계된 확장입니다. 벤치마크에서 대부분 플랫폼의 LRCPC-load가 일반 로드와 동등한 성능을 보였고, FEX는 이 확장을 감지하면 acquire-load 사용을 중단합니다. 예외는 Apple M1으로, Apple은 CPU에 x86-TSO 모드를 하드웨어로 내장해 토글 시 일반 로드/스토어가 x86 의미를 따르게 했습니다. M1에서 LRCPC-load가 느린 이유는 Rosetta가 이 명령어를 쓰지 않아 acquire-load의 별칭일 뿐이기 때문입니다. FEX는 Asahi Linux에서 이 기능을 감지해 활성화하며, TSO 토글 자체의 비용보다 대부분의 접근이 빨라지는 이득이 크다고 평가합니다. 한편 ARM은 FEAT_LRCPC → LRCPC2(오프셋 immediate 추가) → LRCPC3(벡터·스택 접근 추가)까지 세 차례 확장을 보완했고, 팀은 하드웨어 TSO 토글이 근본 해법이라고 주장합니다.

비정렬 접근 — 진짜 골치거리

x86 애플리케이션은 정렬을 신경 쓰지 않아 캐시라인을 가로지르는 접근과 split-lock을 일으키며, Linux 커널조차 split-lock을 감지해 게임을 느리게 만들 정도입니다. 문제는 ARMv8.0의 acquire/release 명령어가 natural alignment를 요구해, 8바이트 접근이 8의 배수 오프셋에 있지 않으면 alignment fault가 발생한다는 점입니다. FEX는 JIT가 패치포인트(NOP)를 심어두고, fault 발생 시 코드를 일반 로드/스토어 + DMB(Data Memory Barrier)로 패치한 뒤 실행을 재개합니다. 실측 결과 Cortex-X4와 X925는 비정렬 시 DMB 페널티로 로드·스토어 모두 약 50~70% 성능 하락, Oryon-3은 정렬 LRCPC-load가 일반 로드와 동일하지만 비정렬 스토어가 약 43%까지 떨어졌습니다. 반면 하드웨어 TSO 모드를 쓴 M1은 비정렬에서도 스토어 약 5% 하락에 그쳐 사실상 무료였습니다. 다만 TSO 모드 자체는 일반 스토어 대비 76% 수준의 비용이 있습니다. ARM의 FEAT_LSE2는 정렬 요구를 16바이트 그래뉼 내로 완화하지만, x86 앱은 캐시라인 전체를 넘나들어 실질 개선이 미미하다고 지적합니다.

RMW 아토믹의 매핑과 남은 문제

x86은 비정렬 로드/스토어도 대개 원자적으로 완료되지만, ARM은 비정렬 접근의 단일 복사 원자성(single-copy atomicity)을 보장하지 않습니다. ARMv8.1-a부터는 x86의 LOCK 계열 명령어와 거의 1:1로 대응되는 아토믹 명령어(ldaddal, ldclral, casal, caspal 등 18개 매핑 표 제시)가 생겼으나, 글은 이 명령어들이 있어도 그대로 연결할 수 없는 문제가 남아 있음을 시사하며 후속 논의로 이어집니다.

Hacker News 반응

  • @dagmx — 참고로 Fex는 Apple의 Rosetta2나 Microsoft의 Prism처럼 x86를 ARM으로 번역하는 프레임워크입니다. Valve가 개발을 후원하고 있으며, 새로운 Steam Frame이 x86 게임을 지원하는 방식이기도 합니다. Crossover 베타에서도 Rosetta2를 대체하기 위해 포크로 사용되고 있습니다.
  • @MiroslavPokorny — Steam이 바이너리를 어떤 바이트코드로 컴파일하고 설치 시 트랜스파일하도록 요구하지 않는 이유는 무엇일까요? 그러면 새로 컴파일하는 것에는 에뮬레이터가 필요 없을 텐데요.
  • @matheusmoreira — 기존 게임들은 새 바이트코드 타깃으로 재컴파일되지 않을 것이고, 그 게임들이 전부 동작하길 원하기 때문입니다.
  • @rwmj — 잘은 모르지만, Valve가 스튜디오에 재컴파일이나 특별한 툴링을 요구하지 않고 기존 실행 파일을 그대로 패키징하는 방식이라고 생각합니다. Windows용 게임을 Linux에서 에뮬레이션하는 것과 같은 이유입니다.
  • @swiftcoder — 여전히 전체 백카탈로그를 지원해야 합니다. 수천 개의 기존 게임이 ARM으로 이식되길 기대하는 것은 합리적이지 않습니다.

원문: FEX-Emu / 번역·요약: Trawling

참고 자료

오류 제보