"Optimizing Javascript for fun and for profit"라는 자바스크립트 최적화 관련 글의 영어 원문과 번역본을 바탕으로, 부가적인 설명을 추가하고 이해하기 쉬운 언어로 정리해봤다. 예시 코드도 조금 더 다듬어서 개선했다.

문자열 비교 피하기 Avoid string comparisons


문자열 비교는 각 문자를 순차적으로 비교해야 하므로 O(n) 시간복잡도를 갖는다. 특히 문자열 enum을 사용하는 것은 성능 최적화 관점에서 피해야 할 패턴 중 하나다.

자바스크립트 엔진에서 정수(Integer)는 일반적으로 값으로 전달되며, 비교 연산 시 바로 값을 확인할 수 있어 빠르게 처리할 수 있다. 반면 문자열은 메모리에 저장된 위치(포인터)를 참조해 값을 가져온 후 비교하기 때문에 추가적인 메모리 액세스가 필요하고 이로인해 더 많은 비용이 발생한다.

// 문자열 enum - 느림
enum Position {
  TOP    = 'TOP',
  BOTTOM = 'BOTTOM',
}
// 정수형 enum - 빠름
enum Position {
  TOP,    // = 0
  BOTTOM, // = 1
}

다른 형태 피하기 Avoid different shapes


함수 파라미터로 객체를 전달할 때, 자바스크립트 엔진은 해당 함수가 항상 동일한 형태(Shape, Hidden Class)를 가진 객체를 전달받는다고 가정하고 최적화를 시도한다. 객체의 속성 개수, 순서, 데이터 타입 등이 일정하게 유지되면 엔진이 최적화된 머신 코드를 생성하여 성능을 극대화할 수 있다.

반면, 다른 형태의 객체(예: 속성의 개수나 순서가 다르거나 데이터 타입이 다른 경우)를 전달하면, 엔진은 기존 최적화를 해제하거나 새로운 최적화 전략을 수립해야 한다. 이로 인해 성능 저하가 발생할 가능성이 높아진다.

예를들어 런타임에 아래 함수가 { x: number, y: number } 형태의 두 객체를 전달받는다고 가정해 보자. 엔진은 이 함수가 앞으로도 동일한 형태의 객체를 받을 것이라고 추측하고, 해당 형태에 대해 최적화된 머신 코드를 생성한다.

function add(a, b) {
  return {
    x: a.x + b.x,
    y: a.y + b.y,
  };
}

const point1 = { x: 1, y: 2 };
const point2 = { x: 3, y: 4 };

console.log(add(point1, point2)); // { x: 4, y: 6 }

위 함수가 일관된 형태의 객체만 처리한다면 최적화가 유지된다. 하지만 아래처럼 다른 형태의 객체를 전달하면, 엔진은 최적화를 해제하거나 다시 설정해야 하므로 성능 저하가 발생할 수 있다.

const point3 = { x: 1, y: 2, z: 5 }; // 추가 속성 z 포함
console.log(add(point1, point3)); // 최적화 해제 가능

또한, 리액트 컴포넌트의 props를 다른 순서나 구조로 작성하는 경우에도 유사한 문제가 발생할 수 있다. 따라서, 객체의 형태를 일관되게 유지하는 것이 중요하다.

배열/객체 메서드 피하기 Avoid array/object methods


함수형 프로그래밍은 코드를 간결하고 가독성 있게 작성할 수 있는 장점이 있다. 그러나 Haskell, OCaml, Rust 등 함수형 코드를 효율적으로 컴파일하는 언어가 아닌 이상 명령형 코드에 비해 성능이 저하되는 경우가 많다.

아래 예시에서 함수형 메서드는 각 단계에서 배열의 복사본을 생성하는데, 이 복사본은 가비지 컬렉터에 의해 처리되어야 하므로 메모리 사용량이 증가한다. 또한 각 메서드 호출마다 배열을 반복 처리(N번의 연산에 대해 N번 반복) 해야하는 단점이 존재한다.