성능 최적화와 관련해서 : reflow 와 repaint

itssweetrain·2021년 6월 17일
8

web

목록 보기
4/4
post-thumbnail

Layout

렌더 트리 생성이 끝나면 배치가 시작된다.

레이아웃 단계는 트리를 생성하면서 계산된 노드와 스타일을 기기의 뷰포트에서 어떤 위치에서 어떻게 보이도록 하는지 결정하는 단계이다.

레이아웃 프로세스는 뷰포트 내에서 각 요소의 정확한 위치와 크기를 캡쳐하는 '상자 모델'이 출력되며 상대적인 측정값도 모두 절대적인 픽셀로 변환된다.

레이아웃 과정 즉, 리플로우는 최초 한 번만 실행되고 끝나는 것이 아니라 width, height, left, top, offsetHeight, offsetWidth, scrollTop 등 수치를 계산해 새로운 위치에 나타나게 하는 동작이 일어날 때마다 반복해서 실행된다.

크롬 브라우저의 개발자 도구에서 web browser을 검색한 후 개발자도구에서 Layout 탭을 확인했을 때 나오는 화면이다.

배치는 반복되며 HTML 문서의 요소에 해당하는 최상위 렌더러에서 시작한다.
최상위 렌더러의 위치는 0,0 이고 브라우저 창의 보이는 영역에 해당하는 뷰포트 만큼의 면적을 갖는다.

따라서, 위의 화면에서 보는 것처럼 Layout의 Flexbox의 맨 상단을 호버해보면, 구글 마크 옆에 offsetHeight 부분 0 x 88 으로 시작되는 것을 알 수 있다. 또한 flexbox 에 보이는 것처럼 영역별로 레이아웃들이 준비되어 있는 것을 볼 수 있다.

이렇게

모든 렌더러는 '배치' 또는 '리플로' 메서드를 갖는데 각 렌더러는 배치해야 할 자식의 배치 메소드를 불러온다.

layout 알고리즘으로는,

각 박스의 넓이는 viewport 기준,
각 박스의 높이는 contents(font) 기준으로 배치가 된다.

소소한 변경 때문에 전체를 다시 배치하지 않기 위해서는 브라우저는 더티 비트 체제를 사용하여 점층적인 배치를 한다. 렌더러는 다시 배치할 필요가 있는 변경 요소 또는 추가된 것과 그 자식을 더티라고 표시하는 것이다.

반대로 렌더러 트리 전체에서 일어날 수 있는 전역 배치(global layout)에는, 윈도우 사이즈를 변경하거나 폰트 크기 변경과 같은 사항에서 발생한다.

배치에 따라 동기와 비동기적인 실행도 달라지는데, 점층 배치는 렌더러가 더치일 때 비동기적으로 일어난다. 예를 들면 네트워크로부터 추가 내용을 받아서 DOM 트리에 더해진 다음 새로운 렌더러가 렌더 트리에 붙을 때이다.

웹킷의 경우 점증 배치를 실행하는 타이머가 있는데 트리를 탐색하여 더티 렌더러를 배치한다. offsetHeight 같은 스타일 정보를 요청하는 스크립트는 동기적으로 점증 배치를 실행한다.

전역 배치는 보통 동기적으로 실행된다. 때때로 배치는 스크롤 위치 변화와 같은 일부 속성들 때문에 초기 배치 이후 콜백으로 실행된다.

또한
렌더링 엔진은 좀 더 나은 사용자 경험을 위해 가능하면 빠르게 내용을 표시하는데 모든 HTML을 파싱할 때까지 기다리지 않고 배치와 그리기 과정을 시작한다.

네트워크로부터 나머지 내용이 전송되기를 기다리는 동시에 받은 내용의 일부를 먼저 화면에 표시하는 것이다.

Paint

레이아웃 과정에서 렌더링 엔진이 각 요소들이 어떻게 생겼고 이를 어떻게 보여 주지 알게 되면 마지막으로 화면에 실제 픽셀로 그려지도록 변환하는 과정을 거치는데 이것이 페인트 과정이다.

레이아웃 계산이 완료되어 렌더링 트리의 각 노드를 화면에 실제 픽셀로 변환해 화면에 그리는 단계이다.
이 과정에서 렌더 트리에 포함된 요소들이나 텍스트, 이미지들이 실제 픽셀로 그려진다.

repaint 역시 최초 한 번만 실행되고 끝나지 않고 reflow 가 발생했을 때는 반드시 화면을 다시 그려야 하기 때문에 실행되고, reflow가 일어나지 않더라도 상술한 속성이 변경되면 역시 화면을 다시 그려야 하기 때문에 실행된다.

페이지를 인터랙팅하면서,
유저는 이벤트를 발생시킬 수 있다. 몇 이벤트들은 페이지 레이아웃과 스타일을 변경시킬 수 있고, 스크립트에 따라서 브라우저는 비트맵과 compositor frames을 스크린에 전달하기 위해 렌더링을 여러번 해야할 수도 있다.

보통은, 페이지 업데이트는 보통 자바스크립트의 실행으로 일어난다. 화면에 나타나는 부분에 변화가 생기면 어떤 변화인지에 따라 reflow와 repaint가 발생하거나 또는 repaint가 발생한다. 크게 3가지 경우로 동작하는데,

  1. 다시 layout이 발생하는 경우
    주로 요소의 크기나 위치가 바뀔 때, 혹은 브라우저 창의 크기가 바뀌었을 때 그림의 순서에 따라서 레이아웃이 다시 발생한다.

이 때 레이아웃 수치를 다시 계산해서 배치를 해야하기 때문에 레이아웃 과정에 다시 발생하고 이에 맞춰서 다시 페인트도 해줘야 하고 마지막으로 레이어를 합성하는 과정까지 발생한다.

  1. Paint 부터 다시 발생하는 경우
    이 때는 주로 배경 이미지나 텍스트 색상, 그림자 등 실제 레이아웃의 수치를 변화시키지 않는 스타일의 변경이 일어났을 때 발생한다.

이와 같은 경우는 레이아웃 과정이 발생하지 않기 때문에 성능상으로 조금 더 이점을 가진다.

  1. 레이어의 합성만 다시 발생하는 경우

레이어에 대해 간략하게 언급하자면,
레이어는 포토샵 레이어와 비슷하게 페인팅할 영역을 나누어 놓는 것을 의미한다.

paint 과정 전, 우리가 계산한 것을 바로 화면에 그리는게 아니라 이 요소들을 어떻게 배치했냐에 따라서 paint 부분에서는 각각 부분을 조금 조금씩 잘게 나누어서 이미지를 준비해 놓는다.

크롬에 검색한 결과 페이지에서 개발자도구로 layers 탭을 확인해보면, 이렇게 이미지를 레이어로 나눈 것을 볼 수 있다.

크롬의 경우에는 레이아웃 과정 이후에 정해진 기준이나 필요에 의해서 브라우저가 레이어를 생성한다.

오른쪽은 생성된 레이어의 모습이다.
즉, 렌더 트리에 있는 노드 객체들은 생성된 레이어에 포함하게 된다. 레이어들은 트리 형태로 구성이 되고 이 렌더링 엔진이 각 레이어를 프린팅 과정에서 각각 그려준 모습인 것이다. 왼쪽 목록에서는 생성된 레이어의 수를 볼 수 있다. 맨 상위에 document 오브젝트 또한 레이어의 첫 번째 요소로 생성된 것을 볼 수 있다.

paint 를 적용시켜보면, 검색 결과 페이지처럼 그려진 것을 볼 수 있다.

레이어가 생성 되고, 그 다음은 각각 요소 이미지를 컴퓨터가 이해할 수 있는 비트맵으로 변환하고 하나의 비트맵으로 합성, 즉 composite 과정을 거쳐 페이지를 완한다.

한 번에 다 그리면 되지 왜 복잡하게 레이어 기능을 준비해서 레이어별로 준비하는가?

브라우저가 조금 성능개선을 위해서 스스로 준비를 해 놓는것이다. 레이어 단위로 하면 이 레이어만 따로 놓으면 이 레이어만 수정하면 되기 때문이다.

이렇게 브라우저도 레이어 기능을 이용해서 성능개선을 위해서 나름의 노력을 하고 있는 것이다.

다시 세 번 째 경우인 composite만 일어나는 경우로 돌아가서,
이와 같은 사실들로 layout과 paint를 수행하지 않고 레이어의 합성만 발생하는 경우에는, 성능상으로 가장 큰 이점을 가지게 되는 것이다.

이와 같은 경우의 예시로는, CSS 애니메이션이 예시가 될 수 있다. 이 애니메이션은 레이아웃, 레이어, 페인트 과정을 모두 건너고 composite 의 과정만 거치게 된다.

그래서 우리가 javascript나 css로 DOM 요소를 조작할 때 composition만 일어나면 베스트, layout 을 다시 일어나게 하면 worst인 것이다.


성능의 최적화

자바스크립트의 실행, 리플로우, 리페인트는 성능을 저하시킬 수 있다.

화면을 새롭게 그려야 하기 때문에 reflow, repaint가 발생하는 것 자체를 문제 삼을 수는 없으나 이 단계가 너무 자주 반복되면 페이지의 성능을 저하시키는 요인이 된다. 때문에 성능을 개선하기 위해 reflow와 repaint 횟수를 줄이는 방법이 고려될 수 있다.

렌더링 엔진이 어떻게 동작하는지 전체적으로 살펴봤는데,
critical rendering path 시간을 줄이면 브라우저가 웹 페이지를 보여주는데 시간도 줄일 수 있다.

Rendering Tree를 만들 때까지 어떻게 하면 빠르게 만들 수 있는지에 대한 물음에는,
당연히 DOM 요소가 작으면 작을수록 css 규칙이 작으면 작을수록 트리가 작기 때문에 빠르게 만들 수 있습니다.

그래서 불필요한 태그, 불필요한 div 태그 남용이나 wrapping class를 만드는 것을 자제해야 한다. 최대한 요소들을 작게 만드는 것이 중요하고, layout이나 paint 부분에서 처리하는 과정, 즉 처음에 사용자에게 보여지는 것도 중요한데, 애니메이션을 쓸 때 paint가 자주 일어나지 않도록 만드는 것이 중요하다. 이러한 과정을 인지하고 렌더링을 최소화하기 위한 효율적인 코드를 작성해야한다.

내가 공부하면서 많이 참고한, 웹 브라우저의 작동 원리에 대해 깊고 자세하게 설명한 두 글을 추천한다
https://d2.naver.com/helloworld/59361
https://cabulous.medium.com/how-does-browser-work-in-2019-part-5-optimization-in-the-interaction-stage-66b53b8ec0ad

profile
motivation⚡️

1개의 댓글