- tscircuit용 오픈소스 PCB autorouter를 약 1년 개발한 경험은 A*, 시각화, 공간 분할, 캐싱처럼 탐색 문제를 줄이는 설계가 성능의 핵심임을 보여줌
- 최적화의 초점은 언어나 반복 1회 속도보다 반복 횟수 감소에 있으며, JavaScript라도 더 똑똑하고 캐시 가능한 알고리듬이면 저수준 구현보다 빠를 수 있음
- 공간 검색에서는 QuadTree 같은 범용 트리보다 Spatial Hash Index가 단순하고 빠를 수 있지만, 셀 크기 선택이 잘못되면 조회마다 높은 고정 비용이 발생함
- 복잡한 autorouter 파이프라인은 단계별 입력·출력을 시각화하고 반복 과정을 애니메이션으로 확인해야 하며, 재귀 함수와 Monte Carlo 방식은 디버깅·최적화·결정성에서 불리함
- A*는 Weighted A* 의 Greedy Multiplier로 최적성을 일부 포기하고 속도를 크게 높일 수 있으며, 각 단계는 이후 단계가 풀기 쉬운 상태를 만들도록 실패 확률을 낮춰야 함
A*를 기본 탐색 도구로 삼기
- A*는 2D 격자 전용 알고리듬이 아니라 여러 형태의 정보 기반 탐색(informed search) 에 쓸 수 있는 기반 알고리듬임
- BFS는 인접 노드를 모두 탐색하지만, A*는 목적지에 더 가까운 노드를 우선 탐색함
- 그래프 바깥의 거리 지표를 사용하므로 정보 기반 탐색에 해당함
- 재귀 알고리듬은 깊이 우선 탐색(DFS)에 가깝고, 후보나 이웃을 정렬하지 않고 탐색하는 루프는 BFS에 가까움
- 기존 BFS나 DFS 형태의 코드를 A*로 바꾸면 큰 성능 향상을 얻는 경우가 많음
- autorouter에서는 여러 수준의 A*를 사용해 문제에 맞는 하이퍼파라미터를 찾음
- 각 autorouter 설정을 후보로 실행함
- 좋은 비용으로 라우팅에 성공하기 시작한 설정에 더 많은 반복을 배정함
- 거리 비용과 반복 비용을 함께 벌점으로 쓰는 meta-A* 형태임
언어보다 알고리듬이 중요함
- tscircuit autorouter는 JavaScript로 작성되고 있으며, 성능 논의에서 언어가 먼저 지적되는 경우가 많음
- 알고리듬 최적화는 크게 두 축으로 나뉨
- 필요한 반복 횟수를 줄여 알고리듬을 더 똑똑하게 만들기
- 각 반복의 실행 속도를 높이기
- 반복 1회의 속도 개선에 과도하게 집중하면, 잘못된 접근을 빠르게 실행하는 데 그칠 수 있음
- 예를 들어 겹침 검사를 위해 모든 것을 격자로 바꾸는 방식은 언어와 관계없이 느려질 수 있음
- 저수준 최적화된 어셈블리의 단순한 알고리듬보다 JavaScript의 똑똑한 알고리듬이 더 빠를 수 있음
- 개발 시간의 95%는 반복 횟수를 줄이는 데 쓰는 편이 좋으며, 가장 똑똑하고 캐시 가능한 알고리듬에 빨리 도달하게 해주는 언어가 좋은 선택임
Spatial Hash Index가 트리보다 나을 수 있음
- 다차원 공간 최적화에서는 QuadTree가 자주 등장하지만, 범용 트리 자료구조는 느릴 수 있음
- QuadTree는 2D·3D 공간에서 가까운 객체 검색을
O(N)에서O(log(N))로 줄이는 자료구조로 알려져 있지만, 트리는 데이터에 대한 정보 기반 표현이 아님 - Spatial Hash Index는 객체 자체가 아니라 객체의 위치를 해시해 셀 또는 가까운 것들의 버킷에 저장함
- 이 접근은 HashSet, HashMap처럼 빠른 해시 기반 접근을 공간 데이터에 적용하는 방식임
- 공간 해시가 덜 대중적인 이유는 적절한 셀 크기를 골라야 하기 때문임
- 셀 크기가 잘못 보정되면 조회마다 높은 고정 비용이 발생함
- 실제로는 합리적인 셀 크기를 고르는 일이 그렇게 어렵지 않다고 봄
공간 분할과 캐싱이 성능을 바꿈
- iPhone 내부 같은 회로 보드는 대략 10,000~20,000개 트레이스를 가질 수 있고, 최고 수준의 EDA 도구로도 팀이 몇 달 동안 라우팅할 수 있음
- autorouting 문제에서 중요한 단순한 아이디어는 이미 라우팅된 것은 이전에도 라우팅된 적이 있다는 점임
- 게임 개발자는 탐색 메시를 미리 굽고, LLM은 검색을 위해 인터넷을 가중치로 압축함
- 다음 세대 autorouter는 문제를 공간적으로 나누고, 이미 풀린 해답을 담은 큰 캐시를 활용할 수 있음
- autorouting 문제의 99%가 캐시에 미리 풀려 있다면 알고리듬 자체의 속도는 덜 중요해짐
- 현재 많은 알고리듬은 캐시 재사용성과 공간 분할에 충분히 집중하지 않음
- 저장과 캐싱 비용은 계산 속도 향상보다 더 빠르게 낮아지는 것으로 보이며, autorouter를 50% 빠르게 만들기 위해 1GB 캐시를 쓰는 것은 큰 문제가 아니라고 봄
시각화와 프로파일링으로 문제를 직접 보기
- 문제에 대한 시각화가 없으면 해결할 수 없다는 원칙이 중요함
- 숫자만 바라보는 방식으로는 디버깅이 어렵고, 작은 하위 문제마다 시각화를 만들면 문제를 훨씬 빠르게 이해할 수 있음
- autorouter 개발에서는 문제 해결을 시각화부터 시작하는 경우도 많음
- 45도 경로를 찾는 하위 알고리듬도 시각화했으며, 이는 autorouter의 거의 마지막 단계인 Path Simplification Phase에서 쓰임
- JavaScript 프로파일링 도구는 각 코드 줄에서 소비한 총 시간을 밀리초 단위로 보여줌
- 브라우저에서 JavaScript를 실행하고 Performance 탭을 열면 됨
- flame chart와 메모리 사용량 기능도 제공됨
- 관련 짧은 영상: youtube short
재귀와 Monte Carlo를 피하기
- 재귀 함수는 성능 지향 코드에서 피하는 편이 좋음
- 거의 항상 동기적으로 동작해 애니메이션을 위해 중간에 끊기 어려움
- 본질적으로 DFS이며 A*로 쉽게 바꾸기 어려움
- 반복 횟수 추적이 쉽지 않음
- 재귀 함수에서는 가변성이 부자연스럽지만, 성능에는 가변성이 중요할 수 있음
- 반복 기반 구현은
visitedNodes집합을 유지하고 탐색 전에 노드를 확인할 수 있어 더 빠를 수 있음 - Monte Carlo 알고리듬은 무작위성으로 해답에 접근하지만, 결정적이지 않아 디버깅이 어렵고 휴리스틱 대비 최적인 경우가 거의 없다고 봄
- 후보를 어떻게 평가할지는 알지만 해답에 도달하는 방식을 모를 때, Monte Carlo 방식은 직관을 얻는 데 도움이 될 수 있음
- 비용 함수에 가까운 것이 생기면 Monte Carlo나 Simulated Annealing 같은 무작위 기법보다 더 나은 방식을 써야 함
- 로컬 최소값에 민감하다면 하이퍼파라미터나 더 복잡한 비용 함수를 고려할 수 있음
- PCB 설계자가 회로 보드 위에 무작위로 선을 긋지 않듯, 이 도메인에서는 더 나은 휴리스틱을 찾을 수 있다고 봄
중간 알고리듬은 같은 좌표계에 두기
- autorouter는 현재 13개 단계와 약 20개 하위 알고리듬으로 구성된 파이프라인임
- 공간 분할 결정이나 독립적으로 autoroute된 구역 경계의 경로 단순화 같은 작업에서 반복 횟수를 측정함
- 각 단계의 입력과 출력을 겹쳐 시각화하면 현재 해결 중인 문제의 맥락을 이해할 수 있음
- downstream 단계, 특히 high density routing 단계의 문제는 이전 단계의 출력을 개선해 해결되는 경우가 많음
- 하위 알고리듬을 만들 때 문제를 가장 단순한 형태로 분리하고 좌표를
(0, 0)주변으로 정규화하고 싶은 유혹이 있음 - 정규화나 복잡한 변환은 초기 단계의 결과가 후속 단계에 미치는 영향을 빠르게 보기 어렵게 만들 수 있음
- 알고리듬 전체 생명주기에서 좌표 공간을 일관되게 유지하는 방식이 유리함
- 각 단계를 순서대로 보고 확대하면 실패한 Design Rule Check의 원인이 되는 단계를 찾는 데 도움이 됨
반복 애니메이션과 격자 회피
- 반복 횟수를 줄이는 것이 중요하므로, 알고리듬 반복을 애니메이션으로 보면 낭비되는 탐색을 직관적으로 파악할 수 있음
- 애니메이션은 특히 Greedy Multiplier를 조정할 때 도움이 됨
- 한 단순 트레이스가 실패해야 할 상황에서 즉시 실패하지 않고 바깥쪽으로 끝없이 해결을 시도하는 사례는 애니메이션 없이는 파악하기 어려웠음
- 두 트레이스 A와 B가 겹치는지 판단하는 방법은 크게 두 가지임
- A와 B의 각 세그먼트를 보고 교차를 확인함
- B가 존재하는 격자를 표시한 뒤 A가 지나는 격자에 B가 있는지 확인함
- 격자 방식은 쉽게 1000배 느려질 수 있음
- 빠른 벡터 수학을 쓰면 단일 격자 칸 확인을 위한 메모리 접근보다 두 세그먼트 교차 판단을 위한 dot product가 더 빠를 수 있음
- 엄밀하게는 적절한 여유 공간을 보장하기 위해 세그먼트 간 거리 계산을 써야 하며, 이는 교차보다 조금 더 복잡하지만 크게 다르지는 않음
실패 확률과 Weighted A*
- 공간 분할 단계에서는 각 단계의 해결 실패 확률을 선행 지표로 측정할 수 있음
- Unravel Autorouter는 주요 파이프라인 단계마다 각 Capacity Node의 실패 확률을 추적함
- 각 단계는 인접 노드 재구성이나 재라우팅을 통해 실패 확률을 줄이는 데 집중함
- 실패 확률은 실제로 측정할 수 있고, 알고리듬이 바뀌면 예측도 개선할 수 있음
- 각 단계가 이후 단계의 실패 가능성을 낮추는 방향으로 동작할 수 있음
- 너무 많은 제약을 한 번에 넣기보다 해결 가능성을 우선하는 편이 나음
- 일단 보드가 풀리면 처음부터 최적 해답을 생성하는 것보다 기존 해답을 다루는 편이 더 쉬운 경우가 많음
Greedy Multiplier로 속도와 최적성 절충
- 기본 A*는 최적 해답을 보장하지만, 속도를 더 중시한다면
f(n)을 조금 바꿔 Weighted A* 를 쓸 수 있음 - 일반 A*:
f(n) = g(n) + h(n) - Weighted A*:
f(n) = g(n) + w * h(n) - Weighted A*는 더 탐욕적으로 문제를 풀며 일반적으로 훨씬 빠르게 동작함
- 이 방식은 최적성을 일부 포기하는 대신 A* 성능을 크게 높이는 Greedy Multiplier 역할을 함
- Weighted A*와 다른 A* 변형은 weighted A* and other A* variants here에서 더 볼 수 있음
- 게임 개발자는 autorouting 개발자와 비슷한 문제를 많이 다루므로, 관련 연구를 찾을 때 게임 개발 논문을 살펴볼 수 있음
공개 예정인 autorouter
- tscircuit용 autorouter는 릴리스에 가까워지고 있음
- 작업물은 MIT 라이선스의 오픈소스로 제공됨
- autorouting 해결은 물리 세계 혁신을 크게 열어줄 수 있고, 전자제품의 “vibe-building”을 가능하게 하는 핵심 조각이라고 봄
- 관련 계정: follow me on twitter.