UIAnimation Hitch와 Render Loop에 대해 알아보자

Lily·2022년 9월 6일
0
post-thumbnail

UI의 performance는 어떻게 측정할 수 있을까요?

이에 대한 답을 찾기 위해 TechTalk: Explore UI animation hitches and the render loop을 시청했습니다.
위 영상에서는 직접적 방법에 대해 알려주진 않지만, UI 렌더 성능이 저하될 때 나타나는 현상인 hitchrender loop의 과정을 설명해주고 있어요!

측정 방법은 위 영상에서 소개한 다른 세션을 보고 포스팅을 해보도록하고요, 오늘은 hitch와 render loop에 대해 정리해보도록 할게요


hitch는 무엇일까?

any time a frame appears on screen later than expected

"프레임이 스크린에 제 때 나타나지 않는, 늦게 나타나는 시간"이라고 정의합니다.

즉 다음 프레임의 생성이 늦어져 제 시간에 나타나지 않고, 늦게 나타나서 애니메이션이 끊기는 시간입니다.

여기서 궁금증이 하나 생겼습니다.

그럼 프레임이 뭘까요?

frame : 정지 사진

동영상을 물리적으로 환원하면 시간상 연속된 정지 사진들의 모음으로 볼 수 있는데, 이 각각의 정지 사진 하나를 '프레임'이라 부른다.

우리가 움직이는 영상으로 인식하는 컨텐츠는 사실, 연속된 정지 사진들의 모임입니다.
사진들을 일정한 시간 단위로 연속적으로 보여주면, 우리는 사진이 움직인다고 느끼는 것이죠!

여기서 하나의 정지 사진을 frame이라고 합니다.

이를 애플리케이션 화면에 대입해보면,
스크롤이 되어 움직이는 것 같은 화면도 사실은 사진(frame)들의 모음인 것이죠

FPS (Frame Per Second)

1초당 몇 개의 frame이 보이느냐. 프레임이 보이는 속도

그래서 'frame이 1초에 몇 개씩 보이느냐'는 영상의 품질을 결정하는 요소가 됩니다.
인간은 1초에 15장의 사진을 연속적으로 보여주면(15fps), 자연스러운 동영상으로 인식한다고 하네요!

Refresh rate

FPS와 함께 많이 사용되는 유사한 개념인 refresh rate도 있습니다.

FPS가 영상의 소스(파일)을 대상으로 한 개념이라면, refresh rate는 디스플레이 디바이스를 대상으로 하는 개념입니다.

디스플레이 기기가 1초동안 몇 개의 프레임을 표시할 수 있는지. Hz 단위로 표시

화면 주사율(Scan rate), 화면 재생 빈도라고도 합니다.


hitch의 원인

UIAnimation은 이렇게 연속적인 frame을 내보내는데,
다음 frame이 보여져야할 시간에 보여지지 않고, 이전 frame을 보여준다면 애니메이션이 버벅이겠죠.

그럼 frame이 왜 제 시간에 나타나지 못할까요?

그 원인은 render loop가 제 시간에 frame을 완성하지 못했기때문입니다.


The Render Loop

렌더 루프란 사용자 터치 이벤트가 앱으로 전달되고, 그에 따른 UI의 변화가 운영체제로 전달되는 과정을 말합니다. 렌더 루프가 한 번 실행 될 때 하나의 프레임이 완성됩니다.

렌더루프는 기기의 refresh rate 시점마다 실행됩니다.

예를 들어 아이폰의 refresh rate가 60Hz라면, 1초에 60장의 프레임이 표시되니
1second / 60 = 16.67ms 마다 프레임이 교체됩니다.

스크린에서 프레임이 교체되는 시점에 하드웨어는 VSYNC라는 이벤트를 방출합니다.
이때까지 다음 프레임은 준비되어있어야 합니다.

(뒤에서 설명하겠지만 "VSYNC == frame이 교체되는 시점"은 렌더 루프의 각 단계가 일을 마쳐야하는 마감기한이 됩니다. 그리고 이 마감기한을 맞추지 못하면 hitch가 발생합니다.)

정리하자면,
Refresh rate에 맞춰 일정 시간(16.67ms 또는 8.33ms) 마다 frame이 교체 되고,
교체되는 시점에 시스템은 VSYNC라는 이벤트를 내보냅니다.
VSYNC에 맞춰 렌더 루프도 시작합니다.

3 phase of Render Loop

렌더 루프는 크게는 3가지 단계로 이루어져있습니다.

  1. App
    이벤트가 처리되고, UI에 변화가 생기는 단계입니다. 이 일은 다음 VSYNC 전에 완료되어야 합니다. 그래야 지연 없이 다음 단계를 시작 할 수 있습니다.
  2. Render server
    실제로 UI가 렌더되는 단계입니다. render server라는 별개의 프로세스에서 수행됩니다. 이 단계 또한 다음 VSYNC전에 완료되어야합니다. 그래야 다음 단계에서 새로운 프레임을 보여줄 수 있음
  3. On the display
    완성된 새로운 프레임이 화면에 표시됩니다.

위 그림에서 보여지듯이
프레임이 보여지기 두 프레임 전에 렌더루프가 시작되어 다음 보여질 프레임을 준비합니다.

Double Buffering

2 프레임 전에 렌더루프가 시작되는 것을 double buffering이라고 합니다.

Triple Buffering

Triple Buffering은 hitch를 피하기 위해 3 프레임 전에 시작합니다. render sever에게 2 프레임을 할당함으로써 모든 렌더링 작업을 완료할 수 있도록 하는 것인데요, 이건 fall back 모드라고 합니다.

그래서 영상에서는 Double buffering을 기준으로 설명을 이어나가네요


5 phase of Render Loop

전체 렌더 루프는 총 5단계로 구성되어있습니다.

  1. Event : 앱이 터치 이벤트를 처리하고, UI를 변화 시킬지를 결정 App 단계
  2. Commit : 앱이 UI를 업데이트, 업데이트한 UI를 렌더링해달라고 렌더 서버에게 제출 App 단계
  3. Render prepare : 렌더 서버는 다음 VSYNC에서 제출본을 받고, 새로운 UI를 GPU에서 그릴 준비를 함 Render 단계
  4. Render execute : GPU는 UI를 최종 이미지로 그림 Render 단계
  5. Display : 다음 VSYNC에서 사용자에게 최종 이미지인 프레임이 보여짐 Display 단계

각 단계에서 어떤 일을 하는지 자세히 살펴볼게요

1. Event phase

앱이 이벤트를 처리하고 UI를 변경시키는 단계입니다. 이벤트라 하면 사용자 터치, 네트워크 콜백, 키보드와 같은 이벤트를 포함합니다.

이벤트가 UI를 변경시킨다면, 앱은 레이어의 계층 구조, 배경 색, 사이즈나 위치를 변경합니다.
또한 앱이 레이어의 bounds를 바꾸면, CoreAnimation은 setNeedsLayout을 자동으로 호출합니다. 또는 개발자가 직접 setNeedsLayout을 호출해 UI를 다시 그려달라고 예약할 수 도 있습니다.

setNeedsLayout이 호출되어 레이아웃 재계산이 필요한 레이어들을 식별되고, commit phase로 넘깁니다.

2. Commit phase

레이어의 레이아웃을 배치하고, draw를 하는 단계입니다.

먼저, 시스템은 레이아웃 업데이트 요청들을 병합해서 중복 작업을 제거하고 순서대로 처리합니다. 그리고 레이아웃의 변경이 필요한 레이어들을 부모부터 자식까지 하나씩 배치해 Layer tree를 만듭니다.

레이아웃은 흔히 일어나는 성능 bottleneck입니다. 따라서 이 과정이 몇 ms안에 이뤄진다는 것을 고려해야합니다.

두번째로, drawRect을 오버라이딩한 커스텀 뷰나 setNeedsDisplay가 호출된 뷰들이 그려집니다. 레이아웃과 마찬가지로 시스템은 이러한 요청들을 병합한 후 레이아웃이 완료되면 한 번에 처리합니다.

코어 애니메이션이 사용되는 한, 이 단계에서는 레이어는 단지 이미지입니다. (CALayer가 관리하는 비트맵 이미지라는 것으로 이해 했는데, 맞나요..? 좀 더 공부해봐야겠습니다)

이렇게 배치되고 그려진 레이어 트리는 render server로 보내집니다.

3. Render Prepare

레이어 트리를 순회하며 각 레이어의 drawing command를 GPU가 실행할 수 있는 선형 파이프라인으로 준비하는 단계입니다.

top layer에서 부터 시작해 부모에서 자식 순으로, 레이어를 정렬합니다. 개발자 입장에서 보았을 때, 뒤에서 부터 앞으로 정렬 되네요. 따라서 그림은 뒤에서부터 앞으로 그려집니다.

4. Redner Execute

선형 파이프라인이 GPU로 전달되고, 렌더 서버에서 레이어가 최종본으로 합성됩니다.

어떠한 레이어는 렌더링 소요시간이 길기 때문에, 이는 bottleneck의 원인으로 작용하기도 합니다.

5. Display

이미지가 완성되면 다음 VSYNC에서 화면에 보여집니다.

병렬적으로 실행되는 루프

파이프라인은 병렬적으로 실행됩니다. 동시다발적으로 각 단계를 수행하고 있습니다.
따라서 하나의 단계라도 데드라인을 맞추지 못하면 치명적이죠...


2가지 hitch 타입


hitch는 두가지 타입이 있습니다.

  • Commit hitch: 앱 프로세스에서 commit하는데 오래 걸려서 발생
  • Render hitch: 렌더 서버에서 제 시간내 렌더링하지 못해서 발생

1. Commit hitch

커밋이 기한 내 완료되지 않아 다음 VSYNC에 렌더링을 하지 못하고, 이전 프레임을 2프레임에 걸쳐 보여줍니다. 결국 1개의 프레임만큼 지연이 발생합니다.

이와 같이 지연되는 시간을 hitch time이라 하고, ms로 측정합니다.

커밋 히치를 찾고, 고치려면? 을 참고해주세요!
Commit Hitch를 찾고 제거하는 방법 -> TechTalk: Find and fix hitches in the commit phase

2. Render hitch

렌더링이 다음 VSYNC 전까지 완성되지 못해 프레임이 지연되는 현상입니다.

렌더 히치에 대해 더 알아보고, 레이어 트리를 최적화하는 방법은 아래 영상을 참고해주세요!
Demystify and eliminate hitches in the render phase


hitch 정량적으로 측정하기

hitch time으로 측정하는 것은 부정확 할 수 있습니다.
하나의 hitch를 분석할 땐 유용할 수 있지만, 스크롤이나 애니메이션과 같은 상황에서는 부적절합니다. 왜냐하면 스크롤이나 애니메이션은 UI를 보여주는 시간과 프레임의 개수가 똑같아야 비교를 할 수 있기 때문입니다. 심지어 iOS 기기는 항상 스크린을 업데이트 하지 않습니다. 커밋이 있어야 새로운 프레임이 생기죠. 따라서 기기 간에 히치 타임을 비교하는 것이 더 어렵습니다.

Hitch time ratio

대신 Hitch time ratio를 미터법으로 사용합니다.

총 히치 시간을 지속 시간으로 나눈 값

총 시간으로 나눈 정규화된 값이므로 비교가 가능한 수치라고 할 수 있습니다.

hitch time ratio가 UI의 자연스러움에 얼마나 영향을 주느냐에 따라 기준을 설정해뒀네요.
측정하고 개선할 때 참고해봐야겠습니다~

그럼 어떻게 측정하느냐!
는 아래 영상을 참고하라고 하네요

Eliminate animation hitches with XCTest
What's new in MetricKit


👩🏻‍💻 정리: 그래서 개발자가 할 수 있는 일은?

frame이 렌더 루프에서 만들어지는 과정과, UI의 성능을 가늠할 수 있는 hitch, hitch time ratio에 대해 알아봤습니다.

이번 영상에서는 구체적인 hitch를 제거하는 방법론에 대해선 이야기 하지 않았는데요, 그래서 다음 이야기가 매우 궁금한 채로 끝이 났습니다....

소개해 준 다른 영상들 보러 갈게요~!

Commit Hitch를 찾고 제거하는 방법 -> TechTalk: Find and fix hitches in the commit phase

profile
i🍎S 개발을 합니다

0개의 댓글