SIMD 최적화, 2부 · baby steps

ko생성일: 2025. 10. 22.

이전 글의 후속으로, SIMD 최적화에서 비관적·낙관적 접근과 타입 폴리시를 비교하고, 레지스터 할당기·코드 생성기·bailout·벡터 타입 지원을 위한 단계적 작업 계획을 제시한다.

SIMD 최적화, 2부

2013년 11월 22일

이전 글에 대한 짧은 후속 글이다. 내가 제안했던 접근(“박싱 명령을 생성하되 가능하면 우회한다”)은 어떤 의미에서 비관적이다. 즉, 최악의 경우에 필요한 명령을 먼저 모두 만든 뒤 나중에 정리하는 방식이다. 컴퓨터 과학의 많은 문제와 마찬가지로, 이에 상응하는 낙관적 이중(dual)도 있다. 처음부터 언박싱된 데이터를 생성하고, 필요한 지점에만 박스를 삽입할 수 있다. 사실 이를 위한 기존 메커니즘이 있는데, 이를 _타입 폴리시_라고 부른다. 요컨대, 각 MIR opcode가 자신의 입력 타입을 점검하고, 필요에 맞도록 그 타입을 맞춰 보려 하는 단계가 있으며, 이때 필요에 따라 박싱하거나 언박싱한다.

[원글에 달린 댓글][c]에서 Haitao가 더 낙관적인 접근을 제안했다. 예전에도 Luke와 이 점을 간단히 이야기한 적이 있다. 좋은 방향이라고 생각한다. 다만 내가 그것을 바로 제안하지 않았던 주된 이유는, 특히 초기에는 비관적 접근으로 시작하는 편이 더 빨리 손을 댈 수 있겠다고 판단했기 때문이다. 또한 IonBuilder 단계에서 객체(float32x4 래퍼)를 언팩된 값으로 표현하는 것이 어떤 불변식을 깨뜨리지는 않을지 확신이 서지 않았다.

처음부터 래핑된 명령을 만들어 두면 시작하기 쉬울 거라고 생각하는 이유는, 그것들을 bailout에서 그대로 사용할 수 있기 때문이다. 즉, 레지스터 할당기와 코드 생성기에 필요한 변경을 먼저 구현하고 시험해 볼 수 있다. 물론 여전히 임시값을 할당하게 되므로 성능은 시원치 않을 것이다.

비관적 접근은 구현도 더 쉽다. 필요한 코드는 몇 줄이면 충분하다. 요지는 float32x4 값을 언박싱하는 헬퍼를 두는 것인데, 먼저 “이게 박싱된 float32x4인가? 그렇다면 로드 명령을 만들지 말고 입력을 그대로 꺼내 반환하자”라고 검사하는 식이다. 다만 phi를 그만큼 잘 처리하지 못하므로 결과는 최선에 못 미친다. Haitao가 제안하기 전까지는 phi를 다루는 데 낙관적 접근을 쓰는 걸 생각하지 못했다.

어쨌든 두 기법 모두 괜찮은 선택으로 보인다. 변경 범위를 가능한 작게 유지하려면, 우선 비관적 접근으로 시작한 뒤 모든 것이 동작하기 시작하면 낙관적 접근으로 옮겨 가는 편이 좋겠다.

이 접근(먼저 비관적, 이후 낙관적)을 택한다면, 다음이 상위 수준 작업 목록이다. 들여쓰기로 의존 관계를 표시했다.

  • simd 모듈을 추가한다. C++ 코드에 추가하거나(가능하면) 자체 호스팅으로 포팅한다. 여기서는 float32 덧셈의 의미론을 주의 깊게 맞춰야 하지만, Math.fround 덕분에 그리 어렵지 않다.

  • MIR 타입과 연산을 추가한다. 시작은 비관적 최적화를 사용한다

    • 벡터 타입을 처리하도록 레지스터 할당기와 LIR을 보강한다
    • 언박싱된 벡터를 받아 박싱하도록 bailout 코드를 보강한다
      • 타입 폴리시를 변경해 기본적으로 언박싱된 데이터를 생성하도록 구현한다(“낙관적 접근”)
  • 궁극적으로 값 타입의 의미론을 바로잡는다. 지금은 float32x4 값을 객체로 표현하고 있다. 새로운 종류의 값(서로 다른 typeof로 구분된다는 의미에서)을 허용하는 올바른 전략은 버그 654416에서 활발히 논의 중이며, 최종 전략이 정해지면 그에 따를 것이다.