FE 성능 최적화

패스트캠퍼스 the red를 듣고 정리해보았다.

파일 다운로드

최신 브라우저는 대체로 도메인 당 6개의 접속만 동시에 처리한다(HTTP 프로토콜 /1.1기준)
http2에서는 하나의 연결로 계속 파일을 주고받아서 괜찮다.
하지만 아직은 1.1기준으로 작동하는 사이트가 많다.

한 도메인에서 6개의 파일만 동시에 다운로드 할 수 있다.
6개 이상부터는 큐에 넣어두고 하나씩 다운로드 하게 된다.

이미지와 같은 파일들은 사용자가 기다리는것에 익숙하지만
js나 cs파일들의 다운로드가 느려지면 페이지에 변화가 없고 인터액션이 늦어지게 된다.

개선방안

  1. 파일 갯수와 용량을 작게한다.

  2. 별도 도메인이나 CDN등으로 분산하여 해결할 수 있다.

  3. 적절한 포맷을 사용한다. 이미지의 경우 webp파일을 사용하여 크기를 줄일 수 있다.
    ex) 레진코믹스

1
2
3
4
5
6
<!-- 지원 타입에 따라 다른 이미지 로딩 -->
<!-- img 태그를 picture태그로 감싸서 picture태그를 지원하는 브라우저에서는 picture를 해석하도록 만들었다. -->
<picture>
<source srcset="img/photo.webp" type="image/webp" />
<img src="img/photo.jpg" alt="my photo" />
</picture>
  1. 화면 크기와 해상도에 따라 적절한 이미지 로딩
1
2
3
4
5
6
7
8
<!-- 화면 크기에 따라 적절한 이미지 로딩 -->
<picture>
<source srcset="img/photo_small.jpg" media="(max-width:800px)" />
<img src="img/photo.jpg" alt="my photo" />
</picture>

<!-- 해상도에 따라 적절한 이미지 로딩 -->
<img src="img/icon72.png" alt="icon" srcset="img/icon14.png 2x" />
  1. 웹 폰트 최적화
  • 구글폰트: 나눔 고딕 등 한국어 폰트 26종 제공
  • WOFF2는 WOFF,TTF등에 비해 30% 용량이 작다.
  • 필요한 글자만 골라서 글꼴을 만들 수도 있다.(메뉴용 글꼴 등)
  1. 화면 크기 등에 따라 필요한 스타일 시트만 로딩
  • 하지만 실제 이렇게 사용하는 경우는 없다. css는 크기가 작고 파일 개수를 늘리는 것은 좋지 않다.
  • 부트스트랩이 차지하는 용량이 20kb쯤
1
2
3
4
5
6
<link href="mobile.css" rel="stylesheet" media="all" />
<link
href="desktop.css"
rel="stylesheet"
media="screen and (min-width: 600px)"
/>
  1. link 태그 만으로 접속 시간 절약
1
2
3
<link rel="dns-prefetch" media="https://taegon.kim" />
<link rel="preconnect" media="https://cdn.example.com" />
<link rel="prefetch" media="https://cdn.example.com/library.min.js" />

Preconnect를 사용할 경우 외부 도메인을 미리 검색하여 소켓을 연결해둔다. 서드파티 자원 연결에 적합하다.

Prefetch를 사용할 경우 외부 도메인을 미리 검색하여 소켓을 연결 후 다운로드까지 해둔다.
하지만 남용하면 안되고 꼭 필요하거나 다음 경로가 명확하게 예상이 될 경우에만 사용해야 한다.

로딩 속도 개선

  1. 필수 컨텐츠가 아니라면 비동기 로딩을 고려해보자(광고,댓글,헤더/푸터 등)

  2. 이미지/아이프레임/스크립트 등은 필요할 때까지는 읽지 않는 게으른 로딩(lazy loading)기법을 고려해보자. 코드를 분할한 후 필요할 때 읽어오는 방식이다. 웹팩에서 자동으로 해주기도 한다.

1
2
<img src="image.jpg" loading="lazy" />
<iframe src="https://example.com" loading="lazy"></iframe>
  1. 시간이 많이 걸린다면 플레이스 홀더 등으로 대체한다.

-스켈레톤 ui

계산 시간

  1. 웹 워커

자바스크립트는 싱글스레드이다. 하지만 워커스레드를 이용하여 멀티스레드가 가능하다.

워커스레드

  • UI를 조작할 수 없다.
  • 워커 스레드 전용으로 분리된 파일이 필요하다.
  • postMessage()로 데이터를 전송하고, onMessage 이벤트를 통해 받는 방식으로 통신한다.
  1. 느긋한 계산

값이 필요해질 때 까지 계산을 미뤄두는 기법이다.

게으른 평가, 지연 평가라고도 한다.

  1. 메모이제이션

계산 결과를 기억해두고 반복 사용하는 기법.
루프, 재귀 호출 등 최적화.

반응시간

지나치게 느린 애니메이션 및 응답 속도는 사용자 경험을 저해한다.

  1. 시스템의응답이 40ms보다 느리면 주의력 분산이 발생함
  2. ui가 100ms 이하로 반응해야 ui를 다루고 있다고 느낀다.
  3. 애니메이션은 60fps를 기준으로 한다.

애니메이션의 렌더링 순서

js-> 스타일-> 레이아웃 ->페인트 -> 합성

페인트까지는 메인스레드에서 진행한다. 합성만 컴포지터 스레드에서 진행한다.

메인스레드는 자바스크립트가 동작하기 때문에 메인스레드의 작업이 많아지면 자바스크립트의 속도도 느려진다.

개선 방법

메인스레드에서 이루어지는 과정은 가능한 한 피하고 컴포지터 스레드에서 일어나도록 하면 된다.

즉 레이아웃이나 페인트는 피하고 합성단계를 유발하는 css 속성들을 주로 사용한다.

각 단계의 동작을 유발하는 css 속성은 https://csstriggers.com/에서 확인할 수 있다.

정리

  1. 가능하면 js보다는 css 애니메이션을 최대한 활용한다.
  2. 다음 css 속성 위주로 애니메이션 -gpu 가속을 적용한다.(자동으로 적용된다.)
  • transform:translate or scale or rotate
  • opacity
  1. 레이아웃 변경이나 리페인팅을 유발하는 css 속성은 비용이 많이 든다.
  • 레이아웃 : width,height,padding, margin,display
  • 페인트 : color,background,outline,box-shadow
  1. setTimeOut 보다는 requestAnimationFrame, Web Animations API 등 활용
  • requestAnimationFrame: 브라우저가 최적화, 비활성 탭에서는 동작 안 함
  • web Animation API: css애니메이션과 같은 애니메이션 엔진 사용
  1. three.js velocity.js와 같은 고성능 애니메이션 라이브러리 사용

  2. will-change 속성을 통해 브라우저가 최적화 할 속성 명시

  • 남용하면 리소스 낭비,
  1. DOM 접근과 업데이트는 가능한 적게, 한 번에 몰아서 처리,
  • 엘리먼트 추가는 DocumentFragment 활용
1
2
3
4
5
6
7
8
9
10
const app = document.getElementById("app");
const frag = document.createDocumentFragment();

for (let i = 0; i < 1001; i++) {
const el = document.createElement("div");
el.innerText = `Element ${i}`;
frag.appendChild(el);
}

app.appendChild(frag);

리소스

cpu 점유율, 전력 소비량, 스토리지, 메모리 사용량, 네트워크 트래픽 중 메모리 누수와 네트워크 트래픽을 신경써야 한다.

메모리 누수

프로그램이 필요하지 않는 메모리를 계속 점유하는 현상

메모리 생명 주기

  1. 할당 : 사용할 메모리를 확보한다.
  2. 사용 : 메모리를 사용한다.
  3. 헤재 : 불필요한 메모리를 반환한다.

개선 방법

js는 가비지 컬렉션을 통해 메모리를 해제한다.

이 메모리를 몇개의 변수가 참조하는지 카운트하여 0개일 경우 지운다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var x = {
a: {
b: 2,
},
};
// 두 객체 모두 생성된다.
// x 객체를 o1 a객체를 o2라고 하자

var y = x; //y도 o1를 참조한다.

x = 1; // x는 이제 o1를 참조하지 않는다. 이제 o1를 참조하는 변수는 y가 유일하다.

var z = y.a; // o1 내부의 o2을 z가 참조한다.

y = "mozilla"; // y는 o1를 참조하지 않지만 z변수가 o1의 o2를 참조하므로 o1은 제거되지 않는다.

z = null; // 이제 o1과 o2는 아무 곳에서도 참조가 되지않아 가비지 컬렉션이 된다.

순환참조 문제

객체끼리 참조가 맞물려서 가비지 컬렉션이 동작하지 않는 문제

마크스위프 알고리즘을 통해 해결할 수 있게 되었다.

메모리 누수의 대표적 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// case 1 : 큰 전역 변수는 계속 메모리에 존재한다.
const bigData={.../*big data*/};

// case 2 : 클로저는 생성된 실행 컨텍스트의 변수를 해제하지 못하게 한다.
function factory(){
const largeData={.../*big data*/}

return ()=>{
//...
}
}

const fn=factory();
fn();
// fn을 실행한 후에도 여전히 largeData는 메모리에 존재한다.

// case 3: 큰 데이터가 다른 변수에서 참조되면 큰 데이터는 해제되지 않는다.
const data1={linkTo:bigData};
const data2={anotherName:bigData};

개선방안

아래의 코드는 예시일 뿐이다.

실제로는 모듈이나 다른 함수로 분리해서 사용하는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// case 1 : 큰 전역 변수는 계속 메모리에 존재한다.
const bigData={.../*big data*/};

// case 2 : 클로저는 생성된 실행 컨텍스트의 변수를 해제하지 못하게 한다.
function factory(){
const largeData={.../*big data*/}

return ()=>{
//...
}
};

{
const fn=factory();
fn();
}
// fn은 실행 후 현재의 Lexical Enviroment와 함께 해제되고 largeData도 함께 해제된다.

// case 3: 큰 데이터가 다른 변수에서 참조되면 큰 데이터는 해제되지 않는다.
const data1={linkTo:bigData};
const data2={anotherName:bigData};

네트워크 트래픽

파일 용량을 줄이거나 필요할 때만 불러와서 트래픽 낭비를 줄인다.

  1. 최소화된 js,css파일을 사용한다.
  2. 프레임워크는 한 개 이하만 사용한다.
  3. 파일 주소의 파라미터는 주의해서 사용, 의도하지 않은 캐시버스터가 될 수 있다.
  • 브라우저는 전체 url 기반으로 캐시하기 때문에 업데이트가 되어도 받아오지 못할 수 있다.
  • 일부러 랜덤한 숫자를 붙여 새로 캐시되도록 하는 것이 캐시버스터이다.
  1. 이미지, 미디어는 필요할 때 불러오는 게으른 로딩(lazy loading)기법을 사용한다.

댓글