공짜 점심은 끝났다
Herb Sutter의 동시성 혁명: 전문 번역과 해설
CPU가 저절로 성능을 선물하던 시대는 끝났다. 앞으로 더 많은 성능을 원한다면, 소프트웨어가 그 대가를 설계·복잡성·검증의 형태로 치러야 한다.
- 원문: Herb Sutter, “The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software”
- 최초 게시: 2004년 12월. Dr. Dobb’s Journal 2005년 3월호 게재.1
- 번역·해설: 2026-08-25
- 공개 고지: 이 저장소의 기여자가 원저작권자 허락 또는 공개 가능한 라이선스를 확인한 뒤 전문 한국어 번역을 게시했다.
먼저 읽을 핵심 요약
- 과거의 공짜 성능 향상은 클럭 속도 상승이 만들었다. 오래된 단일 스레드 프로그램도 새 CPU에서 자연스럽게 빨라졌다.
- 그 방식은 전력·발열·누설 전류 같은 물리적 한계에 부딪혔다. CPU 제조사는 클럭 경쟁 대신 하이퍼스레딩, 멀티코어, 캐시 확대를 택했다.
- 멀티코어의 추가 성능은 단일 스레드 프로그램에 자동으로 오지 않는다. 코어가 여덟 개여도 순차 프로그램은 대체로 한 코어만 충분히 쓴다.
- 따라서 성능을 계속 얻으려면 프로그램이 명시적으로 동시성을 표현해야 한다. 하지만 이는 레이스 조건, 교착 상태, 공유 상태, 테스트 난이도를 함께 불러온다.
- 결론은 “스레드를 많이 만들라”가 아니다. 병렬화 가능한 목표를 찾고, 공유 상태를 줄이고, 캐시·데이터 배치·알고리즘 효율까지 함께 설계하라는 주장이다.
전문 번역
공짜 점심은 끝났다
소프트웨어에서 동시성으로 향하는 근본적 전환
Herb Sutter 지음
객체지향 혁명 이후 소프트웨어 개발에서 가장 큰 변화가 문을 두드리고 있다. 그 이름은 동시성(Concurrency) 이다.
이 글은 Dr. Dobb’s Journal 30권 3호, 2005년 3월호에 실렸다. 더 짧은 버전은 “The Concurrency Revolution”이라는 제목으로 C/C++ Users Journal 23권 2호, 2005년 2월호에 실렸다.
업데이트 메모: CPU 추세 그래프는 최신 데이터를 포함하고 예측한 흐름이 계속된다는 점을 보이기 위해 2009년 8월 마지막으로 업데이트되었다. 그래프를 제외한 본문은 2004년 12월 이곳에 처음 게시되었을 때의 원문 그대로다.
여러분의 공짜 점심은 곧 끝난다. 무엇을 할 수 있을까? 실제로 무엇을 하고 있는가?
Intel과 AMD부터 Sparc, PowerPC에 이르는 주요 프로세서 제조사와 아키텍처는 CPU 성능을 높이던 전통적 접근 대부분에서 더 갈 곳이 없어졌다. 클럭 속도와 순차 명령어 처리량을 끝없이 올리는 대신, 이들은 일제히 하이퍼스레딩과 멀티코어 아키텍처로 향하고 있다. 두 기능은 이미 칩에 존재한다. 특히 멀티코어는 당시 PowerPC와 Sparc IV에서 사용할 수 있었고, Intel과 AMD도 2005년에 내놓을 예정이었다. 2004년 In-Stat/MDR Fall Processor Forum의 핵심 주제도 멀티코어 장치였다. 많은 회사가 새롭거나 개선된 멀티코어 프로세서를 보였으니, 2004년을 멀티코어의 해라고 불러도 지나치지 않다.
이는 적어도 앞으로 몇 년, 그리고 오늘날 판매되는 소프트웨어 가치의 대부분을 차지하는 범용 데스크톱과 저가 서버용 애플리케이션에서 소프트웨어 개발의 근본적 전환점이 된다. 이 글은 변하는 하드웨어의 모습, 그 변화가 왜 갑자기 소프트웨어에 중요해졌는지, 동시성 혁명이 여러분에게 구체적으로 무엇을 뜻하며 앞으로 프로그램 작성 방식을 어떻게 바꿀지를 설명한다.
어쩌면 공짜 점심은 이미 1~2년 전에 끝났고, 우리는 이제야 그것을 알아차리는 중인지도 모른다.
공짜 성능 점심
“Andy giveth, and Bill taketh away.”라는 재미있는 현상이 있다. 프로세서가 아무리 빨라져도 소프트웨어는 그 추가 속도를 소비할 새 방법을 꾸준히 찾아낸다. CPU가 열 배 빨라지면, 소프트웨어는 대개 열 배 많은 일을 한다. 어떤 경우에는 열 배 비효율적으로 해도 된다고 여길 수도 있다.
대부분의 애플리케이션은 새 버전을 내거나 특별한 일을 하지 않아도 수십 년 동안 무료이면서도 정기적인 성능 향상을 누렸다. 주로 CPU 제조사가, 부차적으로 메모리와 디스크 제조사가 더 새롭고 더 빠른 주류 시스템을 꾸준히 제공했기 때문이다. 클럭 속도는 유일한 성능 척도도, 반드시 좋은 척도도 아니다. 그래도 설명에는 유용하다. 우리는 500MHz CPU가 1GHz로, 2GHz로 넘어가는 흐름에 익숙했다. 당시 주류 컴퓨터는 3GHz 영역에 있었다.
핵심 질문은 이것이다. 언제 끝나는가? 무어의 법칙은 지수적 성장을 예측한다. 그러나 물리적 한계에 닿기 전 지수 성장이 무한히 이어질 수는 없다. 빛도 더 빨라지지 않는다. 성장은 결국 느려지고 멈춘다. 물론 무어의 법칙은 주로 트랜지스터 집적도에 관한 말이다. 그러나 클럭 속도 같은 관련 영역도 비슷한 지수 성장을 보였다. 특히 데이터 저장 용량은 더 빠르게 성장했지만, 그것은 다른 글의 주제다.
소프트웨어 개발자라면 이미 데스크톱 성능이라는 “공짜 점심”의 물결을 타고 있었을 가능성이 크다. 애플리케이션의 어떤 로컬 작업 성능이 한계에 가까운가? 기존의, 다소 의심스러운 통념은 이랬다. “걱정하지 마라. 내일의 프로세서는 처리량이 더 높을 것이다. 게다가 오늘날 애플리케이션은 CPU 처리량이나 메모리 속도 말고도 I/O·네트워크·데이터베이스 같은 요소에 자주 묶인다.” 맞는가?
과거에는 충분히 맞았다. 그러나 예측 가능한 미래에는 완전히 틀리다.
좋은 소식은 프로세서가 계속 강력해진다는 것이다. 나쁜 소식은 적어도 단기적으로 그 성장이 현재 애플리케이션 대부분을 기존처럼 무료로 태워 주는 방향에서는 일어나지 않는다는 점이다.
지난 30년 동안 CPU 설계자는 주로 세 영역에서 성능을 올렸다.
- 클럭 속도
- 실행 최적화
- 캐시
클럭 속도를 높인다는 것은 더 많은 사이클을 얻는다는 뜻이다. CPU를 더 빠르게 돌리면 대체로 같은 일을 더 빠르게 한다.
실행 흐름 최적화는 사이클마다 더 많은 일을 하게 만드는 것이다. 오늘날 CPU는 더 강력한 명령어를 제공하며, 파이프라이닝, 분기 예측, 같은 클럭 사이클에서 여러 명령어 실행, 비순차 실행을 위한 명령어 흐름 재배치 등 평범한 기법부터 복잡한 기법까지 적용한다. 이 기법들은 명령어를 더 잘 흐르게 하고 더 빨리 실행하며, 지연 시간을 줄이고 사이클당 처리 작업을 최대화하려는 것이다.
칩 설계자들은 더 빠른 CPU를 제공해야 한다는 압박을 너무 강하게 받아서, 프로그램을 더 빠르게 만들기 위해 프로그램의 의미를 바꾸거나 깨뜨릴 위험까지 감수한다.
명령어 재배치와 메모리 모델을 잠시 살펴보자. 앞에서 “최적화”라 부른 것 중 일부는 최적화를 넘어선다. 프로그램의 의미를 바꾸고, 프로그래머의 합리적 기대를 깨뜨릴 수 있는 관찰 가능한 결과를 낳는다. 이는 중요하다.
CPU 설계자들은 보통 건전하고 합리적인 사람들이다. 평소에는 여러분의 코드를 해치려 하지 않는다. 하지만 최근 몇 년 동안 이들은 사이클마다 조금이라도 더 빠르게 하려는 공격적 최적화를 추구해 왔다. 그 재배치가 코드의 의미를 위태롭게 할 수 있다는 점을 알면서도 말이다. 이것이 갑자기 악의적인 행동을 하게 된 것일까? 아니다. 더 빠른 CPU를 제공해야 하는 압박이 얼마나 심한지를 보여 주는 신호일 뿐이다.
주목할 예는 쓰기 재배치와 읽기 재배치다. 프로세서가 쓰기 연산의 순서를 바꾸게 하면 매우 놀랍고 많은 프로그래머의 기대를 깨뜨리는 결과가 생긴다. 그래서 임의의 쓰기 재배치가 있는 상황에서 프로그램의 의미를 올바르게 추론하기가 너무 어려워, 일반적으로는 이 기능을 꺼야 한다. 읽기 재배치도 놀라운 결과를 만들 수 있다. 그러나 프로그래머가 감당해야 할 난이도가 쓰기 재배치만큼 크지는 않아 더 자주 켜 둔다. 운영체제와 실행 환경 설계자는 성능 기회를 포기하는 것보다 프로그래머에게 더 큰 부담을 주는 편이 덜 나쁜 선택이라고 보고 타협한다.
마지막으로 온칩 캐시를 키우는 것은 RAM 접근을 피하기 위한 방법이다. 주 메모리는 CPU보다 계속 훨씬 느리므로 데이터를 프로세서 가까이에 둘수록 좋다. CPU 다이 위보다 더 가까운 곳은 없다. 온다이 캐시 크기는 크게 증가했고, 당시 주요 칩 제조사는 2MB 이상의 L2 캐시를 내장한 CPU를 팔았다. CPU 성능을 높여 온 세 가지 전통적 접근 중 가까운 미래에도 계속될 것은 캐시 증가뿐이다.
그렇다면 이것은 무엇을 뜻하는가?
이 목록에서 근본적으로 중요한 점은 세 영역 모두 동시성에 무관하다는 것이다. 이 영역에서의 성능 향상은 순차 실행, 즉 비병렬·단일 스레드·단일 프로세스 애플리케이션에도 직접 성능 향상을 주고, 동시성을 쓰는 애플리케이션도 빨라진다. 오늘날 애플리케이션의 대다수가 단일 스레드라는 점에서 중요하다.
물론 컴파일러도 따라가야 했다. 새 명령어(MMX, SSE 등)와 새 CPU 기능의 혜택을 받으려면 애플리케이션을 다시 컴파일하고 특정 최소 CPU 수준을 대상으로 잡아야 하는 경우가 있다. 그러나 대체로 오래된 애플리케이션도 최신 CPU의 모든 새 명령어와 기능을 활용하도록 다시 컴파일하지 않아도 상당히 빨라졌다.
그 세계는 살기 좋은 곳이었다. 불행히도 이미 사라졌다.
장애물: 왜 아직 10GHz CPU를 쓰지 않는가
우리가 알던 방식의 CPU 성능 성장은 2년 전 벽에 부딪혔다. 대부분의 사람은 최근에야 이를 알아차리기 시작했다.
다른 칩에도 비슷한 그래프를 만들 수 있지만, 여기서는 Intel 데이터를 쓰겠다. 그림 1은 Intel 칩 출시 이력을 클럭 속도와 트랜지스터 수로 나타낸다. 트랜지스터 수는 적어도 당시에는 계속 늘었다. 하지만 클럭 속도는 다르다.
그림 1: Intel CPU 출시 추세 — 그래프는 2009년 8월 업데이트, 본문은 2004년 12월 원문.
2003년 초 무렵부터, 계속 빨라지던 CPU 클럭 속도 추세에 불안할 정도로 급격한 변화가 보인다. 최대 클럭 속도의 한계 추세를 표시하면 기존 경로를 따르지 않고 뚜렷하게 평평해진다. 높은 클럭 속도를 쓰기 어려워진 이유는 하나가 아니라 여러 물리적 문제, 특히 너무 많고 방출하기 어려운 열, 너무 높은 전력 소비, 전류 누설이다.
잠깐 생각해 보자. 여러분의 현재 워크스테이션 CPU 클럭 속도는 얼마인가? 10GHz로 동작하는가? Intel 칩은 2001년 8월에 2GHz에 도달했다. 2003년 전의 추세가 이어졌다면 2005년 초에는 최초의 10GHz Pentium 계열 칩이 나왔어야 한다. 그러나 실제로는 없다. 그런 칩은 지평선에도 없고, 언제 나올지도 알 수 없다.
그렇다면 4GHz는 어떤가? 이미 3.4GHz에 도달했으니 4GHz도 멀지 않아 보인다. 하지만 4GHz조차 매우 먼 목표가 되었다. Intel은 2004년 중반 4GHz 칩 출시를 2005년으로 연기했고, 2004년 가을에는 4GHz 계획을 공식적으로 포기했다. 글 작성 당시 Intel은 2005년 초 3.73GHz까지 조금 더 올릴 계획이었지만, 적어도 당분간 클럭 경쟁은 끝났다. Intel과 대부분 프로세서 제조사의 미래는 다른 곳에 있다. 칩 회사들은 멀티코어라는 새 방향을 적극적으로 추구한다.
언젠가 주류 데스크톱에서 4GHz CPU를 보게 될지도 모른다. 하지만 2005년은 아니다. Intel은 연구실에서 더 높은 속도로 동작하는 칩 샘플을 가졌을 수 있다. 그러나 터무니없이 많은 냉각 장비를 붙이는 식의 영웅적인 노력으로만 가능했다. 사무실은 물론 비행기에서 무릎 위 노트북에 그런 냉각 장비를 붙이게 되지는 않을 것이다.
TANSTAAFL: 무어의 법칙과 다음 세대
“공짜 점심 같은 것은 없다.” — R. A. Heinlein, 『달은 무자비한 밤의 여왕』
그렇다면 무어의 법칙은 끝난 것일까? 흥미롭게도 전반적인 답은 “아직 아니다”인 듯하다. 모든 지수적 성장처럼 무어의 법칙도 언젠가 끝나야 하지만, 앞으로 몇 년 동안 당장 위험해 보이지는 않는다. 칩 엔지니어가 순수한 클럭 사이클 성능을 높이는 데 벽을 만났어도 트랜지스터 수는 계속 폭발적으로 증가하고 있다. CPU는 앞으로도 몇 년간 무어의 법칙과 유사한 처리량 향상을 이어 갈 듯하다.
오해와 현실: 2 × 3GHz는 6GHz보다 작다
3GHz 코어 두 개를 결합한 듀얼코어 CPU는 실질적으로 6GHz 처리 능력을 제공한다고 생각하기 쉽다. 맞는가? 아니다. 두 물리 프로세서에서 두 스레드를 실행한다고 해서 성능이 정확히 두 배가 되지 않는다. 대부분의 멀티스레드 애플리케이션도 듀얼코어에서 정확히 두 배 빨라지지 않는다. 단일 코어보다 빨라져야 하지만, 성능 향상은 선형적이지 않다.
왜일까? 첫째, 캐시와 주 메모리에 대한 일관된 관점을 보장하고 다른 조정을 하기 위한 코어 간 협업 오버헤드가 있다. 2개 또는 4개의 프로세서를 가진 시스템도 멀티스레드 애플리케이션에서 단일 CPU보다 정확히 2배 또는 4배 빠르지 않다. CPU들이 같은 다이에 들어가도 문제의 본질은 같다.
둘째, 두 코어가 서로 다른 프로세스나 거의 기다리지 않고 독립적으로 실행되도록 잘 작성된 스레드를 실행하지 않는 한 충분히 활용되지 못한다. 저자는 당시 단일 스레드 애플리케이션도 듀얼코어에서 다수 사용자에게 성능 향상을 줄 수 있다고 농담 섞어 추측한다. 추가 코어가 애플리케이션에 유용한 일을 해서가 아니라, 사용자의 단일 CPU를 느리게 만드는 애드웨어·스파이웨어를 대신 실행할 수 있기 때문이다. 스파이웨어를 위해 CPU를 하나 더 두는 것이 좋은 해결책인지는 독자의 판단에 맡긴다.
단일 스레드 애플리케이션은 코어 하나만 활용할 수 있다. 운영체제와 애플리케이션을 다른 코어에서 실행하면 어느 정도 빨라질 수는 있다. 하지만 보통 운영체제가 CPU를 계속 최대 사용하지 않으므로 한 코어는 대체로 유휴 상태다.
이 글의 핵심 차이는 적어도 몇 세대의 프로세서 동안 성능 향상이 근본적으로 다른 방식으로 달성된다는 점이다. 현재 애플리케이션 대부분은 큰 재설계 없이는 더 이상 무료 성능 향상의 혜택을 받지 못한다.
가까운 미래의 새 칩 성능 향상은 세 가지 방식으로 이루어진다.
- 하이퍼스레딩
- 멀티코어
- 캐시
하이퍼스레딩은 하나의 CPU 내부에서 둘 이상의 스레드를 병렬로 실행하는 기술이다. 하이퍼스레드 CPU는 일부 명령어를 병렬로 실행할 수 있다. 그러나 추가 레지스터 같은 일부 추가 하드웨어가 있어도 캐시 하나, 정수 연산 장치 하나, 부동소수점 연산 장치 하나 등 기본 자원의 다수가 하나씩뿐이다. 하이퍼스레딩은 적절히 작성된 멀티스레드 애플리케이션에서 5~15%, 이상적 조건에서 신중히 작성된 경우 최대 40%의 성능 향상을 줄 수 있다. 좋은 결과지만 두 배와는 거리가 멀고 단일 스레드 애플리케이션에는 도움이 되지 않는다.
멀티코어는 하나의 칩 위에 실제 CPU를 둘 이상 넣는 방식이다. Sparc와 PowerPC를 포함한 일부 칩에는 이미 멀티코어 버전이 있었다. 2005년 예정이던 Intel과 AMD의 초기 설계는 통합 수준은 달라도 기능적으로 비슷했다. AMD는 지원 기능을 같은 다이에 더 잘 통합하는 초기 설계상 장점이 있어 보였고, Intel의 초기 제품은 기본적으로 Xeon 두 개를 하나의 다이에 붙인 형태였다. 초기 성능 향상은 진짜 듀얼 CPU 시스템과 비슷할 것이다. 메인보드에 소켓 두 개와 연결용 부수 칩이 필요 없어 비용은 더 낮지만, 이상적 경우에도 속도 향상은 두 배보다 작다. 잘 작성된 멀티스레드 애플리케이션에는 도움이 되지만 단일 스레드에는 아니다.
마지막으로 온다이 캐시 크기는 적어도 가까운 미래에 계속 커질 것이다. 이 셋 중 기존 애플리케이션 대부분에 폭넓은 혜택을 주는 것은 캐시뿐이다. 이유는 간단하다.
공간은 곧 속도다.
주 메모리에 접근하는 일은 비싸며, 가능하면 RAM을 건드리지 않는 편이 좋다. 당시 시스템에서 주 메모리까지 가는 캐시 미스는 캐시에서 정보를 가져오는 것보다 흔히 10~50배의 비용이 들었다. 사람들은 메모리가 빠르다고 생각해서 이를 놀라워한다. 메모리는 디스크나 네트워크보다 빠르지만, 더 빠른 온보드 캐시에 비하면 그렇지 않다.
애플리케이션의 작업 집합이 캐시에 들어가면 좋은 상황이고, 들어가지 않으면 좋지 않다. 따라서 캐시 크기가 커지면 상당한 재설계 없이도 일부 기존 애플리케이션을 살리고 몇 년 더 성능을 유지하게 한다. 기존 애플리케이션은 더 많은 데이터를 다루고, 새 기능 코드가 추가되면서 커진다. 성능에 민감한 작업은 계속 캐시에 들어가야 한다. 대공황 시대를 겪은 이들이 빠르게 상기시켜 줄 말처럼, “캐시가 왕이다.”
저자는 컴파일러 팀의 사례를 든다. 32비트와 64비트 컴파일러는 같은 소스 코드를 썼고, 64비트 CPU는 더 많은 레지스터와 기타 코드 성능 특성 덕분에 기본 성능을 크게 높였다. 하지만 데이터는 달랐다. 특히 포인터가 4바이트에서 8바이트로 두 배가 됐다. 이 컴파일러는 내부 데이터 구조에서 포인터를 유난히 많이 썼고, 순수한 데이터 크기 증가가 64비트 컴파일러의 작업 집합을 크게 늘렸다. 그 성능 손실은 더 빠른 프로세서와 더 많은 레지스터로 얻은 코드 실행 이점을 거의 정확히 상쇄했다. 글 작성 당시 64비트 컴파일러는 원본 소스가 같고 순수 처리량이 더 높아도 32비트 컴파일러와 같은 속도로 실행됐다. 다시 말해, 공간은 속도다.
하지만 캐시가 전부다. 하이퍼스레딩과 멀티코어 CPU는 현재 애플리케이션 대부분에 거의 영향을 주지 못한다.
그렇다면 하드웨어 변화는 소프트웨어 작성 방식에 무엇을 뜻하는가? 이제 기본 답은 보였을 것이다.
소프트웨어에 대한 의미: 다음 혁명
1990년대에 우리는 객체를 이해하는 법을 배웠다. 구조적 프로그래밍에서 객체지향 프로그래밍으로의 전환은 지난 20년, 어쩌면 30년 동안 주류 소프트웨어 개발에서 가장 큰 변화였다. 웹 서비스의 탄생 같은 흥미로운 변화도 있었지만, 객체 혁명만큼 소프트웨어 작성 방식을 근본적이고 광범위하게 바꾼 변화는 없었다.
지금까지는 말이다.
이제 성능이라는 점심은 더 이상 공짜가 아니다. 캐시 크기 증가 덕분에 모든 사람이 누릴 일반 성능 향상은 계속될 것이다. 그러나 새 프로세서의 지수적 처리량 향상을 애플리케이션이 얻으려면, 잘 작성된 동시성 애플리케이션, 보통 멀티스레드 애플리케이션이어야 한다. 하지만 모든 문제가 본질적으로 병렬화 가능한 것은 아니고 동시성 프로그래밍은 어렵다.
“동시성이라고? 이미 동시성 애플리케이션을 쓰는 사람이 있지 않나?”라는 반발이 들린다. 맞다. 다만 개발자 중 일부에 불과하다.
사람들은 1960년대 후반 Simula 시절부터 객체지향 프로그래밍을 해 왔다. 하지만 객체지향이 혁명이자 주류가 된 것은 1990년대였다. 산업이 더 큰 문제를 해결하고, 더 커지는 CPU·저장 장원을 쓰는 더 큰 시스템을 만들도록 요구했기 때문이다. 객체지향의 추상화와 의존성 관리라는 강점은 경제적이고 신뢰할 수 있으며 반복 가능한 대규모 소프트웨어 개발에 필수적이 되었다.
동시성은 소프트웨어 작성 방식에서 다음으로 일어날 주요 혁명이다.
마찬가지로 우리는 오래전부터 코루틴, 모니터 등으로 동시성 프로그래밍을 해 왔다. 지난 10년 정도 동시성 시스템을 쓰는 프로그래머도 점차 늘었다. 그러나 동시성으로 향하는 대전환이라는 실제 혁명은 더디게 나타났다. 오늘날 애플리케이션 대다수는 단일 스레드이며, 여기에는 타당한 이유가 있다.
과장에 관해서도 한마디 하자면, 사람들은 늘 자기 새 기술을 “다음 소프트웨어 개발 혁명”이라 선언한다. 믿지 말라. 새 기술은 실제로 흥미롭고 때로 유익하다. 그러나 가장 큰 혁명은 대체로 이미 수년간 존재했고 점진적 성장을 거친 기술에서 나온다. 폭발적 성장 전에 기반이 충분히 성숙해야 하기 때문이다. 견고한 공급업체와 도구 지원을 포함해 기반이 쌓여야 혁명의 토대가 된다. 새 소프트웨어 기술은 성능 절벽과 다른 함정 없이 널리 쓰일 만큼 견고해지는 데 대개 적어도 7년이 걸린다. 객체지향 같은 진정한 혁명은 대개 수년, 때로 수십 년간 다듬어진 기술에서 발생한다. 할리우드의 “하룻밤 사이 성공”도 대개 큰 성공 전 수년간 활동해 온 경우다.
동시성은 다음 주요 혁명이다. 객체지향보다 더 클지에 대해서는 전문가들도 견해가 다르다. 기술자에게 중요한 점은, 동시성이 예상되는 혁명의 규모와 기술의 복잡성·학습 곡선에서 객체지향과 같은 급이라는 사실이다.
동시성의 이점과 비용
동시성, 특히 멀티스레딩이 이미 주류 소프트웨어에서 쓰이는 이유는 크게 두 가지다. 첫째는 자연스럽게 독립적인 제어 흐름을 논리적으로 분리하기 위해서다. 저자가 설계한 데이터베이스 복제 서버에서는 각 복제 세션을 자체 스레드에 두는 것이 자연스러웠다. 같은 데이터베이스 행을 작업하지 않는 한 각 세션이 다른 활성 세션과 완전히 독립적으로 동작했기 때문이다.
둘째 이유는 과거에는 덜 흔했지만 성능이다. 여러 물리 CPU를 확장 가능하게 활용하거나 애플리케이션 다른 부분의 지연 시간을 활용하기 위해서다. 저자의 복제 서버도 별도 스레드가 더 많은 다른 서버와의 복제 세션을 동시에 처리하면서 여러 CPU에서 잘 확장되었다.
그러나 동시성에는 실제 비용이 있다. 명백해 보이는 일부 비용은 상대적으로 중요하지 않다. 예컨대 락을 얻는 데 비용이 드는 것은 맞다. 그러나 작업을 합리적으로 병렬화하고 공유 상태를 최소화하거나 없앨 방법을 찾을 수 있다면, 락을 신중하고 올바르게 쓸 때 동기화로 잃는 것보다 동시 실행으로 얻는 것이 훨씬 크다.
동시성의 두 번째로 큰 비용은 모든 애플리케이션이 병렬화에 적합하지 않다는 것이다.
아마 가장 큰 비용은 동시성이 정말 어렵다는 점이다. 프로그래머가 프로그램을 안정적으로 추론하기 위해 머릿속에 세워야 하는 모델이 순차 제어 흐름보다 훨씬 어렵다.
동시성을 배우는 사람은 모두 처음에는 이해했다고 생각한다. 그러다가 불가능하다고 여겼던 신비한 레이스 조건을 발견하고, 아직 완전히 이해하지 못했다는 사실을 안다. 동시성을 추론하는 법을 배우면서 보통 합리적인 사내 테스트로 그런 레이스를 잡을 수 있다는 점을 알게 되고, 지식과 자신감의 새 단계에 도달한다.
그러나 진짜 다중 프로세서 시스템에서만 드러나는 잠재적 동시성 버그는 대개 테스트에서 잡히지 않는다. 단일 프로세서에서 스레드가 번갈아 전환되는 것이 아니라, 스레드가 실제로 완전히 동시에 실행되어 새 오류 유형을 드러내는 환경이다. 저자는 강도 높은 장기 스트레스 테스트에서도 잘 동작하고 여러 고객 환경에서 완벽했던 애플리케이션이, 실제 다중 프로세서 장비를 가진 고객에게 배포된 날부터 간헐적으로 매우 신비한 레이스와 데이터 손상을 보이는 팀을 많이 봤다고 말한다.
오늘날 CPU 환경에서 멀티코어 장비에서 멀티스레드로 실행되도록 애플리케이션을 재설계하는 것은 수영을 배우기 위해 가장 깊은 곳으로 뛰어드는 일과 비슷하다. 실수를 가장 잘 드러내는 가장 가혹한 진정한 병렬 환경으로 바로 들어가는 것이다. 팀이 안전한 동시성 코드를 쓸 수 있어도, 완전히 안전하지만 단일 코어보다 빨라지지 않는 코드가 있을 수 있다. 보통 스레드가 충분히 독립적이지 않고 하나의 자원에 의존성을 공유해 프로그램 실행이 다시 직렬화되기 때문이다. 매우 미묘한 문제다.
오늘날 프로그래머 대부분은 동시성을 완전히 이해하지 못한다. 마치 15년 전 프로그래머 대부분이 객체를 완전히 이해하지 못했던 것처럼.
구조적 프로그래머가 객체지향을 배우는 일이 큰 도약인 것처럼, 순차적 프로그래머가 동시성을 배우는 일도 비슷한 규모의 도약이다. 레이스 조건은 무엇인가? 교착 상태는 무엇인가? 어떻게 발생하고 어떻게 피하는가? 병렬이라고 생각한 프로그램을 실제로 직렬화하는 구성 요소는 무엇인가? 메시지 큐는 왜 도움이 되는가? 올바른 설계 관행은 왜 올바른가?
오늘날 프로그래머 대다수는 동시성을 아직 완전히 이해하지 못한다. 하지만 동시성 프로그래밍 모델은 배울 수 있다. 특히 메시지와 락 기반 프로그래밍을 고수한다면 그렇다. 한 번 이해하고 나면 객체지향보다 그렇게까지 어렵지는 않고, 객체지향만큼 자연스러워질 수 있다. 다만 자신과 팀의 교육과 학습 시간에 투자할 준비는 해야 한다.
저자는 위 논의를 메시지와 락 기반 동시성 모델로 의도적으로 제한한다. 락 없는(lock-free) 프로그래밍도 있으며 Java 5와 적어도 하나의 널리 쓰이는 C++ 컴파일러가 언어 차원에서 이를 지원한다. 하지만 락 없는 동시성 프로그래밍은 락 기반 동시성보다도 이해하고 추론하기 훨씬 어렵다고 알려져 있다. 대부분의 경우 이는 시스템과 라이브러리 작성자만 이해하면 된다. 다른 개발자는 그들이 만든 락 없는 시스템과 라이브러리를 활용하면 된다. 솔직히 락 기반 프로그래밍도 위험하다.
우리에게 무엇을 의미하는가
다시 우리에게 어떤 의미인지 살펴보자.
1. CPU 처리량 향상을 충분히 활용하려면 애플리케이션은 점점 동시성을 가져야 한다. 이제 등장하기 시작했고 앞으로 몇 년간 이어질 CPU 처리량 향상을 충분히 활용하려면 애플리케이션이 동시성을 갖춰야 한다. Intel이 언젠가 100코어 칩을 만들면 단일 스레드 애플리케이션은 잠재 처리량의 최대 100분의 1만 활용할 수 있다. “성능은 그리 중요하지 않다. 컴퓨터는 계속 빨라지니까.”라는 말은 원래도 의심해야 할 순진한 주장이고, 가까운 미래에는 거의 항상 틀릴 것이다.
CPU 처리량의 계속되는 지수적 향상을 충분히 활용하려면 애플리케이션은 점점 동시성을 갖춰야 한다. 효율성과 성능 최적화는 덜 중요해지는 것이 아니라 더 중요해질 것이다.
모든 애플리케이션, 더 정확히는 중요한 모든 작업이 병렬화 가능한 것은 아니다. 컴파일처럼 거의 이상적으로 병렬화할 수 있는 문제도 있다. 하지만 그렇지 않은 문제도 있다. 한 여성이 아이를 낳는 데 9개월이 걸린다고 해서, 여성 9명이 한 달 만에 아이 하나를 낳을 수는 없다는 비유가 흔하다.
그러나 이 비유에는 빠진 질문이 있다. “인간 아기 생산 문제는 본질적으로 병렬화할 수 없다고 결론 내릴 수 있는가?” 목표가 아이 한 명을 낳는 것이라면 본질적으로 병렬화할 수 없다. 하지만 목표가 많은 아이를 낳는 것이라면 오히려 이상적으로 병렬화할 수 있다. 실제 목표를 아는 일이 모든 차이를 만든다. 소프트웨어를 병렬화할 수 있는지, 어떻게 병렬화할지를 생각할 때 이 목표 중심 원칙을 기억해야 한다.
2. 애플리케이션은 점점 CPU 바운드가 될 가능성이 크다. 모든 작업이 CPU 바운드가 되는 것은 아니며, 이미 그렇지 않은 작업이 하루아침에 CPU 바운드가 되는 것도 아니다. 그러나 전통적 CPU 성능 향상 기법은 한계에 도달한 반면 I/O·네트워크·데이터베이스 성능은 계속 빠르게 좋아지고 있다. 따라서 “애플리케이션은 점점 I/O 바운드·네트워크 바운드·데이터베이스 바운드가 된다”는 추세는 끝에 도달한 듯하다.
당분간 클럭 속도는 3GHz 수준에서 멈춘다. 단일 스레드 프로그램은 캐시 크기 증가의 이점 외에는 크게 빨라지기 어렵다. 칩 설계자가 파이프라인을 채우고 중단을 피하는 새 방법을 찾으면 작은 개선은 있을 수 있다. 그러나 이 영역의 쉽게 얻을 수 있는 개선은 이미 대부분 수확되었다. 새 기능에 대한 요구는 줄지 않을 것이고, 급격히 늘어나는 애플리케이션 데이터를 처리해야 한다는 요구는 더 가속될 수 있다. 프로그램에 더 많은 일을 요구할수록 동시성을 고려하지 못한 프로그램은 CPU가 부족해지는 상황을 더 자주 맞는다.
3. 효율성과 성능 최적화는 더 중요해진다. 이 큰 변화에 대응하는 방법은 둘이다. 애플리케이션을 동시성을 위해 재설계하거나, 더 효율적이고 덜 낭비적인 코드를 쓰는 것이다. 따라서 효율성과 성능 최적화는 덜 중요해지는 것이 아니라 더 중요해진다. 이미 강한 최적화에 적합한 언어는 새 기회를 얻고, 그렇지 않은 언어는 경쟁하고 더 효율적이며 최적화하기 쉬워질 방법을 찾아야 한다. 장기적으로 성능 지향 언어와 시스템의 수요가 늘 것으로 예상할 수 있다.
4. 프로그래밍 언어와 시스템은 동시성을 더 잘 다뤄야 한다. Java는 처음부터 동시성 지원을 포함했지만, 더 정확하고 효율적인 동시성 프로그래밍을 위해 여러 릴리스 동안 고쳐야 했던 실수도 있었다. C++는 대규모 멀티스레드 시스템 작성에 오래 쓰였지만, 당시 ISO C++ 표준에는 동시성 지원이 전혀 없었다. 표준은 의도적으로 스레드조차 언급하지 않았다. 따라서 보통 이식성 없는 플랫폼별 기능과 라이브러리로 구현해야 했고, 종종 불완전했다. 예컨대 static 변수는 한 번만 초기화되어야 하므로 컴파일러가 보통 락으로 감싸야 하지만, 많은 C++ 구현은 그 락을 만들지 않았다.
pthreads와 OpenMP를 포함한 몇몇 동시성 표준도 있으며 일부는 명시적 병렬화와 암묵적 병렬화를 지원한다. 컴파일러가 단일 스레드 프로그램을 보고 자동으로 병렬화 방법을 찾는 것은 좋다. 그러나 자동 변환 도구에는 한계가 있고, 개발자가 직접 명시적 동시성 제어를 쓸 때 얻을 수 있는 향상에는 크게 못 미친다. 주류 기술의 중심은 락 기반 프로그래밍이며, 이는 미묘하고 위험하다. 오늘날 언어가 제공하는 것보다 높은 수준의 동시성 모델이 절실하다.
결론
아직 하지 않았다면 이제 애플리케이션 설계를 면밀히 살펴볼 때다. 현재 CPU에 민감한 작업 또는 곧 CPU에 민감해질 가능성이 있는 작업이 무엇인지 판단하라. 그리고 그 지점이 동시성의 혜택을 어떻게 받을 수 있는지 찾아야 한다. 여러분과 팀은 동시성 프로그래밍의 요구 사항, 함정, 스타일, 관용구를 완전히 이해해야 한다.
일부 드문 종류의 애플리케이션은 자연스럽게 병렬화할 수 있다. 하지만 대부분은 그렇지 않다. CPU 병목이 어디인지 정확히 알아도 그 작업을 어떻게 병렬화할지는 알아내기 어려울 수 있다. 그러므로 지금부터 생각해야 한다. 암묵적 병렬화를 수행하는 컴파일러가 조금 도울 수는 있지만 큰 기대는 하지 말라. 순차 프로그램을 명시적 병렬·스레드 버전으로 바꿀 때 개발자가 할 수 있는 만큼 잘 병렬화하지는 못한다.
계속되는 캐시 성장과 몇 가지 추가 순차 제어 흐름 최적화 덕분에 공짜 점심은 조금 더 이어질 것이다. 하지만 이제 뷔페에는 메인 요리 하나와 디저트 하나만 남는다. 처리량 향상이라는 최고급 스테이크는 여전히 메뉴에 있다. 다만 이제 추가 비용이 든다.
- 추가 개발 노력
- 추가 코드 복잡성
- 추가 테스트 노력
좋은 소식은 많은 종류의 애플리케이션에서 그 추가 노력이 가치 있다는 점이다. 동시성은 프로세서 처리량의 계속되는 지수적 향상을 충분히 활용하게 해 줄 것이기 때문이다.
해설
이 글이 역사적으로 중요한 이유
이 글은 논문이라기보다 산업의 전환점을 정확한 비유로 압축한 기술 에세이다. Sutter와 James Larus는 같은 해 ACM Queue 후속 글에서, 멀티코어의 구조 변화가 기존 순차 프로그램에 더 이상 자동 성능 향상을 주지 못하며 동시성 애플리케이션만 이점을 얻는다고 다시 설명했다.2
Sutter는 2011년 후속 글에서 이 에세이가 단일 코어에서 멀티코어로 넘어가는 이유와, 운영체제·언어·도구를 포함한 소프트웨어 스택 전체가 바뀌어야 하는 이유를 설명했다고 회고했다. 그는 이 전환이 되돌릴 수 없다고 보았고, 이후 논의를 GPU·이기종 코어·클라우드까지 넓혔다.3
그래서 이 글의 지속력은 당시 CPU 수치를 맞혔다는 데만 있지 않다. 하드웨어의 병렬성이 공짜 성능이 아니라 개발자의 설계 문제로 전가된다는 구조를 포착했기 때문이다. 오늘날의 비동기 UI, 서버의 병렬 요청 처리, GPU 컴퓨팅, 데이터 병렬 처리, 분산 시스템은 모두 그 연장선에 있다.
그러나 글을 오늘의 규칙처럼 그대로 읽어서는 안 된다. “모든 코드는 멀티스레드여야 한다”는 뜻이 아니다. 병렬화가 이득인 부분을 찾고, 변경 가능 공유 상태를 줄이고, 작업 단위·데이터 지역성·실패와 취소·테스트 방식을 함께 설계하라는 경고다. 순차 코드가 더 단순하고 정확한 곳에서는 순차 코드가 여전히 좋은 선택이다.
비평적 해설
전문 번역은 원문의 주장과 온도를 옮기는 작업이므로 특정 현존 작가의 고유 문체를 덧씌우지 않았다. 대신 이 해설에서는 독자적인 비평 문체로 한 가지 역설을 강조할 수 있다.
“공짜”는 비용이 사라졌다는 말이 아니다. 비용이 눈에 보이는 장부에서 다른 장부로 옮겨갔다는 말일 수 있다. 과거에는 칩 설계자가 복잡한 파이프라인과 클럭 상승으로 성능 비용을 떠안았다. 멀티코어 이후 그 비용의 일부는 개발자의 설계, 동기화, 테스트, 디버깅, 운영으로 이동했다. CPU가 선물을 거둔 것이 아니라 청구서를 다른 수신자에게 보낸 셈이다.
TANSTAAFL: “공짜 점심은 없다”의 실제 유래
Sutter의 제목은 그가 만든 말이 아니다. 그는 널리 알려진 경제적 격언을 소프트웨어 성능의 비유로 빌렸다. 그리고 이 말의 “원조”는 한 사람으로 단정하기 어렵다.
1. 처음의 free lunch는 정말 공짜처럼 보이는 술집 판촉이었다
19세기 미국의 술집은 술을 사는 손님에게 무료 음식을 제공했다. 음식값은 사라진 것이 아니라 술값과 추가 구매에 기대고 있었다. 그러므로 free lunch는 자선의 반대말이라기보다, 비용이 다른 항목으로 옮겨진 판매 전략이었다.45
2. 격언은 한 명이 발명했다고 확인되지 않는다
정확한 최초 발화자를 확정하기는 어렵다. 조사 자료는 1897년의 문자 그대로의 용례, 1909년 “누군가는 지불해야 한다”는 비유적 설명, 그리고 1938년 Walter Morrow의 「Economics in Eight Words」를 중요한 이정표로 제시한다. 후자의 우화에서 마지막 경제학자는 경제학의 지혜를 여덟 단어로 압축한다. “There ain’t no such thing as free lunch.” 다만 이것은 현재 확인된 가장 이른 일반 경제 격언으로서의 용례이지, 사상 전체의 단일 발명자를 확정하는 증명은 아니다.45
3. TANSTAAFL 약어도 Heinlein의 창작이 아니다
TANSTAAFL은 There Ain’t No Such Thing As A Free Lunch의 머리글자다. Robert A. Heinlein의 1966년 소설 『달은 무자비한 밤의 여왕』이 이 말을 대중적으로 각인시킨 것은 맞다. 그러나 어원 조사에서는 약어가 적어도 1949년의 풍자 소책자 제목 TANSTAAFL: A Plan for a New Economic World Order에 이미 나타난다고 본다. 따라서 Heinlein은 창안자라기보다 가장 강력한 대중화자다.45
4. Milton Friedman도 원조가 아니라 확산자다
Milton Friedman은 1975년 에세이집 제목 There’s No Such Thing as a Free Lunch로 이 격언을 경제학의 상식처럼 널리 퍼뜨렸다. 하지만 그보다 앞선 용례가 확인되므로 Friedman에게 최초 발명을 돌리는 것도 정확하지 않다.4
5. Sutter가 빌려 온 뜻
이 격언은 “무료는 사기다”라는 단순한 냉소가 아니다. 어떤 이득이든 자원·노동·위험·기회비용 가운데 적어도 하나는 누군가가 부담한다는 뜻이다. Sutter는 이를 성능에 적용한다. 클럭 속도 상승이 멈춘 뒤에도 더 높은 처리량은 가능하다. 다만 그 대가는 하드웨어의 자동 선물이 아니라, 병렬 설계·복잡도·검증의 비용으로 나타난다.
오늘의 실무자가 가져갈 질문
- 이 작업은 정말 병렬화 가능한가, 아니면 단순히 동시에 보이기만 하는가?
- 병렬화 목표는 지연 시간 단축인가, 처리량 증가인가, UI 응답성인가?
- 작업 간 공유 상태를 없애거나 메시지로 바꿀 수 있는가?
- 병렬화의 비용이 성능 이득보다 큰 곳은 어디인가?
- 캐시·메모리 접근·데이터 배치·알고리즘을 먼저 고치면 더 쉬운 해법이 되는가?
- 레이스·교착·취소·재시도·관측 가능성을 실제 테스트에 포함했는가?
각주와 참고 문헌
Footnotes
-
Herb Sutter, The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software. ↩
-
Herb Sutter and James Larus, Software and the Concurrency Revolution, ACM Queue, 2005. ↩
-
Herb Sutter, Welcome to the Jungle, 2011. ↩
-
Quote Investigator, There Ain’t No Such Thing as a Free Lunch — TANSTAAFL. ↩ ↩2 ↩3 ↩4
-
Wordorigins.org, free lunch (no such thing as) / TANSTAAFL. ↩ ↩2 ↩3