Software is slow because of bad design, not slow languages
소프트웨어가 느린 이유는 느린 언어가 아니라 나쁜 설계다
언어를 C나 Rust로 바꿔도 대부분의 프로그램은 I/O 대기나 이미 최적화된 라이브러리 때문에 크게 빨라지지 않는다고 주장합니다. 프로파일링으로 병목을 찾고 쿼리 수를 줄이거나 작업을 비동기화하는 등 설계를 바꾸는 편이 효과적이라는 조언입니다.
- 주제
에디터 노트
성능 개선을 재작성보다 프로파일링과 설계 변경부터 찾으라는 조언은 N+1 질의를 단일 질의로 바꾸거나 불필요한 작업을 생략하는 구체적인 사례와 잘 맞습니다. 다만 글의 출발점인 CRUD 서비스가 대체로 I/O-bound라는 주장은 저자 자신의 Rails flamegraph 경험에 기대며, 댓글에는 데이터베이스가 아니라 직렬화와 JSON 처리에서 CPU가 병목이 된 사례와 5ms 질의가 500ms 응답으로 이어지는 반론이 나옵니다. 언어를 바꿔도 빨라지지 않는다는 결론으로 넓히기보다, 병목을 측정하고 작업량과 데이터 흐름을 먼저 바꾸라는 실무 지침으로 읽는 편이 정확합니다. 댓글은 메모리 사용량, 스레딩, 객체 생성과 언어 간 변환 비용도 성능에 영향을 준다고 지적합니다. 빠른 언어의 생태계가 더 효율적인 설계를 가능하게 한다는 반론까지 고려하면, 설계와 언어 선택은 분리된 변수가 아닙니다. 글도 이 점을 인정하지만, 재작성만으로 얻는 속도 향상은 측정 없이 단정할 수 없습니다.
AI 요약
언어를 바꾸면 소프트웨어가 빨라진다는 기대와 달리, 성능 개선의 큰 몫은 프로그램이 하는 일을 줄이는 설계에서 나온다는 주장입니다. 저자는 AI 에이전트에게 코드를 다른 언어로 다시 쓰게 하기보다 프로파일링으로 병목을 찾게 하라고 권합니다.
언어 재작성만으로 얻는 속도 향상은 제한적입니다
간단한 CRUD 웹사이트에서 요청 처리 시간의 절반을 데이터베이스 질의가 차지한다면, 나머지 절반을 C로 바꿔 두 배 빨라지는 것이 이론상 최대치입니다. 실제로는 약 1.5배에 그칠 가능성이 큽니다. 저자는 많은 프로그램이 데이터베이스, 외부 API, 디스크, 네트워크 응답을 기다리는 I/O-bound 작업이라고 설명합니다. CPU를 쓰는 작업도 Python의 NumPy처럼 최적화된 C 라이브러리에 맡기는 경우가 많아, 바깥 코드를 C로 바꿔도 성능 차이가 작을 수 있습니다.
병목을 찾아 적은 일을 하도록 바꿉니다
저자가 경험한 주요 개선은 느린 외부 호출 캐싱, 요청 처리 경로 밖에서 작업하기, 순차 작업의 병렬화, N+1 쿼리를 단일 SQL 질의로 바꾸기, 불필요한 질의 생략 등입니다. 모두 언어 자체보다 작업의 순서와 양을 바꾸는 방법입니다. 다만 CPU-bound 코드가 실제 병목이라면 빠른 언어로 다시 쓰는 일이 효과를 낼 수 있습니다. Actual Computer는 tiktoken을 어셈블리로 다시 작성해 노트북에서 약 5배, 주장에 따르면 4~23배 빨라졌습니다. 저자는 이 향상도 Rust 자체보다 정규식 의존성을 없앤 재설계에서 나왔을 가능성을 언급합니다.
언어 선택과 자료구조도 성능에 영향을 줍니다
Bryan Cantrill이 2018년에 소개한 사례에서는 C 프로그램을 Rust로 옮기며 단순한 자료구조를 선택했지만, 결과는 예상과 달리 30% 빨라졌습니다. 저자는 Rust 표준 라이브러리의 B-tree가 이진 탐색 트리보다 캐시 효율이 높았던 점을 이유로 듭니다. 언어를 바꾸면 더 나은 설계를 택하기 쉬워질 수 있고, 생태계와 런타임도 결과에 영향을 줍니다. 저자도 언어 변경에 이식성, 효율, 유지보수성 같은 이유가 있음을 인정하지만, 속도만을 위해 처음부터 재작성하는 방식은 좁은 경우에만 유효하다고 주장합니다.
Lobsters 반응
- @snej — 웹 서버를 중심으로 모든 현실의 프로그램을 바라보는 또 다른 서버 개발자군요. 서버 밖에는 I/O-bound 프로그램도 있지만, 브라우저 레이아웃 엔진, 3D 렌더러, 워드 프로세서, 신경망처럼 그렇지 않은 프로그램도 많습니다.
- @dlisboa — 서버 쪽에서도 주장은 맞지 않습니다. 데이터베이스 질의는 약 5ms인데 평균 응답은 어째서 500ms나 걸릴까요? 사람들이 당연하게 여기는 CPU-bound 작업과 오버헤드가 많습니다.
- @gcupc — 회전식 디스크를 쓰던 2006년 무렵에는 서버 요청이 데이터베이스 질의에 좌우된다는 말이 훨씬 잘 맞았습니다. 지금은 그 경우가 드물 것 같습니다.
- @mpweiher — 서버에서도 한동안 맞지 않는 말입니다. SSD는 빠르고 프로그램의 I/O 경로도 실제로 CPU-bound일 때가 있습니다. 성능을 측정하지 않고 이야기하면 우스울 정도로 틀린 결론에 이르기 쉽습니다.
- @untitaker — CLI는 사용자가 --help를 입력한 뒤 기다리는 시간도 바로 체감합니다. 느린 언어에서 빠른 언어로 바꿔도 데이터 전달과 객체 생성에 시간이 들며, 모든 문제를 파일 형식 변환처럼 단순하게 라이브러리로 넘길 수는 없습니다. Python의 느린 작업을 C로 바꾸는 방식도 전체 재작성보다 나은 결과를 보장하지 않습니다.
- @weberc2 — Python에서 느린 부분만 다른 언어로 옮기면 언어 간 데이터 변환에 시간이 더 들어 성능이 나빠지는 경우를 봤습니다. 이를 제대로 하려면 게스트 언어의 자료구조를 Python 코드 전반에서 써야 할 때가 많습니다.
- @swaits — 큰 설계 변경이 중요하다는 점은 맞지만 Ruby, Lua, Perl, PHP, 대부분의 Python 코드를 다른 언어로 옮기면 훨씬 빨라집니다. 재작성하면서 설계도 함께 고치면 됩니다.
- @martinald — 동적 언어 서비스의 CPU와 메모리 사용이 커서 서버를 늘리고 복잡한 캐시를 얹은 뒤, 정적 타입 언어로 옮기자 서버 CPU 사용량이 크게 줄어든 사례를 여러 번 봤습니다.
- @nrposner — 고수준 언어가 프로그램 동작을 한눈에 파악하기 쉽다는 주장에는 동의하기 어렵습니다. 추상화와 동적 디스패치 때문에 실제 동작을 확인하기 어렵고, Rust로 일부를 다시 쓰면 데이터 흐름을 파악하는 데 도움이 되기도 합니다.
- @Jackevanse — 사람들이 LLM에 서비스를 다른 언어로 바꾸게 하는 이유는 프로그램 설계를 고민하고 싶지 않아서일 수 있습니다. 언어 재작성 결과는 공정한 비교가 아닌 경우가 많습니다.
- @singpolyma — 느린 것은 언어가 아니라 구현입니다. 같은 의미의 프로그램이라면 충분히 똑똑한 컴파일러가 같은 기계어를 만들 수 있습니다.
- @nrposner — 언어 의미론은 구현 가능한 성능에도 제약을 줍니다. 동적 타입과 디스패치, 메모리 배치 제어의 차이를 구현 세부만으로 없앨 수는 없습니다.
- @icefox — 1999년부터 거의 같은 이야기를 들어왔습니다. 그때도 틀린 말은 아니었지만, 그렇다고 전적으로 옳았던 것도 아닙니다.
원문: Sean Goedecke / 번역·요약: Trawling