paint-brush
진짜 C++ 킬러(당신이 아니라 Rust)~에 의해@oleksandrkaleniuk
50,726 판독값
50,726 판독값

진짜 C++ 킬러(당신이 아니라 Rust)

~에 의해 Oleksandr Kaleniuk17m2023/02/14
Read on Terminal Reader
Read this story w/o Javascript

너무 오래; 읽다

나쁜 프로그래머와 함께 좋은 코드를 작성하는 것은 20세기의 문제입니다. 이제 우리는 더 나은 코드가 필요하지만 훌륭한 프로그래머가 작성했는데, 이는 현재 C++ 킬러가 다루지 않는 작업입니다. 진정한 혁명은 컴파일러 너머에 있습니다.

People Mentioned

Mention Thumbnail
featured image - 진짜 C++ 킬러(당신이 아니라 Rust)
Oleksandr Kaleniuk HackerNoon profile picture


안녕하세요! 저는 Oleksandr Kaleniuk이고 C++홀릭입니다. 나는 17년 동안 C++로 글을 써왔고 그 17년 동안 이 파괴적인 중독을 없애려고 노력해 왔습니다.


이 모든 것은 2005년 3D 공간 시뮬레이터 엔진으로 시작되었습니다. 엔진에는 2005년 C++의 모든 기능이 포함되어 있습니다. 별 3개 포인터, 8개 종속성 레이어, 모든 곳에 C 스타일 매크로가 있습니다. 조립용 비트도 있었습니다. 반복자는 Stepanov 스타일과 메타코드 Alexandrescu 스타일입니다. 코드에는 모든 것이 있었습니다. 물론 가장 중요한 질문에 대한 대답은 제외됩니다. 왜?


얼마 지나지 않아 이 질문에도 답이 나왔습니다. "무엇을 위해"가 아니라 "어떻게"로요. 결과적으로 엔진은 5개의 다른 팀에서 약 8년 동안 작성되었습니다. 그리고 모든 팀은 이전 코드를 새로운 스타일의 래퍼로 포장하여 약 10-20 마이크로카맥의 기능만 추가하는 프로젝트에 자신이 가장 좋아하는 유행을 가져왔습니다.


처음에는 솔직히 작은 것 하나하나를 억누르려고 했어요. 그것은 결코 만족스러운 경험이 아니었고, 어느 순간 포기하게 되었습니다. 나는 여전히 작업을 종료하고 버그를 수정하고 있었습니다. 제가 매우 생산적이라고 말할 수는 없지만 해고되지 않을 만큼 충분했습니다. 그런데 상사가 나에게 "어셈블리의 셰이더 코드 중 일부를 GLSG로 다시 작성하시겠습니까?"라고 물었습니다. 나는 신이 이 GLSL이 어떻게 생겼는지 알고 있다고 생각했지만 C++보다 더 나쁠 수는 없으며 그렇다고 말했습니다. 더 나쁘지는 않았습니다.


그리고 이런 종류의 패턴이 생겼습니다. 나는 여전히 주로 C++로 글을 쓰고 있었지만 누군가가 나에게 “C++가 아닌 일을 하시겠습니까?”라고 물을 때마다 했습니다. 나는 확신 했어!" 그리고 나는 그것이 무엇이든 그 일을 했습니다. 저는 C89, MASM32, C#, PHP, Delphi, ActionScript, JavaScript, Erlang, Python, Haskell, D, Rust는 물론 엄청나게 나쁜 InstallShield 스크립팅 언어로 글을 썼습니다. 저는 VisualBasic, bash 및 몇 가지 독점 언어로 글을 썼는데 법적으로 말할 수조차 없습니다. 우연히 직접 만든 적도 있습니다. 게임 디자이너가 리소스 로딩을 자동화하는 데 도움이 되도록 간단한 lisp 스타일 인터프리터를 만들고 휴가를 떠났습니다. 내가 돌아왔을 때 그들은 이 통역사로 전체 게임 장면을 작성하고 있었기 때문에 우리는 적어도 프로젝트가 끝날 때까지 이를 지원해야 했습니다.


그래서 지난 17년 동안 저는 솔직히 C++를 그만두려고 노력했지만 새롭고 멋진 것을 시도한 후에 매번 다시 돌아왔습니다. 그럼에도 불구하고 C++로 작성하는 것은 나쁜 습관이라고 생각합니다. 이는 생각만큼 안전하지 않고 효과적이지 않으며, 소프트웨어 제작과 관련이 없는 일에 프로그래머의 정신적 능력을 엄청나게 낭비합니다. MSVC에서 uint16_t(50000) + uin16_t(50000) == -1794967296 알고 계십니까? 이유를 아시나요? 응, 나도 그렇게 생각했어.


나는 젊은 세대가 C++를 직업으로 삼는 것을 막는 것이 오랜 C++ 프로그래머의 도덕적 책임이라고 믿습니다. 술을 끊지 못하고 청소년에게 위험에 대해 경고하는 것이 알코올 중독자의 도덕적 책임이기 때문입니다.


그런데 왜 그만둘 수 없는 걸까요? 무슨 일이야? 문제는 어떤 언어, 특히 소위 "C++ 킬러"가 현대 세계에서 C++에 비해 실질적인 이점을 제공하지 않는다는 것입니다. 이러한 모든 새로운 언어는 대부분 프로그래머가 자신의 이익을 위해 속박을 유지하는 데 중점을 둡니다. 나쁜 프로그래머와 함께 좋은 코드를 작성하는 것이 트랜지스터가 18개월마다 두 배로 늘어나고 프로그래머의 수가 5년마다 두 배로 늘어나던 20세기의 문제라는 점만 빼면 괜찮습니다.


우리는 2023년에 살고 있습니다. 역사상 그 어느 때보다 경험이 풍부한 프로그래머가 전 세계에 더 많습니다. 그리고 지금은 그 어느 때보다 효율적인 소프트웨어가 필요합니다.


XX세기에는 상황이 더 단순해졌습니다. 아이디어가 있으면 이를 UI로 포장하여 데스크톱 제품으로 판매합니다. 느린가요? 무슨 상관이야! 어쨌든 18개월 안에 데스크탑은 2배 더 빨라질 것입니다. 중요한 것은 시장에 진입하고 기능 판매를 시작하는 것이며 가급적이면 버그가 없는 것입니다. 물론 그런 환경에서 컴파일러가 프로그래머가 버그를 만드는 것을 막는다면 좋습니다. 버그는 현금을 가져오지 않으며, 어쨌든 기능을 추가하든 버그를 추가하든 프로그래머에게 비용을 지불해야 하기 때문입니다.


이제 상황이 달라졌습니다. 아이디어가 있으면 이를 Docker 컨테이너에 포장하고 클라우드에서 실행합니다. 이제 문제가 해결되면 소프트웨어를 실행하는 사람들로부터 수익을 얻을 수 있습니다. 한 가지 일만 제대로 수행하더라도 보상을 받게 됩니다. 새 버전을 판매하기 위해 제품에 기능을 추가할 필요는 없습니다. 반면에, 귀하의 코드 비효율성에 대한 대가를 치르는 사람은 이제 귀하 자신입니다. 최적이 아닌 모든 루틴은 AWS 청구서에 표시됩니다.


따라서 새로운 환경에서는 이제 더 적은 기능이 필요하지만 무엇을 얻든 더 나은 성능이 필요합니다.


그리고 갑자기 모든 "C++ 킬러", 심지어 내가 Rust, Julia, D처럼 진심으로 사랑하고 존경하는 이들조차도 21세기의 문제를 다루지 않는다는 것이 밝혀졌습니다. 그들은 여전히 XX에 갇혀 있습니다. 더 적은 버그로 더 많은 기능을 작성하는 데 도움이 되지만 임대한 하드웨어에서 마지막 실패를 짜내야 할 때는 별로 도움이 되지 않습니다.


C++에 비해 경쟁 우위를 제공하지 않습니다. 아니면, 심지어 서로에 대해서도 마찬가지입니다. 예를 들어 Rust, Julia, Cland 등 대부분은 동일한 백엔드를 공유합니다. 모두가 같은 차를 공유한다면 자동차 경주에서 이길 수 없습니다.


그렇다면 C++ 또는 일반적으로 모든 기존 AOT(Ahead-of-Time) 컴파일러에 비해 경쟁 우위를 제공하는 기술은 무엇입니까? 좋은 질문. 물어봐서 다행이에요.


C++ 킬러 넘버 1. 나선형

그러나 Spiral 자체를 다루기 전에 직관이 얼마나 잘 작동하는지 확인해 봅시다. 표준 C++ 사인 함수와 사인의 4부분 다항식 모델 중 어느 것이 더 빠르다고 생각하시나요?


 auto y = std::sin(x); // vs. y = -0.000182690409228785*x*x*x*x*x*x*x +0.00830460224186793*x*x*x*x*x -0.166651012143690*x*x*x +x;


다음 질문. 단락이 포함된 논리 연산을 사용하거나 컴파일러를 속여 이를 피하고 논리 표현식을 대량으로 계산하는 것 중 무엇이 더 빠르게 작동합니까?


 if (xs[i] == 1 && xs[i+1] == 1 && xs[i+2] == 1 && xs[i+3] == 1) // xs are bools stored as ints // vs. inline int sq(int x) { return x*x; } if(sq(xs[i] - 1) + sq(xs[i+1] - 1) + sq(xs[i+2] - 1) + sq(xs[i+3] - 1) == 0)


그리고 하나 더. 스왑 정렬과 인덱스 정렬 중 트리플렛을 더 빠르게 정렬하는 방법은 무엇입니까?


 if(s[0] > s[1]) swap(s[0], s[1]); if(s[1] > s[2]) swap(s[1], s[2]); if(s[0] > s[1]) swap(s[0], s[1]); // vs. const auto a = s[0]; const auto b = s[1]; const auto c = s[2]; s[int(a > b) + int(a > c)] = a; s[int(b >= a) + int(b > c)] = b; s[int(c >= a) + int(c >= b)] = c;


모든 질문에 단호하게, 심지어 생각하거나 검색하지도 않고 대답했다면 직관이 실패한 것입니다. 당신은 함정을 보지 못했습니다. 이러한 질문 중 어느 것도 맥락 없이는 명확한 답을 얻을 수 없습니다.


코드는 어떤 CPU 또는 GPU를 대상으로 합니까? 어떤 컴파일러가 코드를 작성해야 합니까? 어떤 컴파일러 최적화가 켜져 있고 어떤 것이 꺼져 있습니까? 모든 것을 알고 있을 때만 예측을 시작할 수 있습니다. 더 나아가 각 특정 솔루션의 실행 시간을 측정하는 것이 더 좋습니다.


  1. -O2 -march=native를 사용하여 clang 11로 빌드하고 Intel Core i7-9700F 에서 실행하는 경우 다항식 모델은 표준 사인보다 3배 빠릅니다 . 그러나 --use-fast-math를 사용하여 nvcc와 GPU, 즉 GeForce GTX 1050 Ti Mobile 로 구축한 경우 표준 사인은 모델보다 10배 빠릅니다.


  2. 벡터화된 산술을 위해 단락 논리를 거래하는 것은 i7에서도 의미가 있습니다. 스니펫이 두 배 빠르게 작동하도록 만듭니다. 그러나 동일한 clang 및 -O2를 사용하는 ARMv7에서는 표준 로직이 마이크로 최적화보다 25% 빠릅니다 .


  3. 그리고 인덱스 정렬과 스왑 정렬의 경우 인덱스 정렬은 Intel에서 3배 더 빠르고, 스왑 정렬은 GeForce에서 3배 더 빠릅니다 .


따라서 우리 모두가 그토록 좋아하는 마이크로 최적화는 코드 속도를 3배 향상시킬 수도 있고 속도를 90% 느리게 할 수도 있습니다. 그것은 모두 상황에 따라 다릅니다. 컴파일러가 우리를 위해 가장 좋은 대안을 선택할 수 있다면 얼마나 좋을까요. 예를 들어 우리가 빌드 타겟을 전환할 때 인덱스 정렬이 기적적으로 스왑 정렬로 바뀔 것입니다. 하지만 그럴 수 없었습니다.


  1. 컴파일러가 사인을 다항식 모델로 다시 구현하여 속도를 위해 정밀도를 교환하도록 허용하더라도 여전히 목표 정밀도를 알 수 없습니다. C++에서는 "이 함수에는 해당 오류가 허용됩니다"라고 말할 수 없습니다. 우리가 가진 것은 "--use-fast-math"와 같은 컴파일러 플래그이며 번역 단위 범위에만 있습니다.


  2. 두 번째 예에서 컴파일러는 값이 0 또는 1로 제한되어 있다는 사실을 모르고 우리가 할 수 있는 최적화를 제안할 수도 없습니다. 적절한 bool 유형을 사용하여 이를 암시할 수도 있었지만 이는 완전히 다른 문제였습니다.


  3. 그리고 세 번째 예에서는 코드 조각이 동의어로 인식될 만큼 크게 다릅니다. 코드를 너무 자세히 설명했습니다. 단지 std::sort였다면 이미 컴파일러에게 알고리즘을 선택할 수 있는 더 많은 자유가 주어졌을 것입니다. 그러나 스왑 정렬이 아닌 인덱스 정렬은 선택하지 않았을 것입니다. 둘 다 대규모 배열에서는 비효율적이고 std::sort는 일반적인 반복 가능한 컨테이너에서 작동하기 때문입니다.


이것이 우리가 Spiral 에 도달하는 방법입니다. Carnegie Mellon University와 Eidgenössische Technische Hochschule Zürich의 공동 프로젝트입니다. TL&DR: 신호 처리 전문가들은 모든 새로운 하드웨어에 대해 자신이 선호하는 알고리즘을 손으로 다시 작성하는 것에 지루해졌고 이를 수행하는 프로그램을 작성했습니다. 이 프로그램은 알고리즘에 대한 높은 수준의 설명과 하드웨어 아키텍처에 대한 자세한 설명을 취하고 지정된 하드웨어에 대해 가장 효율적인 알고리즘 구현이 될 때까지 코드를 최적화합니다.


Fortran과 유사 제품의 중요한 차이점인 Spiral은 실제로 수학적 의미에서 최적화 문제를 해결합니다. 런타임을 목표 함수로 정의하고 하드웨어 아키텍처에 의해 제한되는 구현 변형의 요소 공간에서 전역 최적을 찾습니다. 이것은 컴파일러가 실제로는 절대 하지 않는 일입니다.


컴파일러는 진정한 최적을 찾지 않습니다. 프로그래머가 가르친 경험적 방법에 따라 코드를 최적화합니다. 본질적으로 컴파일러는 최적의 솔루션을 찾는 기계로 작동하지 않고 오히려 어셈블리 프로그래머로 작동합니다. 좋은 컴파일러는 좋은 어셈블리 프로그래머처럼 작동하지만 그게 전부입니다.




Spiral은 연구 프로젝트입니다. 범위와 예산이 제한되어 있습니다. 그러나 그것이 보여주는 결과는 이미 인상적입니다. 고속 푸리에 변환에서 해당 솔루션은 MKL 및 FFTW 구현 모두를 결정적으로 능가합니다. 그들의 코드는 ~2배 더 빠릅니다. 인텔에서도요.


성취의 규모를 강조하기 위해 MKL은 Intel 자체, 즉 하드웨어를 가장 잘 사용하는 방법을 아는 사람들이 만든 수학 커널 라이브러리입니다. 그리고 WWTF AKA "서양에서 가장 빠른 푸리에 변환"은 알고리즘을 가장 잘 아는 사람들이 만든 고도로 전문화된 라이브러리입니다. 그들은 둘 다 그들이 하는 일에서 챔피언이며 Spiral이 그들을 두 배로 이겼다는 사실은 놀랍습니다.


Spiral이 사용하는 최적화 기술이 완성되고 상용화되면 C++뿐만 아니라 Rust, Julia, Fortran까지도 이전에 직면하지 못했던 경쟁에 직면하게 될 것입니다. 고급 알고리즘 설명 언어로 작성하면 코드가 2배 더 빨라진다면 누가 C++로 작성하겠습니까?


C++ 킬러 2번. Numba

최고의 프로그래밍 언어는 이미 잘 알고 있는 언어입니다. 수십 년 동안 대부분의 프로그래머에게 가장 잘 알려진 언어는 C였습니다. 또한 C는 TIOBE 지수에서 다른 C 유사 언어와 함께 상위 10위권을 차지했습니다. 그러나 불과 2년 전에는 전례 없는 일이 일어났습니다. C는 다른 것에 첫 번째 위치를 부여했습니다.


"다른 것"은 Python인 것으로 보입니다. 90년대에는 누구도 심각하게 받아들이지 않았던 언어였습니다. 이미 우리가 많이 갖고 있던 또 다른 스크립팅 언어였기 때문입니다.



누군가는 "아, 파이썬은 느리다"고 말할 것이고 이것은 용어상 말도 안 되는 소리이기 때문에 바보처럼 보일 것입니다. 아코디언이나 프라이팬처럼 언어도 빠르지도 느리지도 않습니다. 아코디언의 속도가 연주하는 사람에 따라 달라지는 것처럼, 언어의 "속도"는 컴파일러의 속도에 따라 달라집니다.


“하지만 파이썬은 컴파일된 언어가 아닙니다.” 누군가 계속 말을 이어가다가 다시 한 번 실수를 저지를 수도 있습니다. Python 컴파일러는 많이 있으며 그 중 가장 유망한 것은 Python 스크립트입니다. 설명하겠습니다.


한때 프로젝트를 한 적이 있었습니다. 원래 Python으로 작성된 다음 "성능을 위해" C++로 다시 작성한 다음 GPU로 포팅한 3D 프린팅 시뮬레이션입니다. 그런 다음 빌드를 Linux로 포팅하고 GPU 코드를 최적화하는 데 몇 달을 보냈습니다. Tesla M60은 당시 AWS에서 가장 저렴했기 때문에 Python의 원본 코드와 함께 C++/CU 코드의 모든 변경 사항을 검증했습니다. 그래서 나는 내가 일반적으로 전문으로 하는 일, 즉 기하학적 알고리즘을 고안하는 일을 제외한 모든 일을 했습니다.


그리고 마침내 모든 것이 제대로 작동했을 때 브레멘에서 아르바이트를 하고 있는 학생이 저에게 전화를 해서 "당신은 이질적인 일에 능숙한군요. GPU에서 하나의 알고리즘을 실행하는 것을 도와주실 수 있나요?"라고 물었습니다. 물론! 나는 그에게 CUDA, CMake, Linux 빌드, 테스트 및 최적화에 대해 말했습니다. 아마 한 시간 정도 이야기를 했을 거예요. 그는 그 모든 것을 매우 정중하게 들었지만 마지막에는 이렇게 말했습니다. “모두 매우 흥미롭습니다. 하지만 매우 구체적인 질문이 있습니다. 그래서 함수가 있고 정의 전에 @cuda.jit를 썼는데 Python은 배열에 대해 말하고 커널을 컴파일하지 않습니다. 여기서 문제가 무엇인지 아시나요?”


나는 몰랐다. 그는 하루 만에 그것을 스스로 알아냈습니다. 분명히 Numba는 기본 Python 목록에서는 작동하지 않으며 NumPy 배열의 데이터만 허용합니다. 그래서 그는 그것을 알아내고 GPU에서 알고리즘을 실행했습니다. 파이썬에서. 그 사람은 내가 몇 달 동안 고생한 문제가 하나도 없었습니다. Linux에서 사용하시겠습니까? 문제 없습니다. Linux에서 실행해 보세요. Python 코드와 일관성을 유지하시겠습니까? 문제 없습니다. Python 코드입니다. 대상 플랫폼에 맞게 최적화하시겠습니까? 다시는 문제가 되지 않습니다. Numba는 미리 컴파일되지 않고 이미 배포되었을 때 요청에 따라 컴파일되므로 코드를 실행하는 플랫폼에 맞게 코드를 최적화합니다.


정말 멋지지 않나요? 음 ... 아니. 어쨌든 나에게는 그렇지 않습니다. 나는 Numba에서는 결코 발생하지 않는 문제를 해결하기 위해 C++로 몇 달을 보냈고, 브레멘에서 온 파트타임 직원이 며칠 만에 같은 일을 해냈습니다. Numba와의 첫 경험이 아니었다면 몇 시간은 걸렸을 것입니다. 그렇다면 이 Numba는 무엇일까요? 그것은 어떤 종류의 마법입니까?


마법은 없습니다. Python 데코레이터는 모든 코드 조각을 추상 구문 트리로 변환하므로 원하는 대로 무엇이든 할 수 있습니다. Numba 는 지원하는 모든 플랫폼과 백엔드를 사용하여 추상 구문 트리를 컴파일하려는 Python 라이브러리입니다. 대규모 병렬 방식으로 CPU 코어에서 실행되도록 Python 코드를 컴파일하려면 Numba에 그렇게 컴파일하라고 지시하면 됩니다. GPU에서 무언가를 실행하려면 다시 .


 @cuda.jit def matmul(A, B, C): """Perform square matrix multiplication of C = A * B.""" i, j = cuda.grid(2) if i < C.shape[0] and j < C.shape[1]: tmp = 0. for k in range(A.shape[1]): tmp += A[i, k] * B[k, j] C[i, j] = tmp


Numba는 C++를 쓸모없게 만드는 Python 컴파일러 중 하나입니다. 그러나 이론적으로는 동일한 백엔드를 사용하기 때문에 C++보다 낫지 않습니다. GPU 프로그래밍에는 CUDA를 사용하고 CPU에는 LLVM을 사용합니다. 실제로 모든 새로운 아키텍처에 대해 사전 재구축이 필요하지 않기 때문에 Numba 솔루션은 모든 새로운 하드웨어와 사용 가능한 최적화에 더 잘 적응합니다.


물론 Spiral과 마찬가지로 뚜렷한 성능상의 이점을 갖는 것이 더 좋을 것입니다. 그러나 Spiral은 연구 프로젝트에 가깝습니다. C++를 죽일 수도 있지만 결국에는 운이 좋을 때만 가능합니다. Python을 사용하는 Numba는 지금 실시간으로 C++를 교살합니다. Python으로 작성할 수 있고 C++의 성능을 갖고 있다면 왜 C++로 작성하고 싶겠습니까?


C++ 킬러 3번. ForwardCom

다른 게임을 해보자. 나는 세 가지 코드 조각을 제시할 것이며, 그 중 어느 것이 어셈블리로 작성되었는지 추측하게 될 것입니다. 여기 있습니다:


 invoke RegisterClassEx, addr wc ; register our window class invoke CreateWindowEx,NULL, ADDR ClassName, ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT, CW_USEDEFAULT,\ CW_USEDEFAULT, CW_USEDEFAULT,\ NULL, NULL, hInst, NULL mov hwnd,eax invoke ShowWindow, hwnd,CmdShow ; display our window on desktop invoke UpdateWindow, hwnd ; refresh the client area .while TRUE ; Enter message loop invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw


 (module (func $add (param $lhs i32) (param $rhs i32) (result i32) get_local $lhs get_local $rhs i32.add) (export "add" (func $add)))


 v0 = my_vector // we want the horizontal sum of this int64 r0 = get_len ( v0 ) int64 r0 = round_u2 ( r0 ) float v0 = set_len ( r0 , v0 ) while ( uint64 r0 > 4) { uint64 r0 >>= 1 float v1 = shift_reduce ( r0 , v0 ) float v0 = v1 + v0 }


그렇다면 어느 것이 조립되어 있습니까? 세 가지 모두 그렇다고 생각하신다면, 축하드립니다! 당신의 직감은 이미 훨씬 나아졌습니다!


첫 번째는 MASM32에 있습니다. 사람들은 "if"와 "while"을 사용하여 기본 Windows 응용 프로그램을 작성하는 매크로 어셈블러입니다. 맞습니다. 오늘날에는 "쓰는 데 사용되는" 것이 아니라 "쓰는" 것입니다. Microsoft는 Win32 API를 사용하여 Windows의 이전 버전과의 호환성을 열심히 보호하므로 지금까지 작성된 모든 MASM32 프로그램이 최신 PC에서도 잘 작동합니다.


아이러니한 점은 C가 PDP-7에서 PDP-11로의 UNIX 변환을 더 쉽게 만들기 위해 발명되었다는 것입니다. 이는 70년대 하드웨어 아키텍처의 폭발적인 캄브리아기 시대를 살아남을 수 있는 휴대용 어셈블러로 설계되었습니다. 하지만 21세기에는 하드웨어 아키텍처가 너무 느리게 발전하여 20년 전 MASM32에서 작성한 프로그램이 오늘날 완벽하게 조립 및 실행되지만 작년에 CMake 3.21로 구축한 C++ 애플리케이션이 오늘날 CMake로 구축될 것이라는 확신이 없습니다. 3.25.


두 번째 코드는 웹 어셈블리입니다. 이는 매크로 어셈블러도 아니며 "if"와 "while"이 없으며 브라우저에서 사람이 읽을 수 있는 기계어 코드에 가깝습니다. 아니면 다른 브라우저. 개념적으로 모든 브라우저.


웹 어셈블리 코드는 하드웨어 아키텍처에 전혀 의존하지 않습니다. 그것이 제공하는 기계는 추상적이고 가상적이며 보편적입니다. 원하는 대로 부르세요. 이 텍스트를 읽을 수 있다면 실제 컴퓨터에 이미 텍스트가 있는 것입니다.


하지만 가장 흥미로운 코드는 세 번째 코드입니다. C++ 및 어셈블리 최적화 매뉴얼의 저명한 저자인 Agner Fog가 제안하는 어셈블러인 ForwardCom입니다. 웹 어셈블리와 마찬가지로 이 제안은 어셈블러보다는 하위 호환성뿐만 아니라 상위 호환성도 가능하도록 설계된 보편적인 명령어 세트를 다루고 있습니다. 그러므로 이름. ForwardCom의 전체 이름은 " 개방형 순방향 호환 명령어 세트 아키텍처 "입니다. 즉, 집회 제안이라기보다는 평화 조약 제안이라는 뜻입니다.


x64, ARM, RISC-V 등 가장 일반적인 아키텍처 제품군은 모두 서로 다른 명령어 세트를 가지고 있다는 것을 알고 있습니다. 그러나 이런 식으로 유지해야 할 합당한 이유를 아는 사람은 아무도 없습니다. 가장 간단한 프로세서를 제외하고 모든 최신 프로세서는 사용자가 제공하는 코드가 아니라 입력을 변환하는 마이크로코드를 실행합니다. 따라서 Intel에 대한 이전 버전과의 호환성 레이어가 있는 것은 M1뿐만 아니라 모든 프로세서에는 본질적으로 모든 이전 버전에 대한 이전 버전과의 호환성 레이어가 있습니다.


그렇다면 아키텍처 디자이너가 향후 호환성을 위해 유사한 레이어에 동의하는 것을 방해하는 것은 무엇일까요? 직접 경쟁하는 회사의 상충되는 야망 외에는 아무것도 없습니다. 그러나 프로세서 제조업체가 어느 시점에서 다른 모든 경쟁사마다 새로운 호환성 레이어를 구현하는 대신 공통 명령 세트를 갖기로 결정한다면 ForwardCom은 어셈블리 프로그래밍을 다시 주류로 가져올 것입니다. 이 상위 호환성 계층은 모든 어셈블리 프로그래머의 심각한 신경증을 치료할 것입니다. "이 특정 아키텍처에 대해 일생에 한 번뿐인 코드를 작성하면 이 특정 아키텍처가 1년 안에 쓸모 없게 된다면 어떻게 될까요?"


향후 호환성 레이어를 사용하면 결코 쓸모 없게 되지 않습니다. 그게 요점입니다.


어셈블리 프로그래밍은 또한 어셈블리로 작성하는 것이 어렵고 따라서 비실용적이라는 통념으로 인해 방해를 받습니다. Fog의 제안은 이 문제도 해결합니다. 사람들이 어셈블리로 작성하는 것이 어렵다고 생각하고 C로 작성하는 것은 그렇지 않다고 생각한다면 어셈블러를 C처럼 보이게 만들어 보겠습니다. 문제 없습니다. 현대 어셈블리 언어가 50년대 할아버지의 모습과 똑같아 보일 이유가 없습니다.


방금 세 개의 어셈블리 샘플을 직접 보셨습니다. 그 중 어느 것도 "전통적인" 어셈블리처럼 보이지 않으며, 그래서도 안 됩니다.


따라서 ForwardCom은 결코 쓸모가 없어지지 않고 "전통적인" 어셈블리를 배우게 만들지 않는 최적의 코드를 작성할 수 있는 어셈블리입니다. 모든 실질적인 고려 사항을 고려하면 이는 미래의 C입니다. C++이 아닙니다.

그렇다면 С++는 언제 마침내 죽게 될까요?

우리는 포스트모던 세계에 살고 있습니다. 더 이상 사람 외에는 아무것도 죽지 않습니다. COBOL, Algol 68, Ada처럼 라틴어가 실제로 죽지 않았던 것처럼 C++도 삶과 죽음 사이의 영원한 반쪽 존재로 운명지어져 있습니다. C++는 실제로 결코 죽지 않을 것이며, 더 새롭고 강력한 기술에 의해서만 주류에서 밀려날 것입니다.


글쎄, "밀어질 것이다"가 아니라 "밀어진다". 저는 현재 직장에 C++ 프로그래머로 입사했고 오늘은 Python으로 업무를 시작합니다. 내가 방정식을 작성하면 SymPy가 나를 위해 방정식을 풀어주고 그 해를 C++로 변환합니다. 그런 다음 이 코드를 C++ 라이브러리에 붙여넣습니다. 형식을 지정하는 데 신경을 쓰지 않아도 됩니다. 어쨌든 clang-tidy가 해당 작업을 수행해 주기 때문입니다. 정적 분석기는 네임스페이스를 엉망으로 만들지 않았는지 확인하고 동적 분석기는 메모리 누수를 확인합니다. CI/CD는 크로스 플랫폼 컴파일을 처리합니다. 프로파일러는 내 코드가 실제로 어떻게 작동하는지와 디스어셈블러가 왜 작동하는지 이해하는 데 도움이 됩니다.


C++를 "C++ 아님"으로 바꾸면 내 작업의 80%는 정확히 동일하게 유지됩니다. C++는 내가 하는 대부분의 작업과 전혀 관련이 없습니다. 나에게 있어 C++은 이미 80%가 죽었다는 의미일까요?



안정된 확산에 의해 전개된 리드 이미지.