프로그래머 관점에서의 Surface Pro X 벤치마크

Windows on ARM


나는 Windows를 사랑한다. 정말로 사랑한다. 물론 Windows 3.1과 95에 대한 애착은 크지 않지만 NT커널 이후의 Windows는 정말 사랑한다. 또한 Windows 프로그래밍을 좋아한다. Visual Studio로 Windows프로그래밍을 하는 것은 즐겁다.
그래서 x86이외의 CPU에서 돌아가는 NT커널 기반의 Windows의 소식을 들었을때 무척 흥분했다. Visual Studio로 ARM디바이스에서 돌아가는 Windows 어플리케이션을 개발할 수 있다니!

물론 초기 Windows Phone 8과 Windows RT(Surface RT에서 돌아가는)에서 크게 실망했다. 당장 이 블로그만 검색해도 Windows Phone과 Surface RT에 대한 개발 스토리를 쉽게 찾을 수 있다(욕이 반 이상이다).
Windows 10 Moblie과 Windows 10으로 발전하면서 개발환경이 많이 좋아졌다. 그리고 내 불만도 줄긴 했지만 여전히 만족스럽진 않았다.
그 불만중 큰 부분이 Windows on ARM 디바이스를 위한 소프트웨어를 개발하기 위해선 반드시 UWP API를 사용해야 한다는 점이다. 현시점에선 UWP API는 분명히 많이 좋아졌지만 여전히 win32 API에 비해 기능이 제한적이다. win32로 개발했던 내 게임을 UWP로 포팅하는데 많은 시간을 소비했다. 대체 이런 짓을 왜 해야하는걸까? 라고 수도없이 생각했다.

그리고 몇 년전 MS는 ARM 디바이스에서 완전한 Window 10 OS가 돌아가는 Windows 10 on ARM 을 개발하고 있음을 발표했다. 이후로 삼성과 ASUS, HP 등 몇몇 회사에서 시험적인 Windows 10 on ARM 제품을 출시했다. Surface RT때부터 크게 아픔을 겪었던 나로선 매우 비관적인 시각을 가질수밖에 없었다. ARM에서 돌아가는 Windows 10을 개발하기 위해서 얼마나 많은 엔지니어들이 고생을 했는지는 안봐도 잘 안다. 그 분들을 존경한다. 하지만 현실적으로 보면 매우 비관적이다.
x86에뮬레이션은 불안정하고 느리다. ARM용 네이티브앱은 없다.
하지만 나는 소프트웨어 엔지니어로서 Windows 10 on ARM에 관심이 많다. Surface RT에서 크게 한방 얻어맞았고 Windows Phone에서 또 한방 맞았지만 그래도 관심이 많다. 최초 발표로부터 최근까지도 Windows 10 on ARM에 대해서 계속 관심을 가지고 있었다.

MS는 최근의  Surface이벤트에서 Surface Pro X를 발표했다. HP나 삼성의 제품이 이미 시장에 나와있으므로 제품 그 자체로는 크게 놀랄만한 사건은 아니었다. 다만 명백하게 손실을 안을게 뻔한 제품(나뿐만 아니라 리뷰어들 대부분이 생각하는것 같다)을 MS가 무려 Surface브랜드를 달고 출시했다는데 크게 놀랐다.

Surface 이벤트를 보면서 일반 소비자가 이 제품을 구입했을때는 크게 후회할거라고 확신했다. 지금도 그렇게 생각한다. 나 역시 소비자 입장으로는 Surface Pro 7을 구입해서 내 방 침대 머리맡에 있는 Surface Pro 2를 대체하고 싶었다. 정말 그럴 생각이었다.

그래도 궁금하긴 했다. 하드웨어의 명가 MS에서 Surface 이름을 달고 출시한 ARM디바이스, Surface Pro X의 성능은 어느 정도인가. x86에뮬레이션 성능 말고 ARM 네이티브 코드의 성능이 궁금했다. 확실히 몇 년 사이에 ARM계열 프로세서들의 성능은 놀랍게 향상됐다. MS는 Surface Pro X에 장착된 SQ1 프로세서(Surface Qualcomm이라고 한다)의 성능에 대해 대단한 자신감을 보였다(물론 난 믿지 않는다. 그렇게 자랑하던 Surface Book 1의 dGPU도 성능이 형편 없었거든.).

Surface Pro X의 실제 출시가 이루어진 후부터 유튜브와 리뷰 사이트에 올라온 Surface Pro X의 리뷰들을 열심히 살펴봤다. 그리고 크게 실망했다. 정작 내가 원했던 정보는 전혀 없었다.

비난하는 리뷰는 ‘느리고 배터리 소모가 빠르며 돌아가는 앱이 없다’는 얘기 뿐이다. x86에뮬레이션은 당연히 느리다. 당연히 배터리를 많이 소비한다.

극찬하는 리뷰는 ‘얇고, 이쁘고 가볍다’는 얘기 뿐이다. 바보냐? 디바이스는 악세서리가 아니라고. 비난하는 리뷰보다 이런 멍청한 리뷰에 더 화가 난다.

하여간 프로그래머 입장에서 궁금한건 ‘SQ1프로세서의 진짜 성능’, ‘Windows 10 on ARM의 안정성’, ‘네이티브 앱을 개발하기 위한 환경’이다.
몇 일간 리뷰들을 뒤져보다 결국 포기했다. 이런 멍청한 리뷰들에선 내가 원하는 정보는 얻을 수 없다.
그래서 그냥 내 돈 주고 Surface Pro X를 샀다.
협찬 따윈 없다! 내 돈주고 샀다!
테스트에 필요한 코드도 내가 직접 짠다!
이제 내가 궁금해하던 것들을 알 수 있다. 필요한 테스트는 내가 직접 진행한다.

테스트 목표 & 방향

나는 게임에 들어가는 소프트웨어를 만드는 프로그래머이므로…이 물건에서 게임을 잘 돌릴 수 있는지 여부가 제일 중요했다. 그렇지만 win32베이스에 ARM64 네이티브 앱으로 출시된 게임은 한 개도 없다. 있다해도 내가 맘대로 뜯어고칠수 없으니 어차피 의미는 없다.
그래서 내가 개발했던 게임과 개발중인 게임을 ARM64로 포팅하기로 했다.
2주 정도 테스트와 포팅을 병행했다.
ARM64를 지원하기 위해 x86/x64용 어셈블리어로 작성했던 코드들은 표준 C코드로 다시 짰다. 성능에 민감한 수학함수들의 경우 x86/x64에선 SSE/AVX를 사용하고 있다. 공정한 비교를 위해 이 함수들은 Neon(ARM64용 SIMD명령셋)용 코드를 추가로 작성했다.

테스트도 중요하지만 Surface Pro X를 손에 넣었기 때문에 당연히 내 게임은 Surface Pro X에서 ‘잘’ 돌아가야 한다. 내 소프트웨어 제품들의 타겟 디바이스가 하나 더 늘은 것이다. 내 게임은 Windows 10 on ARM에서 잘 돌아야 한다. 단순히 테스트만 한다면 좀더 빠르게 진행할 수 있었겠지만 게임이 잘 돌도록 코드를 계속 수정했기 때문에 예상보다 시간이 많이 걸렸다.
최종적으로 게임이 잘 돌아가게 됐다고 판단한 후에 실질적인 테스트를 진행했다.
산술 연산 테스트의 경우 게임에서 사용중인 코드를 그대로 사용했다.
내 게임과 툴에서 사용하는 기능이 얼마나 잘 돌아가는지를 테스트 했다.

그리고 미리 말해두지만 ARM vs x86의 성능 비교가 아니다. 테스트에 사용한 ARM 프로세서는 SQ1프로세서서 하나 뿐이다. x86 CPU는 모두 intel 제품이다.
그러니 정확히는
‘Qualcomm에서 만든 Microsft SQ1프로세서의 ARM64네이티브 성능 vs intel x86 프로세서의 성능 비교’
이다.

x86에뮬레이션에서의 성능도 측정했다. 3 – 8 배 정도는 느리니까 그냥 참고만 하기 바란다. 이 테스트의 목적은 SQ1프로세서에서의 ARM64 네이티브 성능을 알기 위함이다.

참고로 SQ1 프로세서에서 SSE 명령어셋도 에뮬레이션 가능하다. SSE4까지 에뮬레이션 가능하다. AVX는 불가능하다.

산술연산 테스트

싱글스레드로 테스트한다. 소요시간을 측정하지 않는다. 소모한 클럭을 측정한다. 클럭 주파수는 당연히 x86 cpu가 높기 때문에 소요시간을 측정하면 x86이 무조건 빠르게 나온다. 따라서 각각의 오퍼레이션이 얼만큼의 클럭을 소모하는지를 측정한다.

– big.LITTLE Architecture on SQ1 Processor
여기서 짚고 넘어가야하는 것이 있다. SQ1프로세서는 다른 ARM AP들과 마찬가지로 big.little아키텍처를 사용한다. 고성능의 4개의 Big Core와 저성능의 4개의 little Core로 구성돼 있다. big core는 Gold Core라 부르고 little core는 Silver Core라 부른다. 클럭만 다른게 아니고 명령어 효율 자체가 다르다.
little core가 0,1,2,3번, big core가 4,5,6,7번이다.8개 동시에 사용될 수 있다. 이 순서를 반대로 생각했기 때문에 처음에 잘못된 결과를 얻었다. 이후 아예 Gold core와 Silver Core를 명시적으로 지정해서 측정했다.

– SIMD(Single Instruction Multiple Data)
명시적으로 SIMD코드를 작성하지 않고 표준적인 C코드를 작성할 경우 컴파일러는 자동으로 SIMD처리를 해주지 못한다. 단 SIMD 명령어셋의 싱글 명령들과 SIMD용 레지스터는 사용한다. 기존의 FPU를 사용하는것보다 빠르다.
이 테스트에서 SIMD로 표기한 경우 명시적으로 SIMD코드를 작성한 경우이다.
No SIMD로 표시한 경우 표준적인 C코드로 작성했음을 의미한다. 그러나 컴파일러는 FPU대신 SIMD레지스터와 명령어를 사용하므로 그래도 FPU를 사용하는것보다 빠르다. 따라서 SIMD코드와 성능 차이가 거의 없거나 때때로 더 빠를 수 있다.

x86/x64용 SIMD코드는 SSE를, ARM64용 코드는 Neon을 사용했다.
x86은 AVX를 사용하면 성능을 더 올릴 수 있는 가능성이 있다. 한편 나는 Neon코드를 이번에 처음 작성해봤다. 내 코드는 충분히 효율적이지 못할수 있다. 따라서 최대 성능이 어느쪽이 높은지를 정확히 판단할 수 없다.
이 테스트는 현대 ARM64프로세서의 성능이 어느 수준까지 왔는지를 알기 위함이지 순위경쟁을 하려는게 아니다.

4칙연산 테스트
4칙연산 테스트다. 메모리 절약을 위해서 게임에선 3차원 벡터(float3)를 주로 사용한다. float3타입은 메모리에서 한번에 읽어들일수 없고 한번에 써넣을수 없다. 따라서 메모리에 읽고쓰는 동작에서 추가적인 비용이 발생한다. 이 테스트는 단순한 산술연산을 얼마나 빠르게 하는지 테스트가 목적이기 때문에 4차원(float4)벡터를 사용했다.
덧셈과 뺄셈은 기본적으로 같은 연산이므로 뺄셈은 따로 테스트하지 않았다.

arithmetic_test_add_mul_div

Add or Sub Or Mul or Div 테스트는 같은 오퍼레이션을 반복하는 대신, 전달된 벡터배열의 인덱스에 따라 4가지 연산으로 분기한다. 분기하는 상황에서 캐시미스를 유도하고 분기예측에 혼란을 주기 위함이다. 이 테스트에서는 intel x86 CPU가 확실히 우월함을 보이고 있다.

Vector 및 Matrix 연산 테스트
게임 프로그래밍에서 많이 사용하는 함수들이다. 내 게임에서 실제로 사용하는 함수들을 사용했다.
TransformVector_xxxx()함수들은 float3 스트림을 배열형태로 전달하기 때문에 함수호출 비용은 무시해도 좋다. 그러나 Matrix곱셈이나 Normalize()함수, CrossProduct(), DotProduct()등은 함수 한번에 한번의 오퍼레이션을 수행하므로 소모된 클럭에는 함수 호출 비용이 포함되어있다.


arithmetic_test_vec_matrix

산술연산 테스트 결론
결과만 놓고 보면 intel x86이 빠르다. 하지만 당초 예상과는 달리 크게 성능이 뒤지지는 않는다. 또한 일부 ARM64가 더 빠른 경우도 있다. SIMD성능으로 보면 차이가 더 작다.
전체적으로 x86계열이 더 좋은 성능을 보인다. 하지만 이 정도의 성능 차이는 게임에서 크게 체감할 정도는 아니라고 생각한다.
즉 게임 돌리기엔 충분한 성능이다.

memcpy 테스트

VC++의 memcpy()를 사용해서 1GiB의 메모리를 복사한다. 여기서는 멀티 스레드를 사용한다.
1GiB의 메모리를 활성화된 스레드 개수로 나눠서 동시에 모든 스레드가 카피를 진행한다.
스레드간 동기화는 필요하지 않으므로 lock은 사용하지 않는다.

memcpy

memcpy 테스트 결론
측정된 bandwidth는 SQ1프로세서의 공식적인 bandwidth와 intel CPU들의 공식적인 bandwidth 수치에는 도달하지 못한다.
하지만 memcpy()는 아주 많이 사용되는 메모리 카피 함수이고 대부분의 경우 높은 성능을 보장하기 때문에 memcpy()비교는 충분히 의미가 있다.
SQ1프로세서의 메모리 전송 능력은 매우 훌륭하다. 4채널 버스를 사용하는(명백하게 공식 스펙으로 대역폭이 높은) xeon cpu를 제외하고 2채널 버스를 사용하는 다른 x86 cpu들보다 성능이 높다.

Multithreading 응답성 테스트

context switching 비용, 스레드간 동기화에 필요한 lock의 비용이 얼마나 높은지를 측정하는 테스트이다.
처음 내 게임과 툴 코드들을 ARM64로 포팅했을때 성능이 만족스럽지 못했다. 멀티스레드로 돌아가는 툴들에서 특히 성능이 나빴으므로 ‘ARM64에서 멀티스레드 응답성에 문제가 있지 않을까’ 라고 추측했다.그래서 이 테스트를 진행했다.
기본적으로 이 테스트는 4바이트 변수가 특정 값에 도달할때까지 여러개의 스레드가 경쟁적으로 값을 증가시킨다. 다만 각 테스트 별로 동기화를 위한 방법이 다르다.


multi-thread_responsibility

atomic increment테스트는 lock객체를 사용하지 않고 4바이트 변수값을 증가시키는 테스트다. x86이라면 어셈블리어로 lock inc dword ptr[mem] 한 줄로 표현된다. ARM CPU에 패널티가 있는지가 궁금했다. 1,4,8 스레드에서 SQ1프로세서와 intel x86은 거의 동일한 성능을 보여준다. 2스레드에선 SQ1프로세서의 성능이 더 뛰어난것으로 측정된다.

Critical Section 테스트는 critical section으로 락을 걸고 변수값을 증가시킨다. critical section으로 감싼 코드 블럭에 다른 스레드가 먼저 진입한 경우 늦게 진입하는 스레드는 먼저 진입한 스레드가 critical seiction에서 나갈때까지 스케쥴링 되지 않는다. 즉 wait상태가 된다. 다시 스케쥴링 될때는 지연시간이 걸린다. 스레드 개수가 많아지면 더욱 성능이 저하된다. 매우 나쁜 상황이다. 이 상황에서 각각의 CPU들이 얼마나 성능이 저하되는지를 알고 싶었다.
이런 상황에서 SQ1프로세서의 성능이 크게 저하되는것으로 보인다. 만약 critical section이나 비슷한 방식의 동기화를 많이 쓰는 어플리케이션이라면 성능 저하의 소지가 있다고 본다.

SRWLock은 최근 많이 쓰이는 경량 lock이다. 내부적으로 spin lock으로 구현되어있다. 매우 효율적인 코드로 작성되어있다. spin lock만 사용하면 먼저 진입한 스레드가 장시간 빠져나오지 않을 경우 진입을 기다리는 스레드가 바쁘게 작동하기 때문에 전체적으로 시스템 성능을 떨어뜨릴 수 있다. SRWLock은 이에 대한 대비가 되어있다. 진입을 대기하는 스레드는 일정 회수 이상 진입에 실패하면 Wait상태가 되므로 성능저하를 피할 수 있다.
x86 cpu에서 월등하게 응답성이 좋다.

spin lock테스트는 내가 직접 만들어서 사용하는 spin lock을 이용해서 동기화한다. SRWLock과 비슷하지만 먼저 진입한 스레드가 장시간 빠져나오지 않을 경우에도 대기중인 스레드가 wait하지 않는다. 명백하게 몇백 클럭 이내로 끝날 정도의 코드블럭을 동기화하는데 사용한다. lock cmpxchg명령을 사용하며 spin lock구현 자체는 SRWLock과 거의 같다.
이 테스트에서도 x86이 월등히 성능이 좋다.

spin lock사용시 SQ1 ARM64의 성능저하
spin lock(SRWLock을 포함하여) 사용시 SQ1의 성능이 x86보다 많이 떨어진다.
측정 단위가 ms이기 때문에 x86쪽이 클럭이 높아서 더 빠르게 측정되는것처럼 보일수 있다. 하지만 모든 x86 CPU가 Surface Pro X보다 빠르게 측정된다. 기본 1GHz로 작동하는 GPDWIN2조차도 Surface Pro X보다 빠르다. spin lock획득에서 SQ1프로세서가 spin lock을 획득할때 상당한 시간을 소모하고 있음을 알 수 있다.
이유가 궁금해서 어셈블리어 코드를 확인했다. 명백한 원인은 찾지 못했다.

처음엔 ARM에서의 CAS구현이 loop를 돌면서 시도하는 방식이고 그에 따라 코드도 길어지기 때문에 더 느린거라고 생각했다. x86의 경우 lock cmpxchg 명령어 한줄로 끝나지만 내부적으론 루프를 돌 것이다. 그럼에도 불구하고 x86의 방식이 더 효율적일것이다. 그렇게 생각했다.

[_InterlockedCompareExchange() – x64]
interlockedCompareExchange_x64

[_InterlockedCompareExchange() – ARM64]
interlockedCompareExchange_arm64

그런데 atomic increment테스트에서 x86과 ARM의 성능의 차이는 거의 없었다. 오히려 2스레드에선 ARM64가 더 빨랐다.
여기서 사용하는 _InterlockedIncrement() 어셈블리 코드를 확인해 보면 다음과 같다.
[_InterlockedIncrement() – x64]
interlockedIncreament_x64

[_InterlockedIncrement() – ARM64]
interlockedIncreament_arm64

ARM64의 경우 베타적 쓰기 작업을 수행하는 코드는 _InterlockedCompareExchange()와 똑같다. 코드도 x86에 비해 훨씬 길다. 그런데도 성능이 똑같거나 더 높게 측정된다.
단순히 arm64쪽이 코드가 길어서 느린거라면 atomc increment에서도 arm64쪽이 많이 느렸어야 한다.
난 CPU전문가가 아니고 ARM에 대해선 더더욱 알지 못한다. 결국 이유를 알 수 없다고 결론지었다.
확실한건 spin lock을 사용하는 경우 intel x86이 훨씬 빠르다는 것이다.

(Awake Next Thread)테스트는 wait 상태의 스레드가 깨어날때 얼마나 지연시간이 있는지를 테스트한다.
예를 들어 3개 스레드로 테스트하는 경우,
각각의 스레드들이 서로 꼬리를 물고 작동된다. 이것은 일종의 라운드 로빈이다.

  1. 0번 스레드는 변수값을 증가시킨 뒤 SetEvent(event[1])을 호출해서 1번 스레드를 깨운다.그리고 WaitForSingleObject(event[0]);를 호출해서 대기상태에 들어간다.
  2. 1번 스레드는 변수값을 증가시킨 뒤 SetEvent(event[2])를 호출해서 2번 스레드를 깨운다.그리고 WaitForSingleObject(event[1]);를 호출해서 대기상태에 들어간다.
  3. 2번 스레드는 변수값을 증가시킨 뒤 SetEvent(event[0])를 호출해서 0번 스레드를 깨운다.그리고 WaitForSingleObject(event[2]);를 호출해서 대기상태에 들어간다.
  4. 변수값이 목표값에 도달할때까지 이를 반복한다.

결과를 보면 SQ1 프로세서와 intel x86프로세서 사이에 의미 있는 성능 차이는 없어보인다.
다수의 스레드가 경쟁상태가 아니라면 멀티 스레드 응답성은 intel x86과 SQ1프로세서간에 차이가 없다고 볼 수 있다.

Multithreading 응답성 테스트 결론
다수의 스레드가 경쟁적으로 lock을 획득하려고 하는 경우(spin lock을 포함하여)에 SQ1프로세서가 intel x86보다 성능이 떨어지는 것으로 보인다.
동기화가 필요없을 경우 스레드가 스케쥴링되는데 있어 지연시간은 차이가 없어보인다.
다수의 코어를 총동원하는 작업에서 스레드 간의 동기화가 필요하다면 x86에서 더 좋은 성능을 얻을 수 있을것이다.

voxel data처리 테스트

내가 개발중인 voxel 기반의 게임 프로젝트가 있다.
여기서 늘 사용하는 툴과 게임 기능을 테스트 한다.
멀티스레드로 CPU성능을 최대한 사용하는 테스트이고 GPU성능도 중요하다.
3가지 테스트를 진행한다.

Loading map of voxels
50cm x 50cm x 50cm 짜리 복셀 1500만개로 이루어진 맵을 한번에 로드한다. 실제 게임에선 현재 위치 기준으로 일부 데이터만을 서버로부터 전송받는다. 오프라인 모드에서 한번에 로드할 수 있다.
복셀 데이터는 압축되어 있다. 파일 로딩 후 압축을 풀어서 vertex,index데이터를 만들고 충돌처리용 삼각형 데이터를 생성한다. 이 작업들은 core개수만큼의 다수의 스레드가 동시에 진행한다. 기하 데이터를 생성하고나면 라이트맵을 계산한다. 라이트맵 계산을 아직 완료하지 못한 경우 흰색으로 렌더링된다. nvidia GPU를 장착한 경우 라이트맵 게산에 CUDA를 사용할 수 있다. CUDA를 사용하면 압도적으로 성능이 좋다.

Baking Light-map
복셀월드 전체는 라이트맵을 사용한다. 월드를 로딩한 직후에, 또는 지형의 일부가 변형됐을때에 자동으로 라이트맵을 다시 계산한다. 그리고 수동으로 한번에 월드 전체의 라이트맵을 다시 계산할 수 있다. 논리 코어 개수만큼의 스레드가 라이트맵을 계산하고 업데이트한다.
라이트맵 계산이 끝나고 텍스처에 업데이트 될때까지 게임 프레임 갱신은 중단된다. 라이트맵 계산에 얼마만큼의 시간이 걸리는지를 측정한다. (여기서도 CUDA를 사용할 수 있다. 유감스럽게도 Surface Pro X에선 CUDA를 사용할 수 없다.)

Voxelization
게임에 필요한 지형 데이터를 이미 만들어진 삼각형 기반의 모델링 데이터로부터 얻기 위한 기능이다.
삼각형 베이스의 모델링 데이터를 복셀 데이터로 변환한다. 이런 류의 게임에서 필요한 복셀 데이터는 눈으로 보기에 복셀인것만으로는 충분하지 않다. 내부가 꽉찬 solid voxel data여야한다. 따라서 삼각형과 단순 교차 판정만으로는 원하는 복셀 데이터를 얻을 수 없다. 여러가지 방법이 있지만 꽤 무식하고 신뢰할만한 방식을 사용한다.
여기선 CPU와 GPU의 파워를 최대한 활용한다.
DirectX 11을 사용하면 부분적으로만 멀티 스레드 억세스가 가능하므로 DirectX 12를 사용한다. 개인적으로 DirectX 12의 성능에 대해선 매우 비관적이다. 할 말이 많지만, 이 경우에는 DirectX 12쪽이 성능이 조금 더 잘 나온다. 스레드 개수만큼의(논리 코어 개수만큼의) Commane Queue를 만들어서 GPU의 H/W queue를 남김없이 채운다.

영상을 보면 테스트 내내 CPU와 GPU점유율이 100%에 도달하고 있는걸 확인할 수 있다.

Surface Pro X vs intel x86 + dGPU devices


Voxel_horizon_test

Loading map of voxels 테스트 결과
SurfaceBook 2의 절반 정도의 성능을 보여준다. 디바이스의 등급을 생각하면 나쁘지 않은 수치라고 생각한다. 처음엔 Surface Pro 2보다 성능이 더 떨어질걸로 예상했다. 예상을 뒤엎고 GPDWIN2, Surface Pro 2보단 월등한 성능을 보여준다. 처음 이 게임을 개발할때 최소 장비로 Surface Pro 2를 생각했으니 이 정도면 만족스럽다.

Baking Light-map 테스트 결과
놀랍게도 Surface Book 1보다 빠르다. 4개 코어가 다소 멍청한 놈들이지만 그래도 8코어의 위력을 보여준다. 무려 Surface Book 2의 70% 수준이다. 예상보단 훨씬 높은 성능이다.

Voxelization테스트 결과
결과를 보면 Surface Pro 2보단 빠르지만 GPDWIN2보다도 느리다. 예전에 개발했던 게임을 DX12로 돌렸을때 심각하게 성능 저하가 있었다. 퀄컴의 DX12용 GPU드라이버는 성능상의 문제가 있다고 추측한다.

voxel data처리 테스트 결론
4개 core가 little코어이기 때문에 아무래도 기대만큼의 멀티 스레드 성능은 보여주지 못한다.
CPU + GPU로 협업을 할 경우 특히 성능이 떨어진다. 드라이버의 성능에 문제가 있다고 생각한다. GPU H/W queue에 command를 전송하는 기능에 문제가 있어 보인다.

게임 fps 테스트

오래전에 개발했던 게임으로 프레임레이트를 측정했다.
오래전에 개발한 게임이지만 계속 코드를 고쳐왔기 때문에 코드는 최신이다. DirectX 12도 지원한다. 퀄컴의 DirectX용 GPU드라이버는 Draw call(DrawPritimitive() 등…) 호출이 많아지면 급격히 성능이 떨어진다. 커맨드를 전송하는데서 병목이 걸리고 이 동안 GPU가 펑펑 논다.
이 게임은 draw call이 많지 않다. 따라서 GPU점유율이 100%에 가까운 상황을 만들 수 있다.
GPU 점유율이 100%에 가까운 상태에서의 성능 비교를 할 수 있다.


proj-d_surfacrprox_gpu_usage.png

비교적 오브젝트가 많은 마을맵을 로딩해서 같은 위치,같은 방향의 카메라 뷰를 설정한다. 해상도별로 프레임 레이트를 측정한다. 1920×1080을 공통으로 테스트한다. 참고삼아 디바이스별로 최대 해상도를 테스트했다.


proj-d_village

중급 이상의 dGPU를 장착한 디바이스에 비하면 형편없는 프레임 레이트지만 내장 GPU치고는 상당한 성능을 보여준다. 거의 Surface Book 1의 dGPU에 필적하는 성능이다. 아마 대부분의 x86 Surface Pro제품들에 비해서도 비슷하거나 더 높은 성능을 보여줄거라고 생각한다. Surface Pro 7의 인텔 내장 GPU와 비교를 하고 싶지만 Surface Pro 7을 가지고 있지 않으므로 아쉽게도 테스트 하지 못했다.

게임 fps 테스트 결론
GPU의 하드웨어 성능은 충분하다. 아니 충분한것 이상으로 좋다. AAA게임을 돌리기엔 무리지만 인텔 내장 GPU로 돌아가는 게임이라면 무리없이 돌릴 수 있는 성능이다.
다만 퀄컴의 DirectX용 GPU드라이버에는 문제가 있다. 성능상의 문제 외에 버그가 있다.
드라이버 문제가 해결된다면 개인적으론 ARM64타겟으로 게임을 출시하고 싶다.

Windows 10 on ARM의 문제점

과도한 메모리 소모 문제
같은 코드의 ARM64빌드와 x86/64빌드를 각각 Surface Pro X와 x86/64머신에서 실행했을때 ARM64에서 메모리 소모가 1.5배쯤 된다. 2GB메모리를 소모하던 앱이 ARM64빌드로 3GB를 소모한다. 내 게임이 일단 그렇다. DirectX Runtime이나 GPU 드라이버에서 추가적으로 더 소모하는것이라고 추측한다.

DirectX용 GPU 드라이버 문제

  1. Draw() 호출이 몰리면 급격히 성능이 저하된다. draw call이 많아지면 심지어 Surface Pro 2보다도 더 느려진다. 유저모드 드라이버의 스케쥴링에 문제가 있다고 생각한다.
  2. DX11 Deivice를 멀티스레드로 억세스 할 경우 드라이버에서 크래시한다. ID3D11DeviceContext는 스레드 세이프하지 않다. 하지만 ID3D11Device는 스레드 세이프하다. 따라서 멀티스레드로 D3D리소스들을 동시에 생성하고 제거할 수 있다. 하지만 현재 Surface Pro X에서는 드라이버(아마도 유저모드 드라이버)에서 크래시한다. 다음과 같은 에러 메시지를 확인할 수 있다.
    D3D11: Removing Device.
    D3D11 WARNING: ID3D11Device::RemoveDevice: Device removal has been triggered for the following reason (DXGI_ERROR_DRIVER_INTERNAL_ERROR: There is strong evidence that the driver has performed an undefined operation; but it may be because the application performed an illegal or undefined operation to begin with.). [ EXECUTION WARNING #379: DEVICE_REMOVAL_PROCESS_POSSIBLY_AT_FAULT]
    Exception thrown at 0x00007FFF8F6A4024 (qcdx11arm64um8180.dll) in MegayuchiLevelEditor_arm64_debug.exe: 0xC0000005: Access violation reading location 0x0000000000000028.
  3. performace counter가 지원되지 않는다.  Surface Pro X에서 그래픽 렌더링 상황을 GPUView로 분석을 하려고 시도했다. etl파일 캡처했는데 GPU정보가 전혀 나오지 않는다.스크린샷에서 볼 수 있듯 GPU정보가 전혀 나오지 않는다.
    Windows Game bar도 작동은 하지만 GPU점유율이 0%로 출력된다. frame rate는 아예 출력되지 않는다. 드라이버 문제라고 생각한다.

GTX1660TI
gpu_view_gtx1660ti

Surface Pro X
gpu_view_surfaceprox

결론

  1. 일반적인 CPU의 연산 – 산술연산, 메모리에서 읽고 쓰기, 이런 작업에서 SQ1프로세서의 ARM64 성능은 충분히 만족스럽다.
  2. spin lock 사용시 intel x86에 비해 크게 성능이 떨어진다. Critical Section사용등 멀티스레드 처리에 있어서 ‘나쁜 상황’에 처했을때 x86에 비해 성능이 크게 떨어진다.
  3. 아직은 intel x86보다 느리다. 클럭 주파수뿐 아니라 명령어 효율도 아직은 intel x86보다 떨어진다.
  4. 하지만 PC로 사용하기에 부족함이 없는 성능이다. CPU 성능은 intel x86에 비해 심각하게 뒤쳐지지는 않는다. 경우에 따라 x86보다 뛰어날때도 있다. 특히 GPU 성능은 인상적이다.
  5. 퀄컴의 GPU드라이버에는 문제가 있다. DirectX사용시 성능과 안정성 모두 문제 있다.
  6. 현세대 생산성 어플리케이션들이 ARM64로 빌드되어서 나와준다면 x86 디바이스에 비해 불편함 없는 작업환경을 제공할 수 있다고 생각한다.
  7. GPU드라이버가 개선된다면 x86 Surface Pro에서 돌리는 게임 정도는 무난히 돌릴 수 있을거라고 본다.
  8. x86 에뮬레이션 성능은 네이티브 ARM64에 비해 상당히 많이 떨어진다. Windows on ARM 생태계가 x86에뮬레이션에 의존해야한다면 미래는 없다.

ARM64로 포팅한 게임 플레이영상

[Project D Online on Surface Pro X]

[Voxel Horizon on Surface Pro X]


프로그래머 관점에서의 Surface Pro X 벤치마크”에 대한 답글 1개

  1. 와 가려운 부분을 긁어주는 훌륭한 포스팅입니다….
    긱벤치5 점수상으로 봤을때 arm 프로세서 성능이 많이 올라왔길래… windows on arm의 실태가 궁금했는데 벤치 감사합니다.

    좋아요

    1. 그런데 이거 체감으론 인텔에 비해 훨씬 더 느립니다. 명령어 효율이 떨어지는데 클럭은 훨씬 더 낮으니까요. 드라이버도 품질이 떨어집니다.

      좋아요

    2. 아직 컴파일러나 라이브러리가 ARM 환경에 최적화가 되지 않은 탓도 있을겁니다. 현재는 거의 황무지 수준이니까요.

      좋아요

      1. 라이브러리는 뭐 쓴거 없고 거의 제가 짠 네이티브 코드로만 돌아갔고요. 그냥 ARM아키텍처가 아직 x86보다 느린거 맞습니다. 클럭소모측면에서도 그렇고 실제로는 훨씬 더 느립니다. 안그래도 명령어당 클럭을 더 소모하는데 x86쪽이 클럭주파수가 훨씬 높으니까요. 제 개인적으론 딱 결론을 냈는데 고성능이 필요한 곳에서 ARM CPU못씁니다.

        좋아요

  2. 너무나 귀중하고 희귀한 벤치마크 자료네요. 감사합니다. 매우 중요한 레퍼런스가 될 것 같습니다. ㄷㄷㄷ

    궁금한게 서피스 북2의 소모클럭이 데스크탑인 8700K보다 적게 나오는 경우가 왕왕 보이는데 왜 그런 건가요?

    좋아요

    1. 측정에서 약간의 오차가 있을수 있고요. 데스크탑 CPU는 클럭이 높고 캐시메모리가 많은거지 명령어 효율이 노트북용 cpu보다 항상 높은것은 아닙니다. 클럭을 높이려고 명령어 효율을 희생하는 경우도 있으니까요. 클럭을 더 소모한다고 느린것이 아닙니다. 그 이상 클럭주파수가 높게 들어가면 더 빼른거니까요.

      좋아요

      1. 아하 답변 감사합니다. 저는 노트북 CPU는 단순히 저클럭 데스크탑 버전인줄 알았는데 그런 단순한 게 아니었다는걸 이번에 잘 알고 가게 되네요.. 정말 천차만별이군요

        좋아요

  3. 긱벤치 기준으로 SQ1의 빅코어와 4300U 점수가 거의 동일해서 4300U와 비교해 가면서 보고 있는데
    벡터/매트릭스 연산 성능에서는 SQ1이 월등하다고 봐도 될까요? SQ1 빅코어 클럭이 3Ghz에 4300U 터보부스트 클럭이 2.9Ghz 더라구요.

    좋아요

    1. 일단 다른 아키텍처의 cpu에서 긱벤치 결과는 좀 신뢰할 수 없다는 생각을 합니다. 이름만 같을뿐이지 ARM버전이랑 x86버전이랑 코드가 얼마나 다를지도 알수 없고요.
      제 테스트에선 SQ1이 월등하다고까지 할만한건 아니고 앞서는 항목들이 있죠. 근데 그것도 SIMD쓸때 그렇고 SIMD를 안쓰면 ARM이 거의 항상 느립니다. 사실 실제 어플리케이션에선 테스트 상황처럼 SIMD를 잘 써먹을수는 없어요. SIMD쓸수 있는 산술처리 코드가 전체 코드중에 아주 극히 일부밖에 안되기 때문에 SIMD를 썼을때 약간 빠르다고 해서 그게 전체적인 성능으론 연결이 안됩니다. 그리고 4300U가 언제적 CPU인데 그거랑 최신의 SQ1프로세서를 비교한다는것도 좀 망신스러운거죠.

      좋아요

      1. 아 제가 물어보고 싶었던 것은 벡터 매트릭스 테스트에서 SIMD 명령어셋을 쓰지 않고 모든 항목에서 SQ1이 지나치게 높길래 저도 좀 믿기지가 않아서
        제가 혹시 자료를 잘못 해석한건지, 아니면 멀티쓰레드에서 이루어진 연산인가 싶어서요.. 제가 해석한 게 맞나요?

        좋아요

      2. 일단 벡터 4칙연산에선 SQ1이 4300U보다 확연히 떨어지고요. 뒷장의 좀더 복잡한 테스트에선 전반적으로 SQ1이 4300U보다 빠른데요. 이건 SQ1이 빠르다기보다 4300U가 유독 떨어진다고 봐야겠죠. 다른 CPU들이랑 비교해도 확연히 떨어지니까요. 멀티스레드 아니고 싱글스레드입니다. 산술 연산 테스트에서 멀티스레드는 전혀 사용하지 않았습니다. 애초에 멀티스레드로 돌리면 정확히 클럭을 측정할수가 없어요.

        좋아요

      3. 애초에 저 리뷰의 기술적 결론은 ‘ARM이 느리지 않다’니까 생각보다 빠른것도 전혀 이상할건 없죠. 리뷰 쓸때만 해도 저도 기대를 많이 했었는데요. 쓸만한 정도의 성능은 나왔으니까요. 문제는 딱 거기까지고, 현실적으로 ARM을 PC에서 사용하게 되면 x86에뮬레이션으로 쓸 상황이 90%이상이란겁니다. 리뷰 쓰고나서 4개월 정도가 지난 지금은 x86에뮬레이션을 벗어날 수 없다고 결론지었어요.

        좋아요

      4. 상세한 답변 감사합니다. 이렇게 보니 인텔의 CPU도 장족의 발전이 있었네요..
        기껏 윈도우 on ARM 제품이 나왔는데 x86에뮬에서 벗어나지 못하는 상황이 참 안타깝긴 하네요…

        애플은 강제할 수 있을런지.. 아케이드를 보니 상당수 게임이 x86과 ARM을 둘 다 지원하는게 신기하더라구요.
        맥에서 실행해보니 터치 인터페이스가 그대로 적용되어있고 추가로 키보드와 컨트롤러를 지원하던데
        사실상 iOS 게임을 에뮬에서 돌리는것처럼 별 변화 동일한 UI가 적용되어 있더군요.
        에뮬은 아닌것 같던데 네이티브로 강제시킬 수 있었던게 신기하긴 해요

        좋아요

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

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

Google photo

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

Twitter 사진

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

Facebook 사진

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

%s에 연결하는 중