# 객체 배열(Object of Arrays, SoA) 패턴이 인터리브 배열을 이긴 이유 – JavaScript 성능 최적화 비밀

---

# # 서론 – “왜 내 코드가 느려지는 걸까?”

JavaScript 로 게임 로직을 짜거나 대규모 데이터 처리를 할 때, **배열 안에 객체** 형태(예: `[{x:…, y:…}, …]`) 로 데이터를 저장하면 직관적이고 코딩도 편합니다. 하지만 실제 실행 속도는 기대에 못 미치는 경우가 많습니다.

최근 Reddit에 올라온 *“Why Object of Arrays (SoA pattern) beat interleaved arrays”* 토론을 보면서 많은 개발자들이 **Structure of Arrays (SoA)**, 즉 **Object of Arrays** 패턴이 메모리 접근과 CPU 캐시 활용 측면에서 훨씬 효율적이라는 사실을 확인했습니다.

이 글에서는 초보자도 이해할 수 있도록 **왜 SoA가 인터리브(Interleaved) 배열보다 빠른지**, 그리고 실제 프로젝트에 바로 적용할 수 있는 방법을 단계별로 살펴보겠습니다.

---

# # 본론

## # 1️⃣ 인터리브 배열이란? – 객체를 배열에 섞어 넣는 방식

```
js

// 인터리브( AoS, Array of Structures )

const particles = [

  { x: 10, y: 20, vx: 1, vy: -1 },

  { x: 15, y: 25, vx: 0, vy: 2 },

  // …

];

```

- **장점**: 가독성이 뛰어나고, `particles[i].x` 같은 접근이 직관적입니다.

- **단점**: 루프를 돌면서 `x`, `y`, `vx`, `vy`를 차례로 읽을 때, CPU는 메모리의 서로 다른 위치를 계속 뒤섞어 접근합니다. 이때문에 **CPU 캐시 미스**가 빈번히 발생해 성능이 저하됩니다.

## # 2️⃣ Object of Arrays (SoA) 패턴 – 데이터를 열(column) 단위로 분리

```
js

// SoA (Structure of Arrays)

const particleX = new Float32Array(count);

const particleY = new Float32Array(count);

const particleVX = new Float32Array(count);

const particleVY = new Float32Array(count);

```

- **핵심 아이디어**: 같은 속성값을 하나의 연속된 메모리 블록에 모아두어 **연속 접근**이 가능하게 함.

- **장점**

  1. **CPU 캐시 효율** – 한 번에 여러 `x` 값을 읽어 캐시 라인에 적재하면, 같은 루프 안에서 `x`만 계속 사용할 때 캐시 히트율이 급상승합니다.

  2. **SIMD 최적화** – `Float32Array` 같은 TypedArray는 브라우저 엔진이 자동으로 SIMD 명령어를 활용해 벡터 연산을 수행합니다.

  3. **메모리 사용량 감소** – 객체 헤더(프로퍼티 이름 등) 오버헤드가 사라져 전체 메모리 풋프린트가 작아집니다.

## # 3️⃣ 실제 성능 차이를 보여주는 간단 실험

아래 코드는 두 가지 구조를 10 000 000개의 파티클에 대해 100번 업데이트하는 벤치마크 예시입니다.

```
js

// 인터리브 버전

function updateAoS(particles) {

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

    const p = particles[i];

    p.x += p.vx;

    p.y += p.vy;

  }

}

// SoA 버전

function updateSoA(x, y, vx, vy) {

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

    x[i] += vx[i];

    y[i] += vy[i];

  }

}

```

Chrome DevTools의 **Performance** 탭으로 측정하면, 동일한 하드웨어에서 **SoA가 평균 2~3배 빠른** 결과가 나옵니다. 특히 모바일 브라우저에서 캐시가 작아질수록 차이는 더욱 커집니다.

## # 4️⃣ 언제 SoA를 선택해야 할까? – 적용 가이드

| 상황 | 권장 구조 | 이유 |

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

| **대량의 동일 속성 데이터** (예: 물리 엔진, 파티클, 이미지 픽셀) | **Object of Arrays** | 연속 메모리 접근 → 캐시 효율 ↑ |

| **데이터가 복잡하고 서로 다른 타입** (예: UI 컴포넌트 리스트) | **Array of Objects** | 가독성·유지보수성 ↑ |

| **WebGL 혹은 Canvas와 연동** | **TypedArray 기반 SoA** | GPU에 바로 전달 가능, 버퍼 복사 최소화 |

| **데이터 변형이 드물고 읽기 중심** | **SoA** | 읽기 전용 루프에서 최적화 효과 극대화 |

## # 5️⃣ SoA 전환 팁 – 기존 코드를 부드럽게 마이그레이션하기

1. **속성 리스트 정의**

   
```
js

   const props = ['x', 'y', 'vx', 'vy'];

   
```

2. **데이터 초기화 함수**

   
```
js

   function createSoA(count) {

     const soa = {};

     for (const p of props) soa[p] = new Float32Array(count);

     return soa;

   }

   
```

3. **기존 AoS → SoA 변환**

   
```
js

   function convertAoSToSoA(aos) {

     const count = aos.length;

     const soa = createSoA(count);

     for (let i = 0; i < count; i++) {

       const p = aos[i];

       for (const key of props) soa[key][i] = p[key];

     }

     return soa;

   }

   
```

4. **루프 로직 교체**

   기존 `particles[i].x` 를 `particleX[i]` 로 바꾸면 끝!

   코드가 길어질 경우 **ES6 모듈**이나 **함수 래퍼**를 활용해 가독성을 유지하세요.

---

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

- **JavaScript 성능 최적화**는 작은 구조 변화에서 시작됩니다.

- **Structure of Arrays**(Object of Arrays) 패턴은 **CPU 캐시**와 **SIMD** 활용을 통해 인터리브 배열보다 **2~3배** 빠른 실행 속도를 제공합니다.

- 대규모 데이터 처리, 게임 로직, WebGL 연동 등 **성능이 중요한** 프로젝트라면 지금 바로 SoA로 전환해 보세요.

> **실천 팁**: 프로젝트에 `benchmark.html` 파일을 하나 만들어, 기존 AoS와 SoA 각각의 실행 시간을 측정해 보세요. 직접 눈으로 확인하면 전환의 필요성을 팀원에게 설득하기 쉬워집니다.

---

## # 📢 행동 유도

- **코드 스니펫을 직접 실행**하고 성능 차이를 체감해 보세요.

- 이 글을 **북마크**하고, 팀 회의 때 **SoA 적용 방안**을 제안해 보세요.

- 더 많은 **JavaScript 성능 최적화** 팁을 원한다면, 댓글이나 구독을 통해 알려 주세요!

---

**SEO 키워드**: JavaScript 성능 최적화, Structure of Arrays, Object of Arrays
