2024년 Box64와 RISC-V: RISC-V에서 Witcher 3 실행 조건
(box86.org)- Box64의 RV64 DynaRec는 1년 전 간단한 네이티브 Linux 게임을 돌리던 수준에서, RISC-V PC에서 The Witcher 3를 실행하는 단계까지 발전함
- 진전의 계기는 AMD 그래픽 카드 사용이 가능해진 점으로, OpenGL 제약이 줄어들며 더 많은 x86 프로그램을 테스트하고 버그를 고칠 수 있게 됨
- RV64 백엔드는 ARM64 백엔드보다 구현된 x86 명령어 수가 적고, AVX 명령어도 아직 RISC-V 쪽에 남은 과제가 큼
- RISC-V는 비트 범위 추출·삽입과 16바이트 원자적 명령어가 부족해, x86 에뮬레이션에서 AArch64나 LoongArch64보다 번역 비용이 커짐
- The Witcher 3는 box64로 실제 실행되며, 게임 내 최대 15fps, 메인 메뉴에서는 풀스피드에 도달함
RISC-V에서 The Witcher 3가 실행되기까지
- 1년 전 RV64 DynaRec는 Stardew Valley, World of Goo 같은 비교적 실행이 쉬운 네이티브 Linux 게임만 돌릴 수 있었음
- 당시 병목은 크게 두 가지였음
- RISC-V 백엔드에 x86_64 명령어를 빠르게 추가하는 과정에서 DynaRec 버그가 많이 남아 있었음
- VisionFive 2와 LicheePi 4A의 IMG 내장 GPU는 OpenGL ES만 지원하고 OpenGL을 지원하지 않았음
- gl4es로 일부 OpenGL 지원을 얻어 Stardew Valley 같은 게임은 실행할 수 있었지만, 더 무거운 Linux 게임과 일반적인 Windows 게임에는 충분하지 않았음
- Sophgo의 Milk-V Pioneer는 64코어 RISC-V PC이며, 그래픽 카드를 꽂을 수 있는 PCIe 슬롯을 제공함
- 또 다른 기여자인 xctan은 VisionFive 2에 M.2 인터페이스를 통해 AMD 그래픽 카드를 “연결”하는 방법을 찾음
- AMD 그래픽 카드를 사용할 수 있게 되면서 테스트 가능한 x86 프로그램 범위가 넓어졌고, RV64 DynaRec 버그 수정과 x86 명령어 추가가 대량으로 진행됨
- 그 결과 The Witcher 3를 처음 실행했을 때 바로 동작함
RV64 DynaRec의 현재 상태
- x86 명령어 집합은 매우 크며, 백엔드별 구현 규모에도 차이가 있음
- ARM64 백엔드는 전체적으로 1,600개 이상의 x86 명령어를 구현함
- RV64 백엔드는 약 1,000개의 x86 명령어를 구현함
- 이 중 300개 이상은 새로 지원된 AVX 명령어이며, RISC-V에는 아직 전혀 구현되지 않은 상태임
- SSE 명령어 구현에서도 RISC-V는 성능상 불리함
- RV64 백엔드는 SSE 명령어를 스칼라 명령어로 구현함
- AArch64는 Neon 확장, LoongArch64는 LSX 확장을 사용함
- 이 차이 때문에 다른 두 백엔드보다 성능이 상당히 낮음
- RISC-V에는 벡터 확장인 RVV가 있음
- Milk-V Pioneer는 RVV 0.7.1 변형인 xtheadvector 확장을 지원함
- SpacemiT K1/M1 SoC는 비준된 RVV 1.0을 지원함
- 이 SoC를 탑재한 Banana Pi F3와 Milk-V Jupiter는 구매 가능함
- box64에는 최근 기본적인 RVV 지원과 몇 가지 일반적인 SSE 명령어 구현이 추가됨
- RVV 작업은 아직 매우 초기 단계라 현재 성능 개선에는 도움이 되지 않음
x86 에뮬레이션에 특히 부족한 RISC-V 명령어
- x86 에뮬레이션 관점에서 RISC-V는 지원하는 3개 아키텍처 중 표현력이 가장 낮은 편임
- AArch64와 LoongArch64에 비해 편리한 명령어가 부족해, 같은 동작을 에뮬레이션할 때 더 많은 명령어가 필요함
- 특히 중요한 두 가지 기능이 없음
- 한 레지스터에서 특정 비트 범위를 골라 다른 레지스터로 가져오는 기능
- 한 레지스터의 일부 비트를 다른 레지스터의 특정 범위에 삽입하는 기능
- LoongArch64와 AArch64에는 이에 해당하는 명령어가 있음
- LoongArch64는
BSTRPICK.D와BSTRINS.D를 사용함 - ARM64는
UBFX와BFIopcode를 사용함
- LoongArch64는
- RISC-V에는 공식 확장이나 벤더 확장 모두에서 이에 대응하는 명령어가 없음
ADD AH, BL 예시가 보여주는 번역 비용
- x86 ISA는 변경되지 않은 비트를 보존하는 경향이 있어, 부분 레지스터 조작이 중요함
ADD AH, BL의 경우 box64는 다음 작업을 해야 함RBX의 최하위 바이트를 추출함RAX의 두 번째 최하위 바이트에 더함- 결과를
RAX의 두 번째 최하위 바이트에 다시 삽입함 RAX의 나머지 바이트는 그대로 유지함
- LoongArch64에서는
BSTRPICK.D,ADD,BSTRINS.D를 이용해 간단하고 직관적으로 구현할 수 있음 - RISC-V에서는 같은 동작에 시프트, 마스크,
AND,OR등을 조합해야 해 10개 명령어가 필요함 - 이런 사례는 고립된 예가 아니며, x86에는 비슷한 형태의 명령어가 많아 RISC-V 구현이 더 번거로움
16바이트 원자적 명령어의 제약
- x86에는 lock-free 원자적 연산을 위한 LOCK 접두 명령어가 있음
- box64는 주로 LR/SC 시퀀스로 이를 에뮬레이션함
- LR/SC는 Load-Reserved / Store-Conditionally의 약자임
- 예를 들어
LOCK ADD [RAX], RCX는LR.D,ADD,SC.D, 조건 분기 형태로 생성됨
RAX주소가 정렬되지 않은 경우 더 복잡해지지만, 일반적으로 이 방식은 잘 동작함- 문제는
LOCK CMPXCHG16B임- 이 명령어는
RDX:RAX를 메모리의 16바이트와 비교함 - 조건에 따라
RCX:RBX를 해당 메모리 주소로 교환함
- 이 명령어는
- AArch64와 LoongArch64에는 구현에 사용할 수 있는 일부 16바이트 원자적 명령어가 있음
- RISC-V에는 이에 대응하는 명령어가 없어, 다른 아키텍처만큼 완전하게 구현할 수 없음
- Unity 게임을 포함해 많은 프로그램이
LOCK CMPXCHG16B를 사용함
실제 실행 결과
- 제약이 남아 있음에도 The Witcher 3는 box64로 RISC-V에서 실행됨
- 성능은 게임 내 최대 15fps까지 도달함
- 메인 메뉴에서는 풀스피드로 동작함
- AAA 게임 실행을 목적으로 설계되지 않은 머신에서 나온 결과로는 나쁘지 않은 수준임
댓글과 토론
Hacker News 의견들
-
칩 쪽 일을 하지 않는 입장에서 궁금한데, RISC-V 대상 소프트웨어를 만들 때 소프트웨어 엔지니어가 다르게 해야 하는 게 뭔지 궁금함
실행 파일 크기가 커져서 캐시 지역성을 공격적으로 최적화해야 하나 싶고, 게임이나 웹서버처럼 CISC/RISC 중 어느 쪽에 더 잘 맞는 소프트웨어 유형이 있는지도 궁금함- 압축 명령 확장이 있는 RISC-V는 평균적으로 x86-64나 ARM보다 코드 크기가 더 작게 나오는 편임
소프트웨어 접근 방식에서 본질적으로 바꿀 건 많지 않고, x86-64 대비 가장 큰 차이는 레지스터가 32개라 스택으로 밀려나기 전까지 중간값을 더 많이 들고 있을 수 있다는 점인데, ARM도 32개라 비슷함. 보통은 미세 최적화를 하지 않는 한 크게 신경 쓸 일은 없음
더 세부적으로는 벡터 확장(V/RVV)이 기본 rv64gc ISA에 없어서 대상에 따라 SIMD 최적화를 못 받을 수 있고, popcount와 선행/후행 0 개수 세기도 기본 rv64gc에는 없으며 Zbb가 필요함. 또a ? b : c같은 분기 없는 선택도 기본 rv64gc에서는 4~5개 명령, Zicond로 3개 명령이 필요하지만 x86-64와 aarch64에서는 1개로 가능함
RISC-V 프로파일이 앞의 두 문제는 어느 정도 해결함. 예를 들어 Android는 RVV, Zbb, Zicond 등을 요구하는 rva23을 요구함. 하지만 리눅스 배포판이 rva20/rv64gc를 목표로 잡으면, 동적 디스패치를 넣지 않은 사전 컴파일 코드에서는 그런 확장을 사실상 오래 못 쓰게 됨. x86-64에도 비슷한 문제가 있지만 ARM은 확장이 훨씬 적어 덜한 편이고, SVE가 가장 큰 예외인데 아직 널리 지원되지는 않음 - 대부분은 아무것도 다르게 할 필요 없음. C 같은 고수준 언어로 올바르게 작성된 코드는 동일하게 동작해야 함
가장 큰 차이는 약한 메모리 모델인데, ARM 같은 대부분의 비 x86 아키텍처에도 있는 특성이고 애초에 코드가 강한 메모리 모델에 의존하면 안 됨
실행 코드 밀도는 역사적인 이유로 x86이 그리 좋지 않아서 실행 파일 크기가 생각만큼 늘지는 않음. 압축 명령 확장이 있는 RISC-V와 Thumb 확장이 있는 32비트 ARM은 꽤 조밀함
중요한 건 CISC 대 RISC가 아니라 벡터 명령과 암호화 확장의 존재와 품질임. 비디오 인코딩/디코딩은 좋은 성능을 위해 벡터 명령에 크게 의존하고, 전체 디스크 암호화나 해싱은 AES, SHA256 같은 특정 알고리즘을 가속하는 전용 명령의 도움을 받을 수 있음 - 어떤 명령어 집합이든 거의 모든 종류의 작업 부하에 비슷하게 잘 맞아야 함. 어셈블리 프로그래밍을 한다면 차이가 나겠지만 Python이나 Unity로 만든다면 거의 문제 되지 않음
핵심은 ARM 특허에서 자유로워지고, 배운 교훈을 바탕으로 새 출발을 하는 쪽에 가까움
- 압축 명령 확장이 있는 RISC-V는 평균적으로 x86-64나 ARM보다 코드 크기가 더 작게 나오는 편임
-
유명한 러시아 사람이 Elbrus 8S에서 Atomic Heart를 돌린 게 떠오름
Elbrus에는 네이티브 번역기가 있고, 알기로는 꽤 괜찮음. Atomic Heart는 15~25fps 정도로 어느 정도 플레이 가능했음- 이 사람임: https://www.youtube.com/watch?v=-0t-5NWk_1o
- Elbrus가 RISC-V였거나 지금도 그런 건가?
-
글이 “기본” 설명은 좀 부족함. Wine 포트 같은 걸 써서 실행한 줄 알았는데, 실제로는 RISC-V 칩 위에 x86_64 ISA를 어떤 방식으로 구현한 것처럼 보임
그 부분이 어떻게 되는지 더 설명해줄 수 있는 사람이 있으면 좋겠음- 기본 설명은 여기 있음: https://box86.org/
에뮬레이터이긴 하지만 libc, libm, SDL, OpenGL 같은 일부 “시스템” 라이브러리는 네이티브 버전을 쓰기 때문에 대부분의 애플리케이션과 통합해 쓰기 쉽고, 경우에 따라 성능도 놀랄 만큼 높을 수 있음. Wine도 네이티브로 컴파일해서 실행 가능함
- 기본 설명은 여기 있음: https://box86.org/
-
놀라운 결과임. 엄청난 작업량이고, 일부 경우에는 RISC-V의 한계에 닿아 있는 것처럼 보임
비트 모으기/흩뿌리기 명령은 확장으로 들어가야 할 듯함- CPU보다 그래픽 코어에 더 많이 의존하는 게임으로 테스트한 결과도 보고 싶음. Divinity 2 같은 게 적당할지도 모름
-
x86 에뮬레이션 맥락에서 지원하는 3개 아키텍처 중 RISC-V가 가장 표현력이 낮다는 부분이 흥미로움
컴퓨터 과학사 수업에서는 RISC를 축소 명령어 집합 컴퓨터라고 배웠는데, 요즘 RISC-V 프로파일 제안이나 글을 보면 “기능 동등성을 위해 명령 몇 개만 더 필요하다”는 식이 많음. RISC-V가 많은 사람에게 다른 플랫폼의 편리한 대안이라는 건 이해하지만, 이게 RISC의 꿈이 죽었다는 뜻인지도 궁금함- 실제 RISC는 “절대적으로 최소한의 명령어 집합”이라기보다, “어셈블리 프로그래머 편의용 영리한 기능을 넣지 말고 가능하면 프런트엔드 실리콘 대신 컴파일러에 맡기자”에 가까운 것으로 이해함
RISC-V 사양을 읽었던 기억으로는, 흔한 명령 시퀀스는 프런트엔드에서 융합할 수 있으니 “콤보” 명령을 추가하지 않는 데 꽤 엄격했음
x86/ARM 대비 RISC-V의 부족함은 RISC 근본주의 때문이라기보다, 사양이 아주 기본적인 임베디드 칩부터 시작해 시간이 지나며 애플리케이션 CPU용 확장을 추가해온 데서 온 듯함. 기본 RV32I에는 정수 곱셈조차 없음. 안타깝게도 비트 조작과 SIMD/벡터 확장의 논쟁을 끝내는 데 너무 오래 걸렸고, 그 결과 지금 이야기하는 기능 공백이 생김 - 학생이 한 학기 수업 안에 구현할 수 있는 명령어 집합을 만들려면 모든 명령이 입력 2개와 출력 1개를 갖게 하는 식의 단순화가 필요함. 프로세서 설계를 실험하는 연구자들에게도 훨씬 쉬워짐
다만 그만큼 고성능을 위해 편리한 명령 일부는 제외됨
단순한 파이프라인은 고성능 설계를 하는 팀의 엔지니어링 자원을 덜 먹어서 최적화에 더 많은 시간을 쓸 수 있게 해주는 장점도 있음
RISC는 대체로 단순화 철학이지만, 그 정도는 다양함. MIPS는 RISC-V만큼 단순화되어 있지만 ARM과 POWER는 더 절충적이고, 고성능 영역에서 x86과 맞붙는 데 큰 문제가 없어 보임
프로세서의 시장은 애플리케이션 실행 말고도 임베디드, 가속기 등 여러 틈새가 있음. 애플리케이션 코어라는 특정 틈새에서는 RISC-V에 좀 비관적이지만, 더 넓게 보면 잠재력이 크고 몇몇 상업적 틈새를 지배할 가능성도 있으며 교육과 연구 도구로는 훌륭함 - RISC의 꿈은 대부분의 소프트웨어가 직접 어셈블리가 아니라 컴파일러로 작성되므로 CPU 설계 단순화를 하자는 것이었음
고전적인 RISC의 특징은 대부분의 데이터 조작 명령이 레지스터에서만 동작하고, 메모리 명령은 대체로 레지스터로의 load/store뿐이며, 그래서 많은 레지스터가 필요하다는 점임. 매개변수 전달을 위해 스택을 직접 조작해야 하니 스택도 직접 만들고, CALL/JSR 명령 없이 명령 포인터 레지스터에 직접 load/store하는 기본 명령들로 구현함. 명령 인코딩은 예측 가능하고 모든 명령 크기가 같음. 여러 RISC 아키텍처에는 항상 0을 읽고 쓸 수 없는 레지스터도 있었고, 값을 0으로 설정할 때 사용했음
이 방식은 통했지만, 이후 비순차 실행과 SIMD가 중요성을 낮췄음. 원시 명령 스트림은 원하는 결과로 가는 경로의 선언에 가깝고, CPU가 실제로 그대로 실행한다는 뜻은 아님. 그 뒤에는 추측 실행, 분기 예측, 레지스터 이름 변경이 있음. SIMD는 넓은 레지스터 공간과 그 안의 모든 값에 작동하는 명령에 가까움. 결국 비순차 실행과 SIMD가 주도권을 가져감 - RISC의 꿈이라는 게 있나 싶음. 효율의 꿈, 성능의 꿈, 비용의 꿈은 있고, 비용·성능·효율 대비 낮은 복잡도의 꿈도 있지만, RISC 자체를 비용·성능·효율·단순성보다 더 중요하게 여기는 사람이 있나?
- 이 맥락에서는 x86_64용으로 이미 컴파일된 코드를 RISC-V에서 실행하려는 것임. “기능 동등성을 위해 명령 몇 개만 더 필요하다”는 요구는 이미 그런 추가 명령을 가진 아키텍처용으로 컴파일된 코드를 실행하려다 보니 나옴
이론적으로 원본 소스 코드를 RISC용으로 컴파일하면 완전히 다른 바이너리가 나오고, 그 특정 명령들이 필요하지 않을 수 있음
현실적으로는 누군가 이 게임들을 실제로 RISC-V용으로 컴파일할 것 같지는 않음
- 실제 RISC는 “절대적으로 최소한의 명령어 집합”이라기보다, “어셈블리 프로그래머 편의용 영리한 기능을 넣지 말고 가능하면 프런트엔드 실리콘 대신 컴파일러에 맡기자”에 가까운 것으로 이해함
-
스크린샷에는 램이 31GB로 나오는데, 언급된 개발 보드의 최대 사양보다 확실히 큼. 여기서는 다른 걸 쓰는 건가?
- 더 오래된 보드인 Pioneer임
요즘이라면 RVA22와 RVV 1.0을 구현한 더 빠른 코어 여러 개를 갖춘 최근 선택지 중 하나를 쓰는 편이 더 나음 - https://milkv.io/pioneer
- milk-v pioneer는 128GB RAM을 탑재함
- 더 오래된 보드인 Pioneer임
-
이게 86Box인가? Amstrad PC1512를 샀던 시절을 다시 떠올리는 게 재미있었음
500MB 하드 카드 2개와 128KB 메모리 확장을 추가해 640KB로 만들었더니 훨씬 재미있어졌음. 처음에는 360KB 플로피 2개뿐이었고 몇 년 뒤에 32MB 하드 카드를 추가했음. Borland TurboPascal과 Zortech C도 있었음. 즐거운 시절이었음- 아님, 이건 Box64고 완전히 다른 프로젝트임
그래도 Amstrad PC1512 쓰던 시절은 기억남
- 아님, 이건 Box64고 완전히 다른 프로젝트임
-
언젠가 큰 RISC-V CPU 몇 개와, 작은 RISC-V CPU 다발로 구현한 “GPU”를 함께 싣는 시스템이 나올지 궁금함
적절한 벡터 기능을 붙인 형태일 텐데, 곁가지 질문으로 packed SIMD 대신 고전적 벡터 방식이 GPU에서 유용할 수 있는지도 궁금함 -
기술적으로 인상적인 Witcher 3 성과로는 Switch 포트도 있었고, 정말 잘 돌아갔음
최적화로 얼마나 많은 걸 해낼 수 있는지, 그리고 PC에서 나쁜 최적화 때문에 얼마나 많은 자원이 낭비되는지 보여줌- 훨씬 낮은 품질의 텍스처와 3D 모델을 써서 에셋용 RAM을 훨씬 적게 쓴 것도 큼
사과 대 사과 비교가 아니고, 화면에 표시되는 범위가 크게 다른데 PC의 나쁜 최적화를 단정하기는 어려움 - 렌더링 해상도를 720p, 휴대 모드에서는 540p로 낮추고, 설정을 최소보다 더 낮게 두며, 약 30fps를 괜찮다고 부를 수 있다면 최소 사양 PC에서도 Witcher 3를 비슷하게 돌릴 수 있음
- 훨씬 낮은 품질의 텍스처와 3D 모델을 써서 에셋용 RAM을 훨씬 적게 쓴 것도 큼
-
이런 ISA 수준 피드백이 RVI 쪽 사람들에게 전달되면 좋겠음
- 스칼라 효율 SIG에서는 이미 비트필드 삽입/추출 명령을 논의하고 있음
어제 확인해 보니 [1], 글의 예제는 이미 RISC-V 명령 4개로 할 수 있음. 다만 떠올리기가 좀 까다로움
# a0 = rax, a1 = rbx
slli t0, a1, 64-8
rori a0, a0, 16
add a0, a0, t0
rori a0, a0, 64-16
[1] https://www.reddit.com/r/RISCV/comments/1f1mnxf/box64_and_ri... - 이 중 새로운 건 하나도 없음
사실 비트필드 추출이 빠진 건 너무 명백한 실수라서, RISC-V ISA가 얼마나 어처구니없는지 보여주는 내가 가장 좋아하는 예시임. 두 번째는 제정신인 주소 지정 모드가 없다는 점임
더 나은 RISC-V 설계 중 일부는 실제로 이를 위한 커스텀 명령을 구현함. 예를 들어 Hazard3의 BEXTM이 있음: https://github.com/Wren6991/Hazard3/blob/stable/doc/hazard3....
- 스칼라 효율 SIG에서는 이미 비트필드 삽입/추출 명령을 논의하고 있음