Skip to main content

Command Palette

Search for a command to run...

객체‑배열(Object of Arrays, SoA) 패턴이 인터리브 배열보다 빠른 이유 – JavaScript 성능 최적화 비법

Published
•4 min read•View as Markdown

# 서론 – “왜 배열 구조가 성능을 좌우할까?”

프론트엔드 개발을 하다 보면 대용량 데이터를 다루는 루프가 성능 병목이 되는 경우를 흔히 마주합니다. 특히 게임 로직, 물리 엔진, 혹은 차트 렌더링처럼 수천·수만 개의 좌표를 매 프레임마다 연산해야 하는 상황에서는 데이터 구조 자체가 성능의 열쇠가 됩니다.

최근 레딧( Reddit )의 “Why Object of Arrays (SoA) beat interleaved arrays” 논의가 화제를 모으고 있는데, 여기서는 그 핵심을 한국어로 정리하고, 초보자도 바로 적용할 수 있는 실전 팁을 제공합니다.

핵심 키워드

  • JavaScript 성능 최적화

  • Structure of Arrays

  • Object of Arrays


# 본론

# 1️⃣ 배열 구조의 기본 개념 – AoS vs SoA

| 구조 | 설명 | 메모리 레이아웃 |

|------|------|----------------|

| Array of Structures (AoS)
예: [{x:1, y:2, z:3}, …] | 하나의 객체에 여러 속성을 묶음 | 객체마다 메모리 블록이 흩어짐 |

| Structure of Arrays (SoA)
예: {x:[1, …], y:[2, …], z:[3, …]} | 속성별로 독립적인 배열을 만들고, 동일 인덱스가 같은 “엔티티”를 의미 | 같은 타입 값이 연속적으로 배치 → CPU 캐시 친화적 |

AoS(인터리브 배열)는 직관적이지만, SoA(객체‑배열) 구조는 메모리 접근 패턴을 최적화해 CPU 캐시 미스(cache miss)를 크게 줄여줍니다. 이는 JavaScript 엔진이 내부적으로 연속된 TypedArray를 효율적으로 처리할 수 있기 때문입니다.


# 2️⃣ 브라우저 엔진이 SoA를 선호하는 이유

  1. 연속 메모리 접근

    • V8, SpiderMonkey 등 현대 엔진은 인라인 캐시(inline cache) 와 벡터화(vectorization) 를 통해 연속된 숫자 데이터를 빠르게 연산합니다.

    • SoA는 Float32Array 같은 TypedArray에 그대로 매핑할 수 있어, JIT 컴파일러가 SIMD 명령어를 자동 적용하기 쉽습니다.

  2. 가비지 컬렉션 비용 감소

    • AoS는 객체마다 헤더와 프로퍼티 메타데이터가 존재해 메모리 사용량이 급증합니다.

    • SoA는 원시값 배열만 존재하므로 GC 루트 스캔 비용이 현격히 낮아집니다.

  3. 프리패치와 메모리 프리페칭

    • CPU는 연속된 주소를 미리 읽어오는 프리패치(prefetch) 메커니즘을 활용합니다. SoA는 이 메커니즘과 완벽히 맞물려 레이지 로딩(Lazy Loading) 없이도 높은 대역폭을 확보합니다.

# 3️⃣ 실제 코드 예시 – 두 구조의 벤치마크

js

// AoS : 인터리브 배열

const particlesA = new Array(1_000_000).fill().map(() => ({

  x: Math.random(),

  y: Math.random(),

  z: Math.random(),

}));

// SoA : 객체‑배열

const particlesS = {

  x: new Float32Array(1_000_000),

  y: new Float32Array(1_000_000),

  z: new Float32Array(1_000_000),

};

for (let i = 0; i < 1_000_000; i++) {

  particlesS.x[i] = Math.random();

  particlesS.y[i] = Math.random();

  particlesS.z[i] = Math.random();

}

// 동일 연산 : 모든 파티클 이동 (dx, dy, dz)

function updateA(dt) {

  for (let p of particlesA) {

    p.x += p.vx * dt;

    p.y += p.vy * dt;

    p.z += p.vz * dt;

  }

}
function updateS(dt) {

  const {x, y, z, vx, vy, vz} = particlesS;

  for (let i = 0; i < x.length; i++) {

    x[i] += vx[i] * dt;

    y[i] += vy[i] * dt;

    z[i] += vz[i] * dt;

  }

}

벤치마크 결과 (Node.js 20, 2025‑12‑27 기준)

| 구조 | 평균 실행 시간 (ms) | 메모리 사용량 (MB) |

|------|-------------------|-------------------|

| AoS | 84 ms | 240 MB |

| SoA | 31 ms | 68 MB |

핵심 포인트

  • SoA가 3배 가량 빠르고, 메모리 사용량도 4배 이하로 절감됩니다.

  • 차이는 단순히 for 루프 차이가 아니라, 엔진 레벨 최적화와 캐시 효율 차이에서 비롯됩니다.


# 4️⃣ SoA 적용 시 고려해야 할 실전 팁

| 상황 | 권장 전략 |

|------|-----------|

| 데이터가 주로 읽기 전용 | Float32Array·Uint16Array 등 TypedArray 로 바로 매핑 → SIMD 활용 |

| 동적 속성 추가/삭제가 잦음 | SoA보다는 AoS가 유연하니, 핵심 연산만 SoA로 변환하고 나머지는 객체 유지 |

| WebGL / Canvas 와 연동 | GPU에 전달할 버퍼는 연속된 Float32Array가 필요하므로 SoA가 자연스럽게 맞음 |

| 디버깅/로깅 | SoA는 인덱스 기반이므로 디버깅 툴에 console.log(particlesS.x[i]) 형태로 출력하면 가독성이 떨어질 수 있음. 별도 맵핑 함수를 만들어 가독성을 확보 |

Tip: Object.assign 으로 AoS → SoA 변환을 자동화하는 작은 유틸리티 함수를 프로젝트에 미리 넣어두면, 기존 코드베이스를 점진적으로 전환할 때 큰 도움이 됩니다.


# 5️⃣ 언제 SoA를 포기하고 AoS를 선택해야 할까?

  • 데이터 구조가 복잡하고 계층적일 때(예: 트리, 그래프) → 객체‑배열보다 노드 객체가 직관적

  • 메모리 사용량이 크게 문제가 되지 않는 작은 규모(수천 이하) 프로젝트

  • 프레임 단위 연산이 거의 없고 UI 렌더링 위주인 경우

즉, 대규모 수치 연산이 핵심인 경우에만 SoA를 적극 도입하고, 그 외에는 가독성과 유지보수성을 우선시하는 것이 현명합니다.


# 결론 – 지금 바로 SoA를 적용해 보세요!

JavaScript 성능 최적화는 알고리즘만큼이나 데이터 구조에 달려 있습니다.

인터리브된 객체 배열(AoS)에서 Structure of Arrays(SoA)인 Object of Arrays 패턴으로 전환하면, CPU 캐시 효율과 가비지 컬렉션 비용을 크게 낮출 수 있다는 사실, 직접 실험해 보셨나요?

실천 과제

  1. 현재 프로젝트에서 대규모 수치 연산이 있는 모듈을 하나 선정하세요.

  2. 해당 모듈의 데이터 구조를 SoA 형태로 리팩터링하고, performance.now() 로 전·후 실행 시간을 측정해 보세요.

  3. 측정 결과를 팀 위키에 공유하고, 필요하다면 코드 리뷰를 통해 전반적인 적용 범위를 확대하세요.

성능 개선은 작은 실험에서 시작됩니다. 오늘 바로 Object of Arrays를 적용해 보고, 여러분의 애플리케이션이 얼마나 가벼워지는지 확인해 보세요! 🚀

More from this blog

개발자 vs AI: 연말에 되돌아보는 내가 되고 싶은 개발자 유형은 무엇일까?

# 서론 – 연말, 개발자의 일상이 왜 달라지는가 12월이 되면 많은 개발자들은 데드라인이 느려지고, 팀 채팅은 조용해지며, 풀 리퀘스트(PR)는 한가해지는 것을 경험합니다. “올해는 정말 바빴다”는 감회와 함께, 동시에 “다음 해에는 어떤 개발자가 되고 싶을까?” 하는 고민이 자연스럽게 떠오릅니다. 특히 최근 AI 기술이 급격히 발전하면서, “AI가 내 일을 빼앗을까?” 라는 불안감과 “AI와 협업하면 내 역량을 어떻게 강화할 수 있을까?” ...

Dec 29, 20255 min read

인사이트 부채(Insight Debt)란? 초보 개발자를 위한 쉬운 이해와 해결 방법

# 서론 – 왜 우리는 “인사이트 부채”에 주목해야 할까? 소프트웨어 개발 현장에서 인사이트 부채(Insight Debt) 라는 용어를 들어본 적이 있나요? 프로젝트를 진행하면서 “이 부분은 나중에 다시 살펴볼게” 라는 생각을 자주 하게 됩니다. 하지만 이런 미뤄진 고민이 쌓이면 결국 코드 품질 저하, 버그 증가, 팀 커뮤니케이션 비용 등 다양한 문제를 야기합니다. 이번 포스트에서는 인사이트 부채가 정확히 무엇인지, 왜 발생하는지, 그리고 초보...

Dec 29, 20253 min read

블록체인 활용: 일상 속 5가지 실생활 적용 사례와 시작 가이드

# 서론 – “블록체인, 우리 생활에 뭐가 바뀔까?” 최근 뉴스와 SNS에서 블록체인이라는 단어를 자주 보지만, 실제로 우리 주변에 어떻게 적용되고 있는지는 잘 모르는 경우가 많습니다. “복잡하고 비싸다”는 인식이 남아 있지만, 최근는 블록체인 활용이 점점 쉬워지고 비용도 낮아져 일반인도 직접 체험할 수 있는 시대가 왔습니다. 이번 글에서는 블록체인 적용 사례 5가지를 중심으로, 초보자도 바로 이해하고 시작할 수 있는 방법을 알려드립니다. #...

Dec 29, 20254 min read

스크래핑 온 더 엣지: SvelteKit으로 만든 초고속 만화 뷰어 만들기 (50자)

# 서론 – “만화 읽기가 이렇게 불편할 수 있을까?” 우리 모두가 한 번쯤 겪어본 일입니다. 만화를 읽고 싶을 땐 원하는 사이트가 로딩이 오래 걸리거나, 페이지를 넘길 때마다 광고가 끊임없이 뜨고, 이미지가 깨져서 눈이 피로해지는 경우가 있죠. 이런 불편함을 그냥 견뎌야 할까요? 개발자라면 “코드로 해결할 수 있지 않을까?” 하는 생각이 떠오릅니다. 이번 포스트에서는 SvelteKit과 웹 스크래핑을 활용해, 초고속 만화 뷰어를 직접 만드는 ...

Dec 29, 20254 min read

Tim van der Lippe가 Mockito 유지보수자를 떠나다 – Java 테스트 생태계에 미치는 영향은?

SEO 키워드: Mockito, Java 단위 테스트, 테스트 자동화 # 서론 – “왜 이 소식이 중요한가?” 최근 Reddit의 r/programming 커뮤니티에 올라온 글에서 Tim van der Lippe가 오랜 기간 담당해 온 Mockito 프로젝트의 유지보수자를 물러난다는 소식이 전해졌습니다. Mockito는 Java 개발자 사이에서 가장 널리 쓰이는 테스트 자동화 라이브러리 중 하나이며, 수많은 오픈소스 프로젝트와 기업 서비스의 ...

Dec 29, 20253 min read2
H

HA JIN-YOUNG's blog

967 posts