Lisp, Smalltalk 같은 고수준 언어가 기성 RISC급 하드웨어 대비 실리콘 면적 손해 없이 훨씬 빠르게 돌 수 있게 만드는 구체적 하드웨어/컴파일러 설계를 제시하라고 요구하는 글. 막연한 ‘폰 노이만의 한계’ 담론을 비판하며, 특수 명령, 태깅, GC, 함수형 언어, 메모리, 비(非) 폰 노이만 대안 등을 검토한다.
당신은 (매우) 고수준 언어를 사랑하는가? Lisp, Smalltalk, Python, Ruby? 아니면 Haskell, ML? 나도 고수준 언어를 사랑한다.
기성 하드웨어가 ‘뇌가 망가짐’/‘C를 돌리려고 만든 것’/‘(다른 멋진 무언가가 아닌) 폰 노이만 머신’만 아니었다면, 고수준 언어가 빨랐을 거라고 생각하는가? 그렇다면 당신을 위한 도전 과제가 있다. 아마 흥미로울 것이다.
배경:
내 도전은 이렇다. HLL을 지원하도록 하드웨어와/또는 컴파일러가 어떻게 설계되어야 하는지 안다고 생각한다면, 잠깐 언급하는 데 그치지 말고 실제로 우리에게 들려달라. 요구 사항: 당신의 아키텍처는, RISC 류의 명령을 내보내는 컴파일러 대비, 물리적 크기에 큰 손해 없이 HLL 코드를 훨씬 더 빠르게 실행할 수 있어야 한다. 다시 말해, 실리콘 몇 제곱밀리미터가 주어졌을 때, MIPS 코어 같은 것으로 채우는 대신 당신의 코어로 채우면, 성능 손실 없이(상한 25%면 합리적이라 본다) 훨씬 더 고수준 방식으로 내 앱들을 구현할 수 있어야 한다. 저정밀 벡터화 연산에 대한 내장 지원이 있으면 보너스 점수.
당신의 아키텍처가 이 요구를 충족한다면, 나는 물리적 구현을 매우 진지하게 고려하겠다(우리도 그런 게 필요하기 때문이다). 잘 되면, 사람들에게 당신 아이디어가 어떤 모습인지 보여줄 수 있도록 칩을 하나 받게 될 것이다. 약속은 못 한다. 늘 그렇듯 아이디어의 이론적 기술적 미덕 말고도 작용하는 힘이 많기 때문이다. 다만 당신의 아이디어가 훌륭했고 내가 구현하고 싶었다고 공개적으로 밝히겠다는 것만은 약속하겠다. 크진 않지만, 내가 드릴 수 있는 최선이다.
아무 생각이 떠오르지 않는다면, ‘멍청한 하드웨어’ 운운하던 당신의 일관된 주장들은 멍청한 허풍일 뿐이다. 우리를 위해 입 좀 다물어라. 경고: 나는 하드웨어를 직접 만들 순 없지만, 내 주변에는 뛰어난 하드웨어 해커가 아주 많고, 실제 칩이 어떻게 만들어지는지와 제약 조건이 무엇인지도 보아 왔다. 허튼소리하지 마라, 친구.
진지하게 말한다. HLL 찌질이들의 허풍은 이제 진절머리가 난다. 게다가 겉으로 봐선 믿을 만하고 지독히 유능해 보이는 사람들에게서 그런 소리가 나오면 더더욱.
Alan Kay, Smalltalk의 창시자: “여담이지만 흥미로운 벤치마크 하나를 들자면 — 대략 같은 시스템에서, 대략 같은 수준으로 최적화한 1979년의 Xerox PARC 벤치마크가 오늘날엔 고작 50배 빨라졌을 뿐이다. 그 사이 무어의 법칙은 대략 4만에서 6만 배의 향상을 줬다. 즉 나쁜 CPU 아키텍처 때문에 효율이 대략 1000배 날아갔다.” … “그걸 폰 노이만 컴퓨터로 컴파일할 수 있느냐 없느냐 같은 건 신경 쓰지 않을 것이다. 마이크로코드는 우리가 필요로 하는 일을 하게 만들어 이런 비효율을 우회할 것이다. 왜냐면 많은 비효율이 구식 하드웨어 아키텍처에 맞추느라 생기는 거니까.”
Jamie Zawinski, Mozilla의 저자: “큰 애플리케이션에선, 좋은 가비지 컬렉터가 malloc/free보다 더 효율적이다.” … “좋은 GC를 당신이 좋아하는 언어와 잘 맞물리게 본 적이 없다고 해서 GC라는 개념 자체를 탓하지 마라.” 또 다른 곳에선: “리스프가 본질적으로 느리다, 혹은 C보다 느리다는 건 오해다” … “C나 C++로 큰 프로젝트를 하려고 들면, 결국 리스프 런타임의 대부분을 다시 만들게 될 것이다”
Steve Yegge, 뛰어난 테크 블로거: “폰 노이만 머신은 계산을 수행하는 유명한 추상 모델인 튜링 머신을 1950년대에 편리하고 비용 효율적으로 구현한 것이다.” … “뉴럴 네트워크나 셀룰러 오토마타를 편리하게 구현한 다른 종류의 컴퓨터도 있지만, 아직은 그만큼 대중적이지 않다.” 그리고… “폰 노이만 아키텍처는 유일한 것도 아니고, (큰 그림에서 400년 스케일로 보면) 그리 오래 가지도 않을 것이다.”
와. 눈부시고 시야가 트이는 소리처럼 들리지 않는가? 하지만 기술적 디테일은 하나도 없다. 열린 마음이 중요하다는 건 맞다. 정말 그렇다. 어떤 게 ‘실용적’으로 보이지 않는다고 해서 생각하거나 말하지 말란 뜻은 아니다. 하지만 무언가가 무언가조차 아니고, 그저 어썸한 쿨함에 대한 흐릿한 관념뿐이라면, 그건 독자의 머리를 오염시킨다. 절벽을 마음만 먹으면 훌쩍 뛰어넘을 수 있다는 식의 영성 타령과 비슷하다(뉴에이지에는 그런 게 있는 걸로 안다만, 내가 그쪽엔 밝지 않다). 결과는 딱 셋 중 하나다:
이 위대한 고수준 하드웨어 타령도 똑같다. 나는 무시하거나, 그게 나타날 때까지 영원히 기다리거나, 혹은 스스로 해보려다 비참하게 실패할 수 있다. 진지하게, 이 주장들을 좀 더 들여다보자.
Alan Kay는 오늘날 CPU가 얼마나 한심한지 보여주는 벤치마크를 언급한다. 정말 그 벤치마크를 보고 싶다. 그가 그 글에서 칭찬한 B5000도 살펴봤다. 그리고 그 아키텍처를 현대적으로 구현한다고 해서, 원시 효율 면에서 요즘 CPU를 이길 거라곤 생각하지 않는다. 보라, RISC가 괜히 등장한 게 아니다. 아주 대략적으로는 이렇다:
문자열을 지원하고 문자열 비교 명령을 넣고 싶다고 하자. ‘하드웨어에서 해준다’고 생각하면 눈부시게 빠를 것 같나? 아니다. 하드웨어는 여전히 메모리에 접근해야 하고, 사이클당 한 워드씩 읽어야 한다. 슈퍼스칼라/VLIW 어셈블리 루프도 똑같이 빨리 돌 것이다. 당신이 절약하는 건 인코딩 몇 바이트뿐이다. 반면, 그 문자열 비교 따위가 여러 골칫거리를 만든다:
사람들이 어셈블리를 직접 쓰던 시절에는, 복잡하고 고수준스러운 명령을 넣는 게 어느 정도 자연스러웠다. 프로그램 대부분을 컴파일러가 쓰게 되면서(컴파일이 충분히 싸져서), 프로세서는 오히려 덜 고수준이 되었다. 위의 이유들이 왜 그런지 설명이 되었으리라 본다.
그리고 데이터 워드 태깅은 입에도 올리고 싶지 않다. B5000은 다형적 마이크로코드를 썼다 — 두 워드를 로드하고, 타입 비트를 보고, 런타임 타입에 따라 더했다. 그런데 B5000은 부호 없는 8비트 정수 같은 걸 지원하지 않았다. 예를 들면 _이미지_를 그렇게 저장하기 때문이다. 각 바이트마다 태그 비트를 달고 다니란 말인가? _그건 비용이 든다_는 점을 지적하고 싶다. 그리고 이런 류의 저수준 다형성이, Lisp나 Smalltalk식 동적 바인딩의 비용을 압도한다고도 생각하지 않는다(B5000은 Algol을 돌리려고 설계됐다; Smalltalk을 돌리려면 무엇을 하겠는가?).
또 다른 관점: Alan Kay는 B5000이 거의 죽지 않는다고(크래시가 거의 없다고) 말하는데, 그건 그 머신이 돌리도록 설계된 비즈니스 앱에는 정말 잘 맞았다. 멋지다고 생각한다, 정말로(나는 코어 덤프를 무더기로 떠내 봤다). 사실, 현대 데스크톱 OS와 웹 브라우저를 안전하지 않은 언어로, 안전하지 않은 하드웨어 위에 구현한 사람들은, 오늘날 실제 보안 문제의 절대다수에 직접적 책임이 있다고 본다. 하지만 (1) 많은 시스템에선 성능이 정말 정말 중요하고 (2) JVM이나 .NET 식의 소프트웨어 레벨 보안이, 바이트마다 태깅하는 것보다 전체 비용이 더 낮다고 생각한다(2번은 덜 확신한다. 그 VM들의 속사정을 잘 모르기 때문이다). 어쨌든, 하드웨어로 안전을 보장하는 건 비용이 크다고 본다. 그 점을 인정해야 한다(아니면, 이 직관적인 가정을 왜 틀렸는지 진짜로 보여줘야 한다. 즉, 디테일로 파고들어야 한다).
JWZ의 ‘리스프는 기성 하드웨어에서도 효율적일 수 있다’는 주장도, ‘스몰토크는 커스텀 하드웨어에서 효율적일 수 있다’는 주장보다 나을 게 없다고 본다. 어떻게 그럴 수 있나? Lisp의 정적 애노테이션 시스템을 쓰면, 코드는 Java보다 못생겨지고, 안전성은 훨씬 낮아진다(리스프가 파라미터 타입을 정적으로 검사하지 않는다고 본다. 그냥 오브젝트를 건네주고 그걸 정수라고 믿게 놔둔다). 리스프를 애초에 매력적으로 만드는 그 ‘리스프다움’대로 쓰면, 도대체 무슨 수로 동적 타입 체크와 바인딩을 최적화해 없앤단 말인가? 데이터 플로를 이해하려면 판정 불가능 문제를 풀어야 할 것이다. “C로 큰 프로젝트를 하면 리스프 런타임 대부분을 구현하게 된다?” 정말? 변수 하나하나가 LispObject(혹은 PyObject나 뭐 그 비슷한 것) 타입을 갖게 된다고? 그런 일은 없다. C 코드가 깊이 병든 리스프 빠돌이가 쓴 게 아닌 한(특히 gcc와 BetaPlayer, 너희 얘기다). 어떤 사람들이 C 코드를 리스프 백엔드처럼 쓰는 건 그 사람 개인의 문제일 뿐, 그 이상도 이하도 아니다.
동적 메모리 할당도 만만치 않다. 가비지 컬렉션이 수동 malloc/free보다 유의미하게 덜 효율적이라고는, 나는 장담하지 않겠다. 확실히 말할 수 있는 건, 좋은 리스프 프로그램은 좋은 C 프로그램보다 동적 할당과 간접 레벨을 훨씬 더 많이 쓴다는 점이다(다시 말하지만, C를 리스프로, 혹은 리스프를 C로 에뮬레이션하는 경우는 제외한다. 애초에 시간 낭비라고 생각하기 때문이다). 그리고 오브젝트를 납작하게(flat) 만들고 싶다면 정적 타입 시스템이 필요하다고 본다. 그러면 동적 유연성 면에서 Java보다 크게 고수준이진 못할 것이다. 그리고 간접 레벨은 극도로 비싸다. 데이터 종속적인 메모리 접근은 파이프라인 스톨을 유발할 가능성이 끔찍하게 높기 때문이다.
정적 타입의 순수 함수형 언어는 또 자기 문제를 갖고 있다 — 부작용이 없고, 인터페이스 레벨에서 복사를 많이 만든다. 그 복사를 제거하는 일은 컴파일러 작성자의 연습 문제로 남겨둔다. 나는 그런 연습 문제를 대단히 많이 풀어본 적이 없기에, 그 문제를 깊게 논하진 않겠다. 다만 (타입 추론 기법과 무관하게) 정적 타이핑은 더 저수준 언어의 속성이라고만 말해두겠다. 이제 나는 타입에 대해 생각해야 한다. 마치 명령형 프로그래밍이 함수형 프로그래밍보다 더 저수준인 것처럼 — 이제 나는 부작용의 순서에 대해 생각해야 한다. 당신은 나더러 ‘고수준’의 의미를 모른다고 할 수 있다. 상관없다.
이제 폰 노이만 머신 얘기. 오늘날 메모리 어레이가 얼마나 최적화되고 표준화되어 있는지 아는가? 그건 CPU에서 벌어지는 일과는 급이 다르다. 수많은 CPU 패밀리가 수많은 서로 다른 ISA를 돌린다. 메모리는 전부 로드와 스토어만 한다. SRAM(비싸고 빠른 것)도 DRAM(싸고 느린 것)도, 원시 성능부터 공장 결함 검출용 테스트까지 탈탈 털릴 만큼 최적화되어 있다. 하드웨어를 설계할 때 메모리에 대해선 별로 고민하지 않는다. 수치 앱에서 어떤 부동소수를 쓸지 고민하지 않는 것과 같다 — IEEE를 쓴다. 그 표준을 잘 돌아가게 만들기 위해 모든 레벨에서 지적 노력이 엄청나게 투입되었기 때문이다.
그래도 ‘폰 노이만 머신은 50년대의 유물’이라는 뉴에이지 흐름을 따라가 보자. 다른 아키텍처는 어떤 종류가 있고, 그것을 어떻게 프로그래밍하는가? “C는 폰 노이만 머신을 위한 것이다.” 자바도 그렇고 리스프도 그렇다; 다들 연속된 배열을 갖는다. 연결 리스트와 딕셔너리도 다른 종류의 머신을 위해 설계된 게 아니다. 사실 많은 표준적인 big-O 복잡도 분석은 폰 노이만 머신을 가정한다 — 임의 접근 O(1).
그리고 표준 메모리, 표준 프로그래밍 언어, 표준 복잡도 분석을 기꺼이 버리겠다고 하자. 나는 당신을 괴짜라고 생각하지 않는다, 진심으로. 허풍일 가능성이 높다고는 생각하지만, 당신이 뛰어나고 창의적인 개인일 수도 있다. 수백만이 실천하는 것을 자동으로 ‘진리’나 ‘정답’으로 쳐줄 순 없다고 진심으로 생각한다. 나는 소비에트 연방에서 태어났으니까, 그런 건 잘 안다. 어쨌든, 당신의 아이디어를 듣고 싶다. 내겐 이미지가 있다. 그 이미지를 처리하고 그 안에서 무언가를 찾아내야 한다. 나는 프로그램을 써서 그 동작을 제어해야 한다. 알다시피, 늘 있는 편집-실행-디버그-욕설의 사이클. 어떤 모델을 쓰자고 제안하는가? “뉴럴넷” 같은 말만 하지 말고. 하드웨어 아키텍처의 디테일을 좀 들려달라.
정말 알고 싶다. 꽤 이름난 사람들이 가진 의견이라면, 거기에 공감하는 사람이 아주 많으리라 가정한다. 여러분 중 다수는 유능한 프로그래머이고, 어떤 이는 나보다 더 강할 것이다. 왜 내가 틀렸는지 말해달라. 샘플 칩을 보내주겠다. 내가 잘난 체하는, 잘못 아는 멍청이였다고 공개적으로 인정하겠다. 뭐든 좋다. “효율/고수준은 트레이드오프가 아니다”라는 소리의 이 부분을 끝내고 싶다. 그래야 예정된 다른 부분에 대해 계속 떠들 수 있다. 알다시피, STL이 ‘고수준’이라고 믿는 C++ 프로그래머들을 놀려대는 그거(하!). 하지만 이 “리스프는 효율적이다”(하!)라는 이슈가 남아 있는 한, 나는 양심에 찔려 마음 편히 계속 떠들 수가 없다. 불균형한 푸념은 악이 아닌가, 아닌가?