# 모든 개발자가 SIMD를 알아야 하는 이유

> Clean Markdown view of GeekNews topic #31734. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31734](https://news.hada.io/topic?id=31734)
- GeekNews Markdown: [https://news.hada.io/topic/31734.md](https://news.hada.io/topic/31734.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-07-23T23:35:11+09:00
- Updated: 2026-07-23T23:35:11+09:00
- Original source: [mitchellh.com](https://mitchellh.com/writing/everyone-should-know-simd)
- Points: 1
- Comments: 1

## Topic Body

- **SIMD**는 최고 성능 소프트웨어만을 위한 복잡한 기법이 아니라, 연속된 데이터를 여러 값씩 처리해 평범한 반복문을 가속하는 일상적 최적화 수단임
- 일반적인 SIMD 코드는 **상수 브로드캐스트**, 벡터 단위 순회, 병렬 연산, 결과 축소·저장, 스칼라 꼬리 처리라는 5단계 구조를 따름
- Ghostty의 코드포인트 검색 루프는 한 번에 4·8·16개의 `u32`를 비교하며, 이론상 처리량을 ARM NEON에서 최대 4배, AVX2에서 8배, AVX-512에서 16배까지 높일 수 있음
- AVX2 Intel 데스크톱의 터미널 전체 처리량은 약 **5배 빨라졌으며**, 지원할 벡터 폭이 없거나 입력이 남으면 기존 스칼라 반복문이 전체 입력 또는 나머지를 처리함
- 컴파일러의 **자동 벡터화**는 단순한 반복문에서도 기회를 놓칠 수 있으므로 먼저 최적화된 출력을 확인하되, 중요한 핫 루프는 명시적 SIMD로 동작과 성능을 예측 가능하게 유지할 수 있음

---

### SIMD가 하는 일
- [SIMD](https://en.wikipedia.org/wiki/Single_instruction,_multiple_data)는 CPU가 하나의 명령으로 여러 값을 병렬 처리하게 함
  - 바이트를 하나씩 비교하는 대신 한 번에 4개, 8개 또는 그 이상을 비교할 수 있음
  - `for (byte in bytes)`, `for (character in string)`, `for (value in array)` 같은 반복문을 **벡터 폭 단위 처리**로 바꿀 기회가 있음
- 데이터가 수백·수천·수백만 바이트라면 병렬 폭에 따라 4배, 8배 이상의 국소적 가속을 얻을 수 있음
- 데이터가 몇 개나 수십 개에 불과하다면 SIMD를 적용할 가치가 없음
- [simdutf](https://github.com/simdutf/simdutf)와 [simdjson](https://github.com/simdjson/simdjson)은 복잡한 SIMD 기법을 사용하지만, 일상적인 SIMD까지 이 정도로 복잡할 필요는 없음
- 예제는 Zig를 사용하지만 **5단계 구조**는 다른 언어에도 적용되며, 언어마다 SIMD 명령 지원 방식은 다름

### 반복되는 5단계 구조
1. 필요한 상수를 모든 레인에 브로드캐스트하고, 필요하면 벡터 누산기를 초기화함
2. 입력을 한 번에 **벡터 폭 크기**만큼 순회함
3. 모든 레인에서 비교나 산술 연산을 병렬 실행함
4. 알고리듬에 맞게 벡터 결과를 축소하거나 저장함
5. 완전한 벡터에 들어가지 않는 나머지는 기존 반복문인 **스칼라 꼬리(scalar tail)** 로 처리함
- 이 구조에 익숙해지면 일반 반복문을 같은 5단계로 분해할 수 있어 SIMD 작성도 스칼라 반복문만큼 단순해짐
- 이 구조로 간단히 표현되지 않는다면 당장은 SIMD 적용을 건너뛰는 편이 적절함

### Ghostty의 실제 검색 루프
- Ghostty는 디코딩된 코드포인트 배열에서 `0xF` 이하의 값을 만날 때까지 데이터를 소비함
  - 터미널 데이터 대부분은 출력할 일반 문자이므로 이를 묶어서 처리함
  - 반복문은 다음 출력 가능 구간의 끝을 가능한 한 빨리 찾음
- 원래 스칼라 구현은 코드포인트를 하나씩 검사함

```zig
while (end < cps.len and cps[end] > 0xF) end += 1;
```

- 벡터 구현은 CPU 전용 내장 함수 없이 일반 벡터를 사용하며, 스칼라 구현보다 코드가 12줄 늘어남
- 기대되는 처리량 향상은 벡터 레인 수와 대응함
  - ARM NEON과 Apple Silicon: 최대 **4배**
  - 대부분의 최신 x86 CPU가 지원하는 AVX2: 최대 **8배**
  - 일부 Intel CPU와 AMD Zen 4 이상이 지원하는 AVX-512: 최대 **16배**
- AVX2 Intel 데스크톱에서 터미널 프로그램 입력부터 최종 터미널 상태까지 측정한 전체 처리량은 약 **5배 빨라짐**
  - SIMD 주변 작업 때문에 이론적 가속을 모두 얻지는 못함
- C0 제어 문자는 `0xF` 이후에도 존재하지만, `0xF`는 이 Ghostty 코드 경로에서 사용하는 기준임
  - ESC와 다른 제어 시퀀스는 별도 경로에서 처리함

### 1단계: 상수 브로드캐스트
```zig
if (simd.lanes(u32)) |lanes| {
    const V = @Vector(lanes, u32);
    const threshold: V = @splat(0xF);
```

- Ghostty의 `simd.lanes(u32)`는 대상 CPU가 동시에 처리할 수 있는 `u32` 개수를 반환함
  - 각각의 값은 **레인(lane)** 이라고 부름
  - ARM은 4, AVX2는 8, AVX-512는 16을 반환함
  - 사용할 벡터 크기가 없으면 `null`을 반환해 SIMD 코드를 건너뜀
- `@Vector(lanes, u32)`는 해당 레인 수를 가진 벡터 타입을 생성함
  - `lanes`가 8이면 `V` 하나에 병렬 처리할 수 있는 `u32` 8개가 들어감
- 벡터 비교에는 양쪽 모두 벡터가 필요하므로 `@splat(0xF)`가 `0xF`를 모든 레인에 복제함

```text
{ 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF }
```

- 이 알고리듬에는 벡터 누산기가 필요하지 않지만, 다른 알고리듬은 이 단계에서 누산기를 초기화할 수 있음

### 2단계: 벡터 하나씩 순회
```zig
while (end + lanes <= cps.len) : (end += lanes) {
    const values: V = cps[end..][0..lanes].*;
```

- `lanes`가 8이면 값이 최소 8개 남았을 때만 반복문에 들어가고, 8개를 `values`에 적재함
- 각 반복이 끝날 때 `end`를 1이 아니라 **레인 수만큼** 증가시킴
- 완전한 벡터를 적재할 수 있어야 하므로 값이 5개만 남았다면 8레인 벡터를 읽지 않음
- 벡터에 들어가지 않는 값은 5단계의 스칼라 꼬리가 처리함

### 3단계: 모든 레인의 병렬 비교
```zig
const greater_than_threshold = values > threshold;
```

- `values`와 `threshold`가 모두 벡터이므로 `>`는 각 대응 레인을 하나의 **벡터 연산**으로 비교함
- 8레인이라면 `cps[end] > 0xF`에 해당하는 비교 8개를 병렬 수행함

```text
values:                 { 0x41, 0x42, 0x43, 0x0A, 0x44, 0x45, 0x46, 0x47 }
threshold:              {  0xF,  0xF,  0xF,  0xF,  0xF,  0xF,  0xF,  0xF }
greater_than_threshold: { true, true, true, false, true, true, true, true }
```

- 명시적인 내부 반복문은 없으며, 결과는 레인별 불리언을 담은 벡터임
- 비교뿐 아니라 덧셈, 곱셈, 최솟값, 최댓값 등 벡터 타입이 지원하는 연산에도 같은 구조를 적용할 수 있음
- 비교 자체는 하나의 벡터 연산이지만 벡터 적재, 결과 축소, 실패한 레인 검색에는 추가 명령이 필요함

### 4단계: 벡터 결과 축소
```zig
if (@reduce(.And, greater_than_threshold)) continue;
```

- `@reduce(.And, ...)`는 모든 불리언을 `and`로 결합해 단일 불리언으로 만듦
- 모든 레인이 `true`이면 다음 벡터로 넘어가며, 하나라도 `false`이면 실패한 정확한 위치를 찾음

```zig
const mask: std.meta.Int(.unsigned, lanes) = @bitCast(greater_than_threshold);
end += @ctz(~mask);
break;
```

- `@bitCast`는 불리언 벡터를 **레인당 1비트인 정수 마스크**로 변환함
  - `1`은 값이 `0xF`보다 큼을 뜻함
  - `0`은 비교에 실패했음을 뜻함
- 마스크를 반전하면 실패한 비교가 `1`이 되고, `@ctz`는 첫 번째 `1` 이전의 0비트 수를 셈

```text
values:                 { 0x41, 0x42, 0x43, 0x0A, 0x44, 0x45, 0x46, 0x47 }
greater_than_threshold: { true, true, true, false, true, true, true, true }
mask:                   {    1,    1,    1,     0,    1,    1,    1,    1 }
~mask:                  {    0,    0,    0,     1,    0,    0,    0,    0 }
```

- 이 예제에서 `@ctz(~mask)`는 `3`을 반환하며, `end`를 첫 제어 문자인 `0x0A`가 있는 3번 레인으로 이동시킴
- 결과 축소는 5단계 중 알고리듬마다 가장 크게 달라지는 부분임
  - 합계는 벡터 누산기를 단일 숫자로 축소할 수 있음
  - 변환은 벡터 전체를 출력 버퍼에 저장할 수 있음
  - 이 검색은 비트 마스크를 만들어 특정 레인의 위치를 찾음

### 5단계: 스칼라 꼬리 처리
```zig
while (end < cps.len and cps[end] > 0xF) end += 1;
```

- 입력 길이가 벡터 폭의 정확한 배수가 아니면 원래 스칼라 반복문이 나머지를 처리함
- 8레인 벡터 반복문 뒤에는 0개에서 7개의 값이 남을 수 있음
- `simd.lanes(u32)`가 `null`인 CPU에서는 SIMD 구간을 건너뛰고 스칼라 반복문이 전체 입력을 처리함
- 원래 구현이 **나머지 처리와 호환성 폴백**을 동시에 담당함
- 일반 벡터는 CPU별 문법을 제거할 뿐 CPU별 코드 생성까지 없애지는 않음
  - Zig는 대상에 활성화된 명령어 집합으로 벡터 연산을 변환함

### 자동 벡터화가 놓치는 것
- 컴파일러는 복잡한 제어 흐름이 없는 규칙적인 산술 반복문처럼 단순한 코드를 [자동 벡터화](https://llvm.org/docs/Vectorizers.html)할 수 있음
- 수동 SIMD를 작성하기 전에 스칼라 버전을 최적화 옵션으로 컴파일하고 **생성된 코드**를 확인해야 함
- 프로덕션 컴파일러는 벡터화 기회를 자주 놓치며, 자동 벡터화는 수십 년간 연구됐지만 [최근 연구](https://arxiv.org/abs/2406.04693)도 이 문제에서 출발함
- 5배 가속이 중요할 정도의 반복문이라면 벡터화를 명시적으로 작성해 동작을 예측 가능하게 유지할 수 있음
  - 관련 없는 코드 변경이나 컴파일러 업데이트가 벡터 반복문을 조용히 스칼라 반복문으로 되돌리는 상황을 피할 수 있음

### 개발자가 익혀야 할 SIMD의 범위
- 대량의 연속 데이터를 검색·비교·계수·변환하는 **핫 루프**를 발견하면 벡터 폭 단위 처리를 고려할 수 있어야 함
- 일상적인 SIMD는 상수 준비, 벡터 적재, 병렬 연산, 결과 축소, 스칼라 꼬리라는 규칙적인 형태를 따름
- 언어가 SIMD를 잘 지원한다면 어셈블리나 CPU별 세부 사항을 직접 알지 못해도 성능을 개선할 수 있음
- 모든 개발자에게 필요한 수준은 복잡한 `simdutf`·`simdjson`식 기법이 아니라, SIMD 적용 기회를 인식하고 공통 구조를 활용할 수 있는 정도임

## Comments



### Comment 62279

- Author: neo
- Created: 2026-07-23T23:35:12+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=49010648) 
- 좋은 글이지만, **SIMD가 이해하기 쉽고 for 루프만큼 작성하기 쉽다**고 시작한 뒤 첫 예제부터 스칼라 코드 한 줄을 12줄로 바꾸는 건 설득력이 떨어짐  
  차라리 SIMD는 어렵지만 결과는 가치 있다고 솔직히 말하는 편이 나음. 초보자 대상이라면 1단계부터 `broadcast` 같은 SIMD 전용 용어를 설명 없이 쓰지 말아야 하며, **스칼라 꼬리 처리**를 설명한 5단계는 좋은 구성임
  - SIMD와 첫 예제는 어렵다기보다 훨씬 **번거로운 작업**에 가까움  
    하드웨어가 한 번에 처리할 항목 수를 파악하고, 그 크기로 작업을 묶고, 결과를 다시 풀고, 남은 항목을 별도로 처리하며, 상수도 복제된 벡터로 만들어야 함. 각각은 어렵지 않지만 작업량을 늘려 번거롭게 만듦
  - 1990년경 Transputer 열풍 속에서 만들어진 **Parallel-C**를 배웠는데, 병렬 프로그래밍 기능을 C에 추가한 언어였음  
    가장 좋아한 기능은 일부 경계 조건 아래 컴파일러가 for 루프를 자동 병렬화하는 `par(; ; )`였음
  - 내가 딱 목표 독자에 가까워 흥미롭게 읽었지만, 난도가 너무 빠르게 치솟아 악명 높은 **올빼미 그리기 밈**과 비슷하게 느껴짐
  - SIMD 자체는 단순하며, **스칼라 언어에서 데이터 병렬 연산**을 사용하는 방식이 어색한 것임
  - 기술 교육에서 가장 큰 잘못 중 하나는 주제에 대한 두려움을 없앤다며 단순하다고 선언하는 것임. 단순하다고 말하지 말고 실제로 보여줘야 함  
    실제로 복잡한 주제라면 더 작고 단순한 부분으로 나눈 뒤, 가파른 학습 곡선을 오를 수 있도록 순서를 잘 구성하고 그만한 가치가 있음을 납득시켜야 함

- 더 나은 조언은 모두가 **배열 프로그래밍**을 알아야 한다는 것임. SIMD 최적화에는 대체로 그 사고방식이 필요하고, 패킹 SIMD에만 특화된 기법은 의외로 드묾  
  배열 프로그래밍은 컴파일러가 **자동 벡터화**하기 쉬워 SIMD를 직접 쓰지 않아도 대체로 성능 좋은 코드를 만들어줌
  - 먼저 비교를 모두 수행하고 나중에 첫 실패를 찾는 배열 프로그래밍은 실행 구간이 짧을 때 큰 도움이 안 됨. 자체적으로 **조기 종료**를 제공하지 않아 불필요한 비교에 시간을 많이 쓸 수 있음
  - 폐쇄 소스 언어를 좋아하지 않고 MATLAB에도 결함이 많지만, 대학에서 수치 시뮬레이션용 **벡터화 코드**를 효율적으로 작성하기는 매우 자연스러웠음  
    경험은 많지 않지만 Julia가 비슷한 벡터화 역량을 갖춘 더 현대적이고 표현력 높은 언어에 가장 가까워 보임

- 최근 며칠간 생물정보학 프로젝트의 행렬 연산을 **AVX-512**로 최적화했는데 매우 만족스러움  
  대부분의 애플리케이션은 대규모 데이터셋을 메모리에서 읽는 과정이 병목이므로, 여러 연산을 위해 반복해서 읽는 대신 AVX 레지스터와 **융합 커널**로 한 번에 모두 처리할 수 있음. 5배 속도 향상도 흔하며, 직접 인트린식을 사용했지만 `wide` 크레이트를 쓰면 일반적인 연산이 아주 간단해짐: [https://docs.rs/wide/latest/wide/](<https://docs.rs/wide/latest/wide/>)

- 압도적 다수의 개발자는 SIMD를 배울 필요가 전혀 없음. 왜 모든 개발자가 이를 알아야 진짜 개발자인 것처럼 오해하게 만드는지 의문임
  - 적어도 SIMD의 존재와 가능성은 알아둘 만함. 개발자라면 단순한 값을 더하거나 비교하는 **핫 루프**를 작성해봤을 것이고, 컴파일러가 대상 CPU 아키텍처에 맞춰 이를 최적화할 수도 있다는 지식은 여러 상황에서 유용함

- 제목은 “모두가 **SIMD가 적용되지 않은 시점**을 알아야 한다”로 바꾸는 편이 나음  
  현대 컴파일러는 벡터화를 매우 잘하다가도 가정 하나나 데이터 의존 분기 하나 때문에 갑자기 스칼라 코드로 후퇴함. SIMD 작성법보다 컴파일러의 **최적화 보고서**를 확인하는 법이 더 가치 있을 수 있음
  - 형편없는 자동 벡터화의 해결책이 직접 SIMD 코드를 작성하는 것인데, 최적화 보고서를 보는 법이 정말 그보다 가치 있는지는 의문임  
    문제만 식별할 수 있다면 결국 “안타깝네”에서 끝남
  - 실제로 바로 그런 일이 발생했음: [https://xcancel.com/mitchellh/status/2079672171321081908#m](<https://xcancel.com/mitchellh/status/2079672171321081908#m>)

- 작년에 오디오 신시사이저를 만들며 **x86과 ARM SIMD**를 배우기 시작했음: [https://github.com/seclorum/SIMDSynth](<https://github.com/seclorum/SIMDSynth>)  
  다중 음색·다성 신시사이저 구조는 여러 데이터 흐름에 같은 처리를 적용하므로 SIMD 원칙을 익히기에 매우 적합했음. 다만 디버깅은 상당히 어려워 각 처리 파이프의 상태를 이해할 수 있는 시뮬레이터가 절실했고, SIMD 도구를 조사하려면 또 큰 투자가 필요할 듯함

- 글은 좋고 더 많은 언어가 SIMD를 지원하면 좋겠지만, 가장 인기 있는 두 언어가 SIMD를 **네이티브로 지원하지 않는 상황**에서 “모든 프로그래머가 알아야 한다”는 표현은 다소 이상함
  - 가장 인기 있는 언어가 이런 글의 대상인 **소프트웨어 엔지니어**에게 가장 많이 쓰이는 언어라고 단정하기는 어려움

- 직접 SIMD를 작성하지 않거나 AI에게 맡길 계획이어도, 어떤 작업이 어떤 하드웨어에서 SIMD로 빨라질 수 있는지 알아야 함. 그래야 SIMD 적용이 가능하도록 **알고리즘과 코드 구조**를 설계할 수 있음  
  데이터 의존성의 영향, 벡터 원소 폭 증가 비용과 회피법, 조건과 분기를 마스크로 바꾸는 법, “나눗셈 명령은 없다” 같은 특성은 직접 SIMD를 조금이라도 사용해보면 훨씬 쉽게 체득됨
  - SIMD가 **언제 빨라지는지**는 충분히 다뤄지지 않음  
    큰 연속 데이터를 한 번에 검사하거나 변환할 때는 잘 작동하지만, 입력의 몇 바이트마다 결정을 내려야 한다면 스칼라 방식과 같거나 더 느릴 수 있음. SIMD는 마법의 가속 버튼이 아님

- The Witness 개발팀이 실제 성능 문제를 SIMD로 해결하는 과정을 Casey Muratori가 설명한 유용한 영상임: [https://www.youtube.com/watch?v=Ge3aKEmZcqY](<https://www.youtube.com/watch?v=Ge3aKEmZcqY>)
  - 훌륭한 발표지만 다른 사람에게 추천하기엔 영상이 너무 길어, 핵심에 집중한 **글 버전**이 있으면 좋겠음  
    성능을 위한 수직 통합의 좋은 예로, 일반적인 추상화가 왜 존재하고 왜 범용적이어야 하는지 이해한 뒤 특정 사용 사례에서는 문제 정의부터 SIMD까지 수직으로 통합해 큰 이득을 얻는 과정을 보여줌

- SIMD 같은 미세 최적화에 들어가기 전에 **자료구조와 접근 패턴**부터 진지하게 검토해야 함  
  예전 Zig 코드에 SIMD를 적용했지만 자료구조 모델이 최적화와 정반대여서, 망가진 엔진을 단 고물차에 고성능 경주용 타이어를 끼우는 꼴이었음. 성능을 측정하지도 않고 메모리 할당 위치도 고려하지 않은 전형적인 성급한 최적화였음  
  이제는 데이터를 SQL 테이블처럼 보고 잠재적인 기본 키와 접근 패턴을 중심으로 구조를 설계함. 예전에는 힙의 다른 구조체를 가리키는 트리를 사용해 연결 리스트의 단점, 다수 힙 벡터의 단편화, 느린 생성·해제 비용을 모두 떠안았고 `Drop`만으로도 실행 시간의 상당 부분을 썼음  
  트리는 언제든 선형화할 수 있으므로 접근·삽입 패턴, 실제로 트리인지 다른 그래프인지, `Vec` 또는 여러 `Vec`의 구조체로 저장할지를 검토함. 그 결과 코드는 더 빠르고 단순해졌으며 데이터가 동종 배열에 모여 컴파일러의 SIMD 최적화와 L1 캐시를 활용하기 쉬워지고, 필요할 때 직접 **분기 없는 SIMD 코드**도 작성할 수 있음  
  관련 자료: [https://hn.algolia.com/?dateRange=all&page=0&prefix=true&que...](<https://hn.algolia.com/?dateRange=all&page=0&prefix=true&query=Data-Oriented%20Design%20author%3ARendello&sort=byPopularity&type=all>)
  - 핫 루프를 SIMD로 빠르게 만들려면 **데이터 배치와 캐시 친화적 구조**가 핵심임  
    실제 병목 구간에서는 메모리 할당, 가상 함수 테이블 조회, 과도한 간접 참조를 피해야 함. C++의 `vector`도 예상치 못한 할당을 호출할 수 있다면 항상 최선은 아님
  - 성능 엔지니어로서 늘 마주하는 문제임. **성능은 아키텍처에서 시작**하며, 데이터 배치가 나쁜 핫 경로에서는 짜낼 수 있는 성능에 한계가 있음  
    반면 데이터 지향 코드는 거의 언제나 스레딩과 SIMD를 쉽게 지원함
  - 그보다 더 기본적으로 **메모리 접근 패턴**이 중요함  
    흥미롭게도 CPU 코드도 결국 GPU 스타일로 작성하게 되며, 객체 배열 대신 Parquet식 **배열의 구조체**를 사용하는 것이 한 방법임
  - 테이블은 범용 그래프를 효율적으로 구현하는 방식이며, 그래프를 특수화할 수 없는 한 내가 아는 최선의 표현임
