JavaScript 코드 성능을 향상시키는 방법
Source: Dev.to
1. Understand the JavaScript Execution Model
현대 JavaScript 엔진(V8, SpiderMonkey, JavaScriptCore)은 Just‑In‑Time(JIT) 컴파일, 인라인 캐싱, 숨겨진 클래스/쉐이프, 그리고 추측 최적화에 의존합니다.
Avoid De‑Optimization Triggers
엔진은 관찰된 타입을 기반으로 함수들을 최적화합니다. 실행 중에 타입을 변경하면 디옵티마이즈가 발생합니다.
// Avoid this
function sum(a, b) {
return a + b;
}
sum(1, 2); // optimized for numbers
sum("1", "2"); // de‑optimizes
// Do this
function sum(a, b) {
a = Number(a);
b = Number(b);
return a + b;
}
2. Shape Stability and Object Access
JavaScript 객체는 숨겨진 클래스를 사용합니다. 객체 구조를 동적으로 변경하면 속성 접근 최적화가 방해됩니다. 대규모 데이터셋을 순회할 때, 형태가 안정적이면 20~50 % 정도 성능 향상을 얻을 수 있습니다.
// Avoid this
const user = {};
user.name = "Alice";
user.age = 30;
user.isAdmin = true;
// Do this
const user = {
name: "Alice",
age: 30,
isAdmin: true
};
3. Minimize Garbage Collection Pressure
가비지 컬렉션(GC)은 실행을 일시 중지합니다. 높은 할당률은 빈번한 GC 사이클을 초래합니다. 핫 경로에서 불필요한 할당을 피하세요.
// Excessive allocation
function process(items) {
return items.map(item => ({
id: item.id,
value: item.value * 2
}));
}
// Object reuse
function process(items) {
const result = new Array(items.length);
for (let i = 0; i < items.length; i++) {
const item = items[i];
result[i] = {
id: item.id,
value: item.value * 2
};
}
return result;
}
Loop vs. Functional Methods
// Using map / filter / reduce
const total = items
.filter(p => p > 10)
.map(p => p * 1.2)
.reduce((a, b) => a + b, 0);
// Optimized loop
let total = 0;
for (let i = 0; i < items.length; i++) {
const p = items[i];
if (p > 10) {
total += p * 1.2;
}
}
5. Async Performance: Avoid Accidental Serialization
async/await는 잘못 사용하면 숨겨진 병목을 초래할 수 있습니다. 적절한 병렬 처리를 사용하면 실행 시간을 O(n × latency)에서 O(max latency)로 줄일 수 있습니다.
// Serialized execution
async function fetchAll(urls) {
const results = [];
for (const url of urls) {
results.push(await fetch(url));
}
return results;
}
// Parallel execution
async function fetchAll(urls) {
return Promise.all(urls.map(fetch));
}
6. Memoization and Cache Invalidation
결정적이고 비용이 많이 드는 함수에 대해 재계산을 피하세요. 메모이제이션은 캐시 크기 제한과 명확한 무효화 규칙과 함께 사용해야 하며, 그렇지 않으면 메모리 누수가 이득을 상쇄합니다.
const cache = new Map();
function expensiveCalc(n) {
if (cache.has(n)) return cache.get(n);
let result = 0;
for (let i = 0; i < n; i++) {
result += Math.sqrt(i);
}
cache.set(n, result);
return result;
}
DOM Batching (Read/Write Separation)
// Bad: interleaved reads and writes
elements.forEach(el => {
el.style.width = el.offsetWidth + 10 + "px";
});
// Good: batch reads then writes
const widths = elements.map(el => el.offsetWidth);
elements.forEach((el, i) => {
el.style.width = widths[i] + 10 + "px";
});
8. Measure, Don’t Guess
사용 가능한 도구와 API를 활용해 성능을 측정하세요. 측정 없이 최적화하는 것은 카고 컬트 엔지니어링과 같습니다.
performance.now()- Chrome DevTools Performance tab
- Node.js
--prof - Flamegraphs
const start = performance.now();
// critical code
const end = performance.now();
console.log(`Execution: ${end - start}ms`);
당신의 코드는 이 시스템들과 협력하거나 적극적으로 대립합니다. 안정된 타입, 안정된 형태, 제어된 메모리 할당, 그리고 의도적인 async 동작은 엔진이 적극적으로 최적화하도록 합니다.