프론트엔드 성능

Yudrey·2022년 5월 8일
1

패스트캠퍼스 강의를 정리한 내용입니다.
"The RED : 프론트엔드 Back to the Basics : 지속 가능한 코드작성과 성능 향상법 by 김태곤"


출처: 패스트캠퍼스 강의 "The RED : 프론트엔드 Back to the Basics : 지속 가능한 코드작성과 성능 향상법 by 김태곤"

코드 바깥의 성능

초기 구동 시간

파일 다운로드

파일 갯수와 용량은 작게, 도메인은 분산, 적절한 포맷 사용

  1. 최신 브라우저는 대체로 도메인 당 6개의 접속만 동시 처리 (HTTP/1.1 기준)
    • RFC2616 Section 8.1.4에 최대 갯수를 2개로 정의
    • 다운로드 할 파일 갯수가 한 도메인에 6개 이상이면 다운로드 지연
    • 별도 도메인과 CDN등으로 분산하는 것도 방법
브라우저 버전도메인 당 최대 접속 갯수
인터넷 익스플로러 108
인터넷 익스플로러 1113
그 외 최신 브라우저6
  1. 이미지는 최신 포맷 사용 시 용량 절약
    • WebP는 JPEG 파일의 평균 약 70% 정도 크기
    *Safari에서는 WebP를 지원하지 않음
<!-- 지원 타입에 따라 다른 이미지 로딩 -->
<picture>
 <source srcset="img/photo.webp" type="image/webp">
 <img src="img/photo.jpg" alt="my photo">
</picture>
  1. 화면 크기와 해상도에 따라 적절한 이미지 로딩
<!-- 지원 타입에 따라 다른 이미지 로딩 -->
<picture>
 <source srcset="img/photo.webp" type="image/webp">
 <img src="img/photo.jpg" alt="my photo">
</picture>

<!-- 화면 크기에 따라 적절한 이미지 로딩 -->
<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/icon144.png 2x"
>
  1. 웹 폰트 최적화
    • 구글 폰트: 나눔 고딕 등 한국어 폰트 26종 제공
    • WOFF2는 WOFF, TTF 등에 비해 30% 용량이 작다.
    • 필요한 글자만 골라서 글꼴을 만들 수도 있다.
  1. 화면 크기 등에 따라 필요한 스타일 시트만 로딩
<link href="mobile.css" rel="stylesheet" media="all">
<link href="desktop.css" rel="stylesheet" media="screen and (min-width: 600px)">
  1. Link 태그 만으로 접속 시간 절약
<link rel="dns-prefetch" media="https://taegon.kim">
<link rel="preconnect" media="https://cdn.example.com">

로딩 속도 개선

  1. 필수 컨텐츠가 아니라면 비동기 로딩을 고려해보자
    (광고, 댓글, 헤더/푸터 등)
  2. 이미지/아이프레임/스크립트 등은 필요할 때까지는 읽지 않는
    게으른 로딩Lazy loading 기법을 고려해보자
<img src="imgae.jpg" loading="lazy">
<iframe src="https://example.com" loading="lazy">
  1. 시간이 많이 걸린다면 플레이스 홀더 등으로 대체

계산 시간

웹 워커, 느긋한 계산, 메모이제이션 등이 자주 사용됩니다.

웹 워커

"자바스크립트는 싱글 스레드이다.
하지만 멀티 스레드가 가능하다."

Worker thread의 특징

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

index.js

<script>
const worker = new Worker('worker.js');
function manipulateImage(type) {
   if (type === "revert") {
      return context.putImageData(original, 0, 0);
   }
 
 	disableButton(true);
 
   const imageData = context.getImageData(0, 0, canvas.width,
  canvas.height);

  	worker.postMessage({ type, imageData });

  	worker.onmessage = (e) => {
   // e.data에 imageData 저장
   
      context.putImageData(imageData, 0, 0);

      disableButton(false);
   
	};
}
</script>

worker.js

<script>
importScripts("image-manips.js");
this.onmessage = (event) => {
   const { imageData, type } = event.data;
   try {
     const length = imageData.data.length / 4;
     for (
       let i = 0, j = 0, ref = length, r, g, b, a, pixel;
       0 <= ref ? j <= ref : j >= ref;
       i = 0 <= ref ? ++j : --j
     ) {
       r = imageData.data[i * 4 + 0];
       g = imageData.data[i * 4 + 1];
       b = imageData.data[i * 4 + 2];
       a = imageData.data[i * 4 + 3];
     
     	pixel = manipulate(type, r, g, b, a);
        
       imageData.data[i * 4 + 0] = pixel[0];
       imageData.data[i * 4 + 1] = pixel[1];
       imageData.data[i * 4 + 2] = pixel[2];
       imageData.data[i * 4 + 3] = pixel[3];
     }
     postMessage(imageData);
   } catch (e) {
     postMessage(undefined);
     throw new Error("Image manipulation error");
   }
};
</script>

느긋한 계산

Lazy Evaluation
값이 필요해지기 전까지 계산을 미뤄두는 기법.
게으른 평가, 지연 평가라고도 한다.

메모이제이션

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


반응 시간

지나치게 느린 애니메이션 및 응답 속도는 사용자 경험을 저해한다.
1. 도허티 임계(Doherty Threshold)
1982년 Walter J. Doherty, Ahrvind J. Thadani. IBM Systems Journal에 발표.
시스템의 응답이 400ms 보다 느리면 주의력 분산, 생산성 하락이 발생함

  1. 사용자는 UI가 100ms 이하로 반응해야 자신이 UI를 다루고 있다고 느낀다.
    Jacob Nielsen "Response Times: The 3 Important Limits"
  2. 애니메이션은 60 FPS를 기준으로 한다.
    현존하는 모니터가 대부분 60Hz 기준으로 동작하기 때문에 가장 자연스럽다

애니메이션 성능


출처: 패스트캠퍼스 강의 "The RED : 프론트엔드 Back to the Basics : 지속 가능한 코드작성과 성능 향상법 by 김태곤"

→ 애니메이션이 필요한 경우, Layout이나 Paint 단계를 유발하는 CSS 속성 사용 지양하고 Composite 단계를 유발하는 것만 사용하는 것이 좋음

각 단계의 동작을 유발하는 CSS 속성 참고 사이트 : https://csstriggers.com/

고성능 애니메이션을 위한 TIP

  • 가능하면 JS보다는 CSS 애니메이션을 최대한 활용
  • 다음 CSS 속성 위주로 애니메이션 - GPU 가속 적용
    • Transform : translate or scale or rotate
    • opacity
  • 레이아웃 변경이나 리페인팅을 유발하는 CSS 속성은 비용이 많이 든다.
    • 레이아웃: width, height, padding, margin, display 등
    • 페인트: color, background, outline, box-shadow 등
  • setTimeout 보다는 requestAnimationFrame, Web Animations API 등 활용
    • requestAnimationFrame: 브라우저가 최적화. 비활성 탭에서는 동작 안함
    • Web Animations API: CSS 애니메이션과 같은 애니메이션 엔진 사용
  • three.js, velocity.js와 같은 고성능 애니메이션 라이브러리 사용
  • will-change 속성을 통해 브라우저가 최적화 할 속성 명시
    • 남용하면 리소스 낭비. MDN에서는 '최후의 수단'으로 생각하라고 권고
  • DOM 접근과 업데이트는 가능한 적게. 한 번에 몰아서 처리.
    • 엘리먼트 추가는 DocumentFragment 활용

JavaScript 코드의 성능

자원 소모 리소스

결과가 같을 때 자원을 더 적게 소모하는 것이 좋은 성능이다.
스크립트 언어는 언어 자체에서 관리해주는 것이 많으므로 CPU, 전력, 메모리 등은 크게 신경쓰지 않아도 된다.

CPU 점유율

무한루프가 아닌 이상 크게 걱정할 게 없음


전력 소비량

브라우저가 배터리의 용량을 확인할 수 있는 API를 제공하고 있지만, 스크립트 언어가 전력 소비량에 미치는 영향은 미미함


스토리지 용량

파일을 저장하는데 있어서 크게 신경을 쓰지 않음
프론트엔드 영역이 아니거나 스토리지 영역을 요즘에는 거의 신경쓰지 않음


메모리 사용량

자바스크립트에서는 필요하지 않은 것을 자바스크립트 엔진이 판단하는데, 엔진이 판단하므로 필요한 시점을 정확히 판단하기는 어렵고 추정만 가능

메모리 생명 주기

Memory life cycle
할당(Allocate Memory) → 사용(Use Memory) → 해제(Release Memory)

  1. 할당(Allocate Memory) : 사용할 메모리를 확보
  2. 사용(Use Memory) : 메모리 사용(읽기, 쓰기)
  3. 해제(Release Memory) : 불필요한 메모리 반환

메모리 누수

Memory leak : 프로그램이 필요하지 않은 메모리를 계속 점유하는 현상
자바스크립트에서는 C, C++ 언어와 달리 JS 엔진이 "추정" 후 해제하는데, 이 과정을 GC(Garbage Collection) 라고 함

이 때 필요하지 않다는 것을 어떻게 확인할까?
참조 카운트(Reference Counting) 방식을 사용

순환 참조 : 객체끼리 참조가 맞물려서 가비지 컬렉션이 동작하지 않는 문제
→ 브라우저들이 마크 스위프(Mart-and-sweep) 알고리즘을 적용하여 고민 해결

메모리 누수 예시

<script>
// 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 = { anotehrName: bigData };
</script>

전역변수나 클로저는 잘 사용하고 제때 해제하는 것이 중요하다.
데이터를 사용할 때는 데이터가 필요한 최소 범위를 유지하는 것이 좋다.
예를 들어, 클로저를 사용할 때도 전역범위가 아닌 다른 함수 내에서 사용한다면 그 함수가 실행을 종료하면서 클로저가 사라지고 동시에 클로저가 참조하고 있던 상위 실행 컨텍스트도 사라짐. 따라서 가능하면 다른 함수나 모듈 등으로 분리해서 사용하는 것이 좋다.


네트워크 트래픽

브라우저로 전달되는 트래픽 최소화하기
파일 용량을 줄이거나 필요할 때만 불러와서 트래픽 낭비 줄이기

트래픽 최소화 방법

  1. 최소화(minified)한 JS, CSS 파일 사용

  2. 프레임워크는 한 개 이하만 사용

  3. 파일 주소의 파라미터는 주의해서 사용
    잘못 사용하면 의도하지 않은 캐시 버스터(cache buster)가 될 수 있음
    버전이 바뀌거나 파일의 내용이 바뀌었을 때, 브라우저는 전체 URL을 기준으로 파일들을 캐시하고 있음
    그런데 가끔 똑같은 주소라면 내부 컨텐츠가 바뀌었어도 웹브라우저가 인식을 못하는 경우가 있음
    브라우저 캐시라는 건 원래 속도를 빠르게 하기 위해서 만드는 건데, 캐시 버스터를 의도하지 않게 잘못된 방식으로 사용하면 브라우저가 불필요하게 동일한 컨텐츠를 여러번 불러오는 결과를 초래할 수 있으므로 주의!

    캐시 버스터(cache buster)란?
    프레임워크나 다른 자바스크립트 또는 CSS 파일을 불러올 때, 뒤에 랜덤한 숫자 또는 해시 스트링을 추가해서 해당 파일이 다시 캐시 될 수 있도록 만들어주는 것을 말함.
    캐시를 부순다는 의미에서 캐시 버스터.

  4. 이미지, 미디어는 필요할 때 불러오는 게이른 로딩(lazy loading) 기법 사용


요약 정리

  1. 브라우저가 읽는 용량을 줄여야 한다. (캐시, 게으른 로딩)
  2. JS 코드 실행이 느리면 로직 개선 및 최적화 기법을 도입해 볼 수 있다. (게으른 계산, 메모이제이션)
  3. 자바스크립트도 멀티 스레드 프로그래밍이 가능하다. (웹 워커)
  4. 사용자의 행동에는 100ms 내 반응을 보여야 한다.
  5. 애니메이션 성능의 기준은 60fps이다.
  6. 너무 모든 걸 다 적용하려고 집착할 필요는 없다. (성능보다 지속 가능한 코드 작성에 신경쓰기)
profile
Frontend Developer

0개의 댓글