ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 렌더링 성능 최적화 수업 정리
    Web/Optimization 2021. 9. 24. 18:12
    반응형

    ※ Jank(화면 버벅거림)

    일반적인 스크린의 주사율은 60FPS(Frame Per Second)이다.

    60FPS = 1초에 60프레임 = 1프레임 당 16.67ms

    컨텐츠가 업데이트되는 주기도 물리적인 스크린이 업데이트되는 주기와 맞춰져야

    끊겨보이지 않는 화면 업데이트가 가능해진다. 이를 위해 requestAnimationFrame을 사용하는 것.

    만약 브라우저의 Main Thread에 일이 넘쳐나게되면 1초에 60프레임을 맞추지 못하고 화면이 버벅거리게 된다.

     

    그러면, Main Thread가 일을 너무 많이 하지 않게 도와주는 방법은 어떤 게 있을까?

    1. reflow, repaint가 생각보다 너무 많이 일어나는 것을 개선한다.

    2. 똑같은 작업을 매번 새로하지 않도록 개선한다.

    3. 일을 여러명이서 나눠서 한다.

    4. 병목 코드를 개선한다.

     

    reflow, repaint가 생각보다 너무 많이 일어나는데?

    ο reflow, repaint를 유발하는 속성 사용 줄이기 & layer 사용하기

    /* X */
    example:hover {
        top: -1rem;
    }
    
    /* O */
    example:hover {
        transform: translateY(-1rem);
    };

    출처는 공원 코치님의 우테코 교육자료

     

    ※ Layout thrashing(Forced synchronous layout)

    const paragraphs = document.querySelector('p');
    const greenBlock = document.getElementById('block');
    
    for (let p = 0; p < paragraphs.length; p++) {
        const blockWidth = greenBlock.offsetWidth;
        paragraphs[p].style.width = blockWidth + 'px';
    }

    offsetWidth를 이용해 element의 width를 가져오는 등의 작업을 할 때는

    브라우저가 해당 element의 width를 실제로 계산하는 작업이 필요한데, 이 때 layout이 발생하게 된다.

    상단의 코드를 보면, width 값을 가져온 후에 style.width를 설정해주고 있다.

    이렇게 되면 layout이 발생한 이후에 style이 발생하게 되는데,

    style이 발생하면(style을 변경하면) 이전에 계산했던 layout은 무효화가 된다.

    대상 element를 변경해버렸기 때문.

    그래서 브라우저는 layout을 포함한 모든 작업들을 다시 처음부터 수행하게 되고

    이는 상당한 비용을 요구하는 작업이다. Forced Synchronous Layout이라고도 함.

    Chrome devTools에 보면 빨간 삼각형 표시가 되어있는 부분이 바로 Forced Synchronous Layout.

    (Waterfall view에서 보면 노란 삼각형 속의 느낌표 표시)

    이런 표시를 발견하게되면, 왜 Forced Synchronous Layout이 발생했는지를 코드에서 찾아내어 삭제해야 한다.

    참고: Layout Thrashing - Styles and Layout - Browser Rendering Optimization - YouTube

     

    ※ useLayoutEffect

    useEffect 훅과의 차이점은, useLayoutEffect는 브라우저가 DOM elements를 화면에 그리기 전에 동작한다는 것.

    useEffect는 UI가 그려지고 난 이후에 동작하는 반면, useLayoutEffect는 UI가 그려지기 전에 동작한다.

    직접적으로 DOM을 변경하고자 할 때는, DOM 변경이 UI가 그려지고 난 후에 이루어지는 것보다

    UI가 그려지기 전에 발생하는 것이 훨씬 부드러운 사용성을 제공하므로 useLayoutEffect가 더 낫다.

    참고: React: useEffect vs useLayoutEffect - DEV Community

     

    ο 플레이스 홀더 사용하기

      - 이미지의 width, height를 미리 지정해둔다

      - 원인 별 Cumulative Layout Shift(CLS)를 줄이는 방법

        - 광고, embed 및 iframe, 이미지 등의 크기를 정해놓지 않은 경우

          - 이미지 및 비디오 요소에 항상 width, height를 정해놓아야 한다.

          - 광고 슬롯을 위한 고정 공간을 확보해 놓는다.

        - 동적으로 컨텐츠를 주입하는 경우

          - Skeleton UI 등을 통해 미리 공간을 확보해 놓는다.

        - FOIT/FOUT를 유발하는 웹 폰트를 사용하는 경우

          - font-display를 활용한다.

          - 핵심 글꼴에 preload를 사용한다.

        - DOM 업데이트 전에 네트워크 응답을 대기하는 경우

     

    참고: Optimize Cumulative Layout Shift (web.dev)

     

    똑같은 작업인데 매번 새로 해야하나?

    - Memoization

      - 복잡한 계산을 다시 수행해야하는 경우, 새로 계산하지 않고 저장해놓은 이전의 계산 결과를 가져와서 사용

      ex. Closure를 이용한 Memoization 함수

    const memoize = (func) => {
        const results = {};
        return (...args) => {
            const argsKey = JSON.stringify(args);
            if (!results[argsKey]) {
                results[argsKey] = func(...args);
            }
            return results[argsKey];
        };
    };
    
    const clumsysquare = memoize((num) => {
        let result = 0;
        for (let i = 1; i <= num; i++) {
            for (let j = 1; j <= num; j++) {
                result++;
            }
        }
        return result;
    });
    
    console.time("First call");
    console.log(clumsysquare(9467));
    console.timeEnd("First call");
    // 89624089
    // First call: 132.389ms
    
    console.time("Second call");
    console.log(clumsysquare(9467));
    console.timeEnd("Second call");
    // 89624089
    // Second call: 0.091ms
    
    console.time("Third call");
    console.log(clumsysquare(9467));
    console.timeEnd("Third call");
    // 89624089
    // Third call: 0.085ms

    참고: Introduction to Memorization in JavaScript | Engineering Education (EngEd) Program | Section

     

    일 좀 나눠서 합시다!

    - Worker 활용하기

      - 렌더링을 수행하는 Main Thread와 별개로 동작하는 백그라운드 thread를 만들어 스크립트 실행

      - DOM 조작 불가, window API 접근 불가

      - 예시) 연산이 오래 걸리는 복잡한 계산, UI와 별개로 수행되는 작업 등

     

    - GPU 활용하기

      - ex. GPU.js

        - GPU를 이용해서 성능을 개선하는 코드를 짤 수 있다.

        - GPU를 사용해서 작업을 수행하는 함수를 만들 수도 있음

    참고: Using GPU to Improve JavaScript Performance | by Chameera Dulanga | Bits and Pieces (bitsrc.io)

     

    병목 코드를 개선하자.

     

    ※ 프레임워크 / 라이브러리가 미리 해주는 일

    프레임워크마다 상태가 UI에 적용되는 과정은 다르지만, 각자의 방식대로 기본적인 최적화를 제공한다.

    주로 DOM 제어와 관련한 최적화, 혹은 라이프사이클 메소드 등을 통한 최적화 방식을 제공한다.

    React의 경우, React.memo 메소드를 사용하면 React가 해주는 일을 줄여줄 수도 있다.

    React.memo 컴포넌트의 경우 Virtual DOM에서 비교했을 때 업데이트 이후의 컴포넌트와 이전의 컴포넌트가 동일한 경우 해당 업데이트를 Real DOM에 반영하지 않는다. (리렌더링을 하지 않는다.)

    참고: React.memo() 현명하게 사용하기 | TOAST UI :: Make Your Web Delicious!

     

    React의 경우 객체를 비교할 때, 빠른 비교를 위해 참조 비교만 이루어지기 때문에

    우리가 변경한 내용을 제대로 반영시켜 업데이트를 하고싶다면 객체의 불변성을 유지하는 것 또한 중요하다.

    참고: The Power of Immutable Data (feat. ImmutableJS) - DAHAE KIM | WEB DEVELOPER

     

    ■ 체감 성능 개선하기

    - Skeleton UI 활용하기

    - SSR 활용하기 

    반응형

    'Web > Optimization' 카테고리의 다른 글

    이미지 프리로딩(Image Preloading)  (2) 2021.09.23
    로딩 성능 최적화 수업 정리  (0) 2021.09.22
    프론트엔드 웹 로딩 최적화  (2) 2021.08.16

    댓글

Designed by Tistory.