Lobsters의 링크 토론: ‘고수준 CPU’ 챌린지(2008)를 둘러싼 RISC와 CISC, 메모리 장벽·원자성, GC·레퍼런스 카운팅, VM 바이트코드, ARM·x86, Azul·Sun 사례 등에 대한 심층 논의.
URL: https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008
Title: The "high-level CPU" challenge (2008)
‘고수준 CPU’ 챌린지(2008) | Lobsters
27 The "high-level CPU" challenge (2008)hardwareprogrammingyosefk.comvia
rrampage1개월 전 | - [x] 캐시 Archive.orgArchive.todayGhostarchive | 댓글 19개
미리보기
1. - [x] [12](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~kmicklas)[kmicklas](https://lobste.rs/~kmicklas)[1개월 전](https://lobste.rs/c/iqs3ct) 실제로 이런 일이 벌어진 구체적인 예 하나가 떠오르는데, 애플이 CPU에서 경합이 없는(uncontended) 원자적 연산의 성능을 최적화해 Swift를 위한 원자적 레퍼런스 카운팅을 빠르게 만든 것입니다. 이에 대한 정말 심층적인 분석을 보진 못했지만, 추적형 GC가 애플 CPU에서 여전히 더 높은 처리량을 보이더라도 ARC와의 격차가 줄어들었고, 더 작은 메모리 요구사항을 고려하면 트레이드오프 공간이 예전과는 다르게 보이는 것 같습니다.
일반적으로, CPU가 GC나 레퍼런스 카운팅 같은 다양한 메모리 배리어 형태의 연산을 하드코딩할 기회가 많다고 생각합니다. 이런 연산은 종종 컴파일된 코드 곳곳에 삽입되어야 해서, 실제 지연(latency) 자체는 상대적으로 저렴하더라도 매우 높은 코드 부풀림/아이캐시 비용을 가지게 됩니다.
여기서 가장 큰 문제는 이런 종류의 연산이 전혀 표준화되어 있지 않고 런타임 시스템마다 완전히 다르다는 점입니다. 참 간단해 보이는 레퍼런스 카운트 감소 같은 것조차도 달라질 수 있는 세부사항이 아주 많습니다. 애플이 이 길을 갈 수 있는 이유는 플랫폼 전반을 통제하기 때문이고, 다른 누구도 그렇게 하진 못합니다.
(이 글에서 비판하는 모호함의 죄를 제가 좀 저지르고 있다는 걸 인정합니다.)
1. - [x]
당신이 큰 문제 중 하나를 강조했다고 생각합니다. 여기에는 실제로 세 가지 범주가 있습니다:
* 언어와 무관하게 기존 설계에 추가하면 더 빨라지는 것들.
* 기존 설계에 추가하면 일부 프로그래밍 모델에 이득이 되는 것들.
* 기본 추상 기계에 큰 변화를 요구하는 것들.
첫 번째 범주는 여기서 해당되지 않습니다. 슈퍼스칼라 설계 같은 것들은 모든 것을 가속하고, 특히 C를 가속합니다. 두 번째 범주는 첫 번째 범주에서 더 이상 추가할 것이 없을 때 추가됩니다. 다크 실리콘 환경에서는 파워 게이팅이 가능하다면 더 매력적입니다. 자바스크립트 VM을 더 빠르게 만들어 준다는 이유로 많은 작은 조정들이 추가되고, 결국 그것들은 작은 조정이고 다른 곳에서도 유용할 수 있기 때문에 언어 특화 요소로 생각하지 않게 됩니다.
정말 흥미로운 것들은 세 번째 범주에 있지만, 그 실험을 수행하는 것은 사실상 불가능합니다. 완전히 다른 아키텍처를 갖고 현대의 애플이나 인텔 칩과 비슷한 복잡도를 가진 칩을 만드는 것은, 단 한 번 하더라도, 10억 달러짜리 프로젝트입니다. 소프트웨어 생태계와의 공동 설계는 여기에 수십억을 더합니다. 그리고 마지막에, 10배 더 빠른 것을 얻을 수도 있고, 전혀 작동하지 않는 것을 얻을 수도 있습니다. 그런 돈을 연구에 쓸 수 있는 누구도 그 위험을 감수하고 싶어하지 않습니다.
그게 일어나는 유일한 곳은, 초기 타깃의 문제 도메인이 충분히 달라 기존 CPU가 그것에 약한 경우입니다. 그래픽이 가장 명백한 예입니다. 그래픽 문제들은 보통 4-원소 벡터(RGBA 또는 XYZW)를 다루고, 이런 벡터 다수에 동일한 작업을 수행합니다. CUDA가 있는 NVIDIA GPU는, 모양이 맞는 어떤 문제에 대해서도, C를 사용하는 NVIDIA GPU와 CUDA를 사용하는 CPU 모두보다 더 좋은 성능을 내죠. 이것이 가능했던 것은, 10배를 훨씬 넘는 가속을 제공했기 때문이고, 또한 가속기로 판매되어 기존 C 코드를 실행할 필요가 없었기 때문입니다.
2. - [x]
흥미로운 글이지만, 논의의 잘못된 층위를 붙잡고 있다고 생각합니다. 사실 잘못된 층위가 여러 개 있습니다.
1. RISC가 실제로 더 빠르다는 게 입증되었습니다. 그걸 되돌릴 무언가를 보지 못했고, 새 하드웨어 설계나 새 언어가 어떻게 그럴 수 있을지도 모르겠습니다.
(가능하다면, GPU 같은 아키텍처에서 거의 네이티브에 가깝게 실행될 수 있는 어떤 광기 어린 APL 변종 정도가 있겠죠. 이는 놀라울 정도로 효율적일 수 있지만, 아마 손가락으로 꼽을 정도의 소수만이 마스터할 수 있을 겁니다.)
1. 고수준 언어가 더 작고 강하게 최적화된 무언가를 능가할 수 있을지 확신이 서지 않습니다. 가능하긴 하겠지만 판단하긴 어려울 겁니다… 왜냐하면, 만약 가능하다면 수십 년 동안 수억 단위의 화폐가 투입되어온 결과물과 경쟁하려면 여러 자릿수의 차이를 보여야 할 테니까요.
예를 들면: 오베론은 극도로 작고, 단순하며, 빠릅니다. 배울 점이 있습니다(현재 시제: 여전히 사실입니다). 하지만 정설에 반하기 때문에 무시되었죠.
1. 하지만 이 두 가지가 더 큰 실제 문제들을 가리고 있습니다.
2. sheer mass(질량) 가 관건입니다. 멍청한 작은 언어들을 쓰거나, 멍청한 작은 언어로 구현된 조금 더 똑똑한 언어들을 쓰면, 그렇게 거대하고 털복숭이 같은 스택이 되어 더 이상 진짜 최적화나 진짜 생산적인 리팩터링이 가능하지 않다고 생각합니다.
작은 팀은 큰 조직이 할 수 없는 기적을 이룹니다. 비유: Ivory 대 MicroVAX.
교훈: 도구를 한 사람이 전부 머릿속에 담을 수 있을 만큼 작게 유지하고, 그 도구 위에 쌓아라. 하지만 그런 큰 두뇌들을 아껴야 할 수도 있습니다. 즉, 오늘날처럼 상품화된 비료 공장의 맷돌에서 가루로 갈지 말라는 뜻입니다.
1. 유지보수성이 중요합니다. 쉽지만 방대한 스택의 이점을 그 스택을 유지보수하느라 탕진합니다.
이 마지막이 HLL/LLL 논쟁의 진짜 비용입니다. 작고 멍청한 도구를 사용하면, 이쑤시개로 만든 에펠탑을 유지보수하느라 수천 명의 천재와 수십억의 화폐가 낭비됩니다.
그게 진짜 성능이 중요한 곳입니다.
벤치마크가 아닙니다.
1. - [x] [5](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~david_chisnall)[david_chisnall](https://lobste.rs/~david_chisnall)[1개월 전](https://lobste.rs/c/gcllxm) > RISC가 실제로 더 빠르다는 게 입증되었습니다. 그걸 되돌릴 무언가를 보지 못했고, 새 하드웨어 설계나 새 언어가 어떻게 그럴 수 있을지도 모르겠습니다
사실 그렇지 않습니다. 그래서 업계가 RISC의 기초 속성 일부에서 멀어지고 있는 겁니다. RISC는 CPU가 많은 면적을 실제 일이 아닌 것(당시에는 주로 디코드와 마이크로코드 실행)에 쓰고 있다는 관찰에서 동기를 얻었습니다. 마이크로코드는 여전히 나쁜 아이디어이긴 한데, 그 이유는 원래와는 다릅니다. 마이크로코드는 사실상 명령 캐시를 위한 압축 방식 역할을 하지만, 시스템의 다른 부분을 복잡하게 만듭니다. 특히 각 마이크로코드 시퀀스를 원자적으로 취급해야 합니다. 그 중간에 인터럽트를 걸 수 없고, 마이크로코드를 통해 쉽게 추측 실행(speculate)하거나 다른 명령과 병렬로 마이크로코드화된 명령을 실행할 수도 없습니다(‘쉽게’라는 단어에 주목: 할 수는 있지만 복잡성이 큽니다).
오늘날 우리는 RISC를 동기부여했던 것과 같은 상황에 있습니다. 명령 스케줄링과 레지스터 리네임은, 디스패치되는 모든 명령에 대해 고정 비용이며(전력과 면적 비용은 동시에 비행 중일 수 있는 명령 수에 비례해 증가하므로, 코어가 복잡해질수록 더 나빠집니다), 큰 비용입니다. 여기서의 추세는 더 많은 일을 하는 명령으로 향하고 있습니다. 초기 예로는 융합 곱셈-덧셈(FMA), SIMD, 복잡한 주소 지정 모드 등이 있습니다. 사이클당 명령을 하나 더 발행하는 것은, 한 파이프라인에서 두 가지 일을 하는 명령을 발행하는 것보다 훨씬 더 어렵습니다.
더 중요한 점은, RISC가 호황을 누렸던 이유는 데나드 스케일링(2007년경 종료)이 18개월마다 동일 전력 한도에서 더 빠른 트랜지스터를 제공하던 시기에, 스케일업 비용이 저렴했기 때문입니다. 이것은 Lisp 머신을 죽인 요인 중 하나였습니다. 설계 비용이 많이 들었고, DEC은 훨씬 더 단순한 파이프라인으로 훨씬 더 높은 클럭을 달성할 수 있는 Alpha를 만들 수 있었기 때문에, Alpha에서 Lisp 머신을 에뮬레이션하는 게 실제 Lisp 머신을 돌리는 것보다 더 빨랐습니다. 또 다른 요인은 명령 수준 병렬성(ILP)이었습니다. 스택 머신은 ILP를 추출하기 어렵게 만들고, 레지스터 머신은 훨씬 쉽게 만들기 때문에 스택 머신은 사라졌습니다.
상업적 측면을 무시하지 않는 것이 정말 중요합니다. 이것이 Azul 아키텍처를 죽인 이유입니다. 그들은 GC를 훨씬 더 빠르게 만들었습니다. 하지만 자바 GC는 최악의 경우 전체 실행 시간의 약 30% 정도입니다. GC를 무한히 빠르게 만든다고 해도, 경쟁력을 갖추려면 동일한 가격대의 Intel/AMD/Graviton CPU 성능의 70%를 넘어야 합니다. 그리고 그 프로세서들은 당신 것보다 몇 자릿수 더 많이 팔려서, 모든 NRE를 더 많은 유닛에 분산시킬 수 있습니다.
1. - [x] 3
fanf 편집됨 1개월 전 > RISC는 CPU가 실제 일이 아닌 것에 많은 면적을 쓴다는 관찰에서 동기를 얻었다
복잡한 주소 지정 모드
ARM의 시초에 관한 Sophie Wilson의 강연이 많은 걸 깨닫게 해줍니다. 그녀는 사용 가능한 메모리 대역폭을 잘 활용하는 것의 중요성을 강조했습니다. 6502는 사실상 모든 사이클에 메모리에 접근했는데, 당시에는 드물었습니다. Acorn이 6502의 후속으로 시도한 CPU들은 이 관점에서 모두 별로 만족스럽지 않았습니다.
1980년대 중반의 다른 RISC들은 매 사이클에 뭔가 유용한 일을 하는 것에 중점을 뒀지만, ARM만큼 메모리 대역폭을 잘 활용하진 못했습니다. 다만 캐시를 추가하면, 메모리 접근당 명령 수가 높은 비용은 훨씬 덜 중요해집니다.
ARM이 다른 RISC와 비교해 눈에 띄는 점은 비교적 화려한 주소 지정 모드입니다. 메모리 연산 중에 ALU를 놀릴 이유가 없죠! 하지만(늘 그렇듯) 저는 John Mashey의 RISC vs CISC 분석을 언급해야 합니다. 그는 명령 집합 기능을 세어 봤습니다. 명확한 경계선 중 하나는 명령당 메모리 오퍼랜드 수였습니다.
Mashey는 분석에 ARM을 포함하지 않았지만, ARM은 가장 덜 RISC스러운 RISC였을 겁니다. 그리고 x86은 가장 덜 CISC스러운 CISC죠. 특히 x86은 대체로 명령당 복수의 메모리 접근을 갖고 있지 않아서, 슈퍼스칼라를 어렵게 만드는 주요 요인 중 하나를 피합니다. 메모리 연산당 화려한 주소 계산 작업은 훨씬 더 쉽게 확장됩니다.
Azul과 Sun이 메모리 서브시스템에 더 많은 영리함을 집어넣은 예를 좋아합니다. 그런 게 더 필요하다고 생각합니다. 매우 RISC스러운 LL/SC 원자 연산은 메모리 인터커넥트 대역폭에 좋지 않았습니다. 더 높은 수준의 원자 연산은 훨씬 더 낫고, 특히 캐시 라인 핑퐁을 피할 수 있게 해줄 때 더욱 그렇습니다.
1. - [x] 3
calvin1개월 전 > 1980년대 중반의 다른 RISC들은 매 사이클에 유용한 일을 하는 것에 중점을 뒀지만, ARM만큼 메모리 대역폭을 잘 활용하진 못했습니다. 다만 캐시를 추가하면, 메모리 접근당 명령 수가 높은 비용은 훨씬 덜 중요해집니다.
Sophie 등이 지적한 또 다른 점은, 그 CPU들이 인터럽트 성능도 형편없어 많은 사이클을 인터럽트에 쏟아부었다는 것입니다. ARM은 이를 더 빠르게 하는 경향이 있었고, FIQ 같은 기능을 포함하기까지 했습니다.
ARM이 다른 RISC와 비교해 눈에 띄는 점은 비교적 화려한 주소 지정 모드입니다. … John Mashey의 RISC vs CISC 분석 … 명령당 메모리 오퍼랜드 수가 경계선.
ARM은 “순수” RISC를 위반해 load/store multiple을 갖고 있는데, 이는 당시 메모리를 포화시키는 데 결정적이었습니다.
Mashey는 ARM을 포함하지 않았지만 가장 덜 RISC스러운 RISC였을 것. x86은 가장 덜 CISC스러운 CISC. 특히 x86은 대체로 명령당 복수 메모리 접근이 없어 슈퍼스칼라를 어렵게 만드는 요인을 피함. 메모리 연산당 화려한 주소 계산은 쉽게 확장됨.
Mashey 표에 남아 있는 또 다른 CISC 아키텍처인 System/360은 교과서적인 RISC처럼 매우 단순한 명령 형식을 가졌고, 또한 보통 메모리-투-메모리 명령을 피합니다. 실제로, 그런 명령이 전혀 없는 시스템을 만들기도 했습니다. 그걸 RISC라고 주장할 수도 있겠죠.
2. - [x] [1](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~lproven)[lproven](https://lobste.rs/~lproven)[1개월 전](https://lobste.rs/c/p5kot1) 매우 흥미롭지만 제 급여 등급을 약간 넘는 이야기네요. 제 이해가 매우 구식임을 효과적으로 보여주고 있습니다. 제가 본 Sophie Wilson의 몇몇 강연도 당신의 주장에 힘을 실어줍니다.
이건 제게 새로웠습니다:
또 다른 요인은 명령 수준 병렬성. 스택 머신은 ILP 추출이 어렵고, 레지스터 머신은 훨씬 쉽기 때문에 스택 머신은 사라졌다.
그렇다면, 그게 Limbo의 Dis가 번성하지 못한 이유일까요? 제가 알던 주요 스택 기반 VM이었고, 다른 모든 이들은 레지스터 기반으로 갔을 때요.
이게 Azul의 아키텍처를 죽인 이유.
처음 듣습니다. 자세히 알려주실래요? 누가, 무엇을, 언제요?
1. - [x]
그렇다면, 그게 Limbo의 Dis가 번성하지 못한 이유일까요? 제가 알던 주요 스택 기반 VM이었고, 다른 모든 이들은 레지스터 기반으로 갔을 때요.
흠, JVM도 스택 기반입니다. VM은 제약이 다릅니다. 스택 기반 시스템이 좋은 점은 두 가지가 있습니다:
* 코드가 촘촘합니다(다만 Dalvik이 좋은 인코딩으로 레지스터 기반 시스템도 이를 달성할 수 있음을 보여줬죠).
* 해석기가 단순합니다.
이를 컴파일한다면, 보통 SSA 형태로 확장해 최적화할 겁니다. 첫 번째 장점은 하드웨어에도 정말 유용한데, 더 작은 명령 캐시를 의미하기 때문입니다. 하지만 하드웨어는 최적화와 동일한 문제에 부딪힙니다. 다음과 같은 산술 시퀀스를 생각해 봅시다:
(a + b) * (c + d)
스택 명령 집합에서는 다음과 같이 쓸 수 있습니다:
load a
load b
+
load c
load d
+
*
이 중 어떤 것을 병렬로 실행할 수 있을까요? 트리를 구성하고 독립적인 리프 노드를 식별해야 합니다.
레지스터 기반 아키텍처에서는 대신 이렇게 표현합니다:
load r1, a
load r2, b
add r3, r1, r2
load r1, c
load r2, d
add r1, r1, r2
mul r1, r1, r3
두 번째 형태에서 의존성을 훨씬 더 쉽게 알아챌 수 있습니다. 레지스터 리네이밍을 한다면, 다음과 같이 다시 쓰일 것입니다:
load r1(%0), a
load r2(%1), b
add r3(%2), r1(%0), r2(%1)
load r1(%3), c # %0 는 이제 죽음
load r2(%4), d # %1 는 이제 죽음
add r1(%5), r1(%3), r2(%4) # %3 는 이제 죽음
mul r1(%6), r1(%5), r3(%2) # %5 는 이제 죽음
# 기본 블록 끝, %6, %4, %2 가 live-out
그러면 로드들이 서로 독립적이라는 걸 즉시 볼 수 있고, 로드-스토어 파이프라인에 밀어 넣기 시작할 수 있습니다. 처음 두 개가 retire되는 즉시 add를 발행할 수 있고, 그 add는 이후의 로드와 병렬로 실행될 수 있습니다. 곱셈은 두 개의 add가 끝나는 즉시 스케줄될 수 있습니다. 이 모든 것이 가능한 이유는 레지스터가 데이터플로를 명시적으로 표현하기 때문입니다. 미리 훑어볼 필요 없이, 명령이 디코드되는 즉시 의존성 그래프를 구축할 수 있습니다.
처음 듣습니다. 자세히 알려주실래요? 누가, 무엇을, 언제요?
Azul Systems는 하드웨어 지원 GC를 갖춘 시스템을 만들었습니다(El Reg가 그들에 대해 몇몇 글을 썼던 걸로 기억합니다). 그들은 포인터에 1비트 세대 카운터를 저장하고 이를 로드 배리어로 사용하는 멋진 트릭을 썼습니다. GC 스윕을 시작할 때, 하드웨어는 잘못된 세대의 포인터를 로드할 때마다 트랩을 걸었습니다. 이것은 빠른 사용자 공간 트랩이어서, 그 다음에 객체를 스캔하고 수백 사이클(캐시 미스와 비슷한 시간) 내에 이동시킬 수 있었고, 이를 통해 GC를 완전히 동시적으로 만들었습니다. 덕분에 매우 높은 지속적 할당률에 도달할 수 있었습니다.
제가 앞서 설명한 문제 외에 또 다른 문제는, 모두가 GC는 느리다고 알고 있기 때문에, 성능을 신경 쓰는 자바 코드는 할당을 피하는 경향이 있다는 점입니다. 그래서 GC 성능을 향상시키면, 사람들이 성능 중요하다고 여기지 않는 코드의 성능이 주로 향상됩니다.
게다가, 그들의 시스템은 정말로 빠르지 않았습니다. (20년 전에도) 제온의 절반에도 못 미치는 속도였고, 가격은 10배였습니다.
아, 그리고 그들이 페이지 테이블 트릭을 써서 x86 시스템에서도 거의 같은 일을 할 수 있다는 걸 알아냈습니다(트랩을 얻으려면 로드된 포인터를 통해 한 번 더 로드해야 했지만, 이는 GC 포인터를 로드할 때마다 명령 하나가 더 붙는 정도였습니다).
1. - [x] [1](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~lproven)[lproven](https://lobste.rs/~lproven)[1개월 전](https://lobste.rs/c/9uxoha) > 흠, JVM도 스택 기반입니다.
죄송합니다, 제가 거꾸로 말했네요.
Dis는 Inferno 커널의 런타임입니다.
https://en.wikipedia.org/wiki/Limbo_(programming_language)#Virtual_machine
대부분(JVM 포함)이 제 이해로는 스택 기반인 반면, 이는 레지스터 기반 VM입니다. (다른 레지스터 기반 VM에는 Parrot VM과 MoarVM이 있습니다.)
그 설계는 AT&T Hobbit RISC CPU에서 영감을 받았습니다:
https://en.wikipedia.org/wiki/AT%26T_Hobbit#Design
이는 원래의 Apple Newton과 BeBox CPU로 계획되었었습니다.
1. - [x] 2
fanf1개월 전 요즘 가장 유명한 레지스터 기반 바이트코드는 Lua입니다.
Lua의 바이트코드는 @david_chisnall의 예와는 다르게 보이는데, 그 이유는 명령이 지역 변수와 임시값에 직접 접근할 수 있기 때문입니다. 로드와 스토어 및 기타 스택 섞기 오버헤드가 거의 필요 없습니다. 인터프리터 비용에서 명령 디스패치가 큰 비중을 차지하기 때문에, 명령 수를 줄이는 게 중요합니다.
1. - [x] 1
lproven1개월 전 아하! 감사합니다.
그게 제게 다음을 설명해 줍니다:
https://github.com/jvburnes/node9/
최근에 알게 된 바로는, Dis가 꽤 32비트에 얽매여 있어서 64비트 포팅이 어렵고, 재작성까지 필요하다는 겁니다.
(이게 제가 이것을 발견하고 올린 부분적 이유입니다: https://lobste.rs/s/ogd44u/purgatorio_wip_64_bit_port_inferno )
한편 누군가는 Node9 작업을 진행 중인데, 이는 Inferno지만 Dis VM을 Lua VM으로 바꾼 것입니다.
https://github.com/jvburnes/node9/
3. - [x] [1](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~fanf)[fanf](https://lobste.rs/~fanf)[1개월 전](https://lobste.rs/c/dni3sr) > 오늘날, 우리는 RISC를 동기부여했던 것과 같은 상황에 있습니다.
또 다른 생각이 떠오릅니다:
CISC의 문제 중 하나는, 화려한 명령 집합 기능이 기본적으로 정적이었다는 점입니다. 이는 어셈블리와 고수준 언어 사이의 간극을 기본적인 구문 수준에서 좁히려던 시도였죠. 1980년대 초에는 최적화 컴파일러가 등장해, 잡다한 호출/반환/루프 명령들이 보통 불필요한 일을 많이 하며, 더 단순한 명령 조합을 사용하면 그 불필요한 일을 제거할 수 있고, 그 결과 코드가 CISC에서도 실제로 더 빨랐음을 보여줬습니다.
1990년대에 바뀐 것은, 슈퍼스칼라 추측 실행과 리네이밍 기계가 정적 분석으로 대체할 수 없는 동적 분석을 하기 시작했다는 점입니다. RISC의 더 똑똑한 컴파일러는 이미 존재했고, VLIW/EPIC/Itanium은 정적 분석에서 또 다른 도약이 있을 것이라 기대했지만, 나타나지 않았습니다.
또 다른 RISC 스타일의 혁명은, CPU가 많은 추측 기계를 버릴 수 있게 해 줄, 프로그램의 동적 동작에 대한 정적 기술을 제공하는 어떤 활성화 기술이 필요합니다. 그리고 컴파일러와 JIT는 20~30년 전보다 훨씬 발전했지만, 그게 가능하다는 징후는 보지 못했습니다.
3. - [x]
Xerox PARC Alto와 후속작은 유명하게도 프로그래머블 마이크로코드를 가지고 있었습니다. Smalltalk-76…80의 마이크로코드가 바이트코드를 직접 실행했는지, 아니면 그 사이에 해석기 레이어가 여전히 있었는지는 잘 모르겠습니다(아마 Dan Ingalls의 훌륭한 회고가 말해 줄 겁니다). 그래픽 기본 연산인 BitBLT가 속도를 위해 마이크로코드로 구현되었다는 건 알고 있습니다.
고수준 언어에 도움이 되는 CPU 수준 아이디어 몇 가지:
1. GC bump allocator가 자신의 영역 끝에서 떨어질 때 잡아내기. 때로는 끝 페이지를 unmap 해서 하기도 하는데, 그러면 CPU 예외/시그널을 처리해야 하고, 제가 알기로는 느립니다.
2. 동적 메서드 디스패치를 위한 커스텀 명령은 Objective-C의 objc_msgsend 같은 일반 코드보다 더 나을 수 있습니다. vtable 조회가 아니라, 클래스별로 메서드 테이블과 캐시를 유지하는 알고리즘을 말합니다.
3. JIT의 프로파일 주도 최적화에 쓰이는 호출 횟수 통계를 유지하기 위한 CPU 지원.
1. - [x] [6](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~david_chisnall)[david_chisnall](https://lobste.rs/~david_chisnall)[1개월 전](https://lobste.rs/c/zzrcac) > GC bump allocator가 자신의 영역 끝에서 떨어질 때 잡아내기. 때로는 끝 페이지를 unmap 해서 하기도 하는데, 그러면 CPU 예외/시그널을 처리해야 하고, 제가 알기로는 느립니다.
Sun Labs의 Project Maxwell은 한 걸음 더 나아갔습니다. 그들은 하드웨어로 bump allocator를 제공했습니다. 젊은 세대 객체는 보통 캐시에서 퇴거되기 전에 죽는다는 관찰에서 출발해, 그 위에 메모리 시스템을 구축했습니다. 젊은 세대는 캐시에 할당되고 하드웨어에서 완전히 비동기적으로 스윕되었습니다. 젊은 세대에 승격할 만큼 충분한 객체가 생기면 배리어가 발생하고, 그때 소프트웨어 관리 GC로 이동했습니다.
동적 메서드 디스패치를 위한 커스텀 명령은 … 클래스별로 메서드 테이블과 캐시를 유지하는 알고리즘
제가 약 10년 전 구현했던 하드웨어 버전에 관한 논문을 꺼내 다시 제출해야겠군요. 소프트웨어 관리 분기 예측기에 가까웠습니다. 저는 두 개의 키로 캐시에 히트하면 그 지점으로 분기하고, 미스면 특수 레지스터에 등록된 핸들러로 분기하는 특별한 명령을 추가했습니다. 호출 시퀀스는 클래스와 셀렉터를 레지스터에 넣고 나서 그 특수 점프를 수행합니다. 캐시에 히트하면 메서드로 직접 분기하고, 미스면 실제 메시지 전송 함수로 분기합니다.
아, 태그된 포인터를 위한 특수 로드도 추가했는데, 정렬된 포인터면 로드하고 그렇지 않으면 태그를 목적 레지스터에 넣는 방식이었습니다(한 필드는 확인할 하위 비트 수였습니다). 이는 로드와 시프트·마스크를 병렬로 수행하고 하위 비트가 0이 아니면 예외를 스쿼시할 수 있었기 때문에 파이프라인에 넣기 쉬웠습니다. 덕분에 isa 포인터나 태그를 한 명령으로 로드할 수 있었고, 호출 경로에서 명령 하나가 추가되는 정도였습니다.
캐시 히트율은 매우 높았습니다. 애플이 비슷한 걸 구현하지 않았다는 게 꽤 놀라웠습니다.
1. - [x] 1
lproven 편집됨 1개월 전 > Sun Labs의 Project Maxwell
당신은 당신의 훌륭한 ‘C는 저수준 언어가 아니다’ 논문에서 이것을 언급했습니다.
(이에 대해 설득력 있는 반론을 한 유일한 사람은 @markmll이고, 두 분의 토론을 돈 주고서라도 보고 싶습니다.)
Oracle의 $ARBITRARY_TIME_PERIOD of the Long Knives 이후에는 이것의 흔적을 찾기가 어렵습니다. 옛 레퍼런스를 찾은 뒤 인터넷 아카이브를 뒤지고 있습니다.
이 페이지를 찾았습니다: https://www.adam-bien.com/roller/abien/entry/when_c_becomes_too_slow
링크는 당연히 죽었습니다.
#1은 이걸까요?
Maxine: Sun의 새 메타-서큘러 연구 VM(자바로 작성).
https://web.archive.org/web/20090204023108/https://research.sun.com/projects/dashboard.php?id=150
그리고…
#2는:
https://research.sun.com/techrep/2006/smli_tr-2006-159.pdf
아마도:
Introspection of a Java™ Virtual Machine under Simulation
https://dl.acm.org/doi/pdf/10.5555/1698148
초록 번호가 일치하고, 이 페이지가 그렇다고 시사합니다:
https://labs.oracle.com/pls/apex/f?p=94065:10:110272925450792:1816
1. - [x] 2
david_chisnall1개월 전 꽤 멋진 기술 보고서가 있었지만, 안타깝게도 공개 출판되진 않았습니다. Mario Wolczko가(프로젝트가 취소된 후—Sun 프로세서 부서 간 내분이 그것을 죽였습니다) 저에게 공유했지만, 정식 공개 릴리스는 아니었던 것으로 기억합니다.
2. - [x]
당신은 당신의 훌륭한 C is not a low-level language 논문에서 이것을 언급했습니다.
(이에 대해 설득력 있는 반론을 한 유일한 사람은 @markmll이고, 두 분의 토론을 돈 주고서라도 보고 싶습니다.)
짧게 물어뜯어 보죠. 인용된 글을 읽기 전에도 “먼저 저수준 언어가 무엇인지부터 결정해야 한다”고 생각했습니다. 글을 읽은 후에도 여전히 그렇게 느낍니다. (K&R C vs 1980년대 Ada 같은) 저수준 vs 고수준 언어 그 자체에 관한 것이라기보다, 사실 추상화의 수준에 관한 이야기이기 때문입니다:
* 어떤 추상화를 도입해야 기계 성능이 무어의 법칙을 따라가게 만들 수 있을까?
* 어떤 추상화를 도입해야 프로그래머 성능(특히 코드 신뢰성 향상에 초점)을 높일 수 있을까?
첫 번째 경우에, 결국 “영리한” 아키텍처들(“The Mill” 같은, 그리고 물론 그 전신인 Burroughs 스택 머신들)이 RISC의 지속적 발전(그리고 백엔드가 문서화되지 않은 RISC 또는 VLIW 같은 하이브리드 x86 시스템)에게 자리를 내줬다고 지적하고 싶습니다.
두 번째 경우에는, “그때는 좋아 보였던 것들이” 종종 그렇지 않게 드러난다고 말하고 싶습니다. 예를 들어 B5000은 본질적으로 정수와 실수의 구분이 없었고(문자 처리에 대해서는 말하지 않는 게 좋겠습니다), Pascal—그리고 Wirth는 Stanford의 Burroughs 시스템을 사용했습니다—가 정한 타입 변환 규칙은 자동 변환 같은 허술함이 있었는데, 이는 나중에 C++를 괴롭힌(제 이해로는) 문제와 비슷합니다.
Wirth는 자신이 틀렸음을 깨닫고, 후속 언어들에서 타입 처리를 엄격하게 했습니다. 안타깝게도, Pascal 커뮤니티는 그 메모를 무시했습니다.
대담한 제안을 하라면—여기 경험이 더 풍부한 분들 앞이라 대담합니다—1960년대 IBM이 비교적 큰 범용 레지스터 파일을 갖추는 게 방식이라고 인식했듯이, 그들이 메인프레임 측면에 특수 목적 서브시스템을 덧붙이는 데도 꽤 잘해 왔다고 생각합니다.
따라서 기계 수준에서 앞으로 나아갈 최선—어쩌면 유일—의 길은, ACID를 준수하면서 복잡한 연산을 완료할 수 있는 비동기 코프로세서를 제공하는 것이라고 생각합니다. 예를 들어, 복잡한 추론을 설정하고 풀어 그 결과를 지정된 프로세스에 전달하는 식으로요.
그리고 응용 수준에서는, 이상적으로는 비전문가에게도 친숙한 표기법을 사용해 이를 지원하는 고수준 추상화입니다(왜 Prolog에 능숙한 사람이 자신을 이해시키기 위해 C++이나 Python을 배워야 하죠?).
이것은 응용 수준에서 무언가를 ‘부술’ 것입니다. 하지만 진보를 억제하는 한 가지가 있다면, 그것은 강제된 하위 호환성입니다.
4. - [x] [2](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~zesterer)[zesterer](https://lobste.rs/~zesterer)[1개월 전](https://lobste.rs/c/8tvtqs) 거의 20년 전 글이라는 건 알지만, “고수준 언어는 지금보다 빨라야 한다”와 “고수준 언어가 느린 건 하드웨어 탓”을 혼동/허수아비 공격하는 부분(이 혼동은 후반부에서 타인에 대한 응답에서 가장 두드러집니다)은 그리 좋아 보이지 않습니다. 고수준 언어 중에서도 여전히 전형적인 HLL보다 상당히 빠르게 동작하는 예는 많고, 2008년 당시에도 JavaScript, Lua, Java 등 최소한 이런 언어들에서는 이미 사실이었습니다.
5. - [x] [1](https://lobste.rs/login) [](https://lobste.rs/s/ykunj2/high_level_cpu_challenge_2008)[](https://lobste.rs/~k749gtnc9l3w)[k749gtnc9l3w](https://lobste.rs/~k749gtnc9l3w)[1개월 전](https://lobste.rs/c/j4q97c) 최근에도 배열 프로그래밍 시도가 꾸준히 진행 중입니다. 예: [https://parallel.princeton.edu/papers/micro19-gao.pdf](https://parallel.princeton.edu/papers/micro19-gao.pdf) (Compute-in-DRAM의 고도화, 수준은 «표준 DRAM 칩 + 커스텀 컨트롤러»에 도달) 그리고 응용으로 [https://arxiv.org/pdf/2503.23817](https://arxiv.org/pdf/2503.23817) (일반 DRAM 칩과 커스텀 DRAM 컨트롤러로 4비트 LLM 추론을 CPU보다 빠르게 수행).