설명하기 전에 먼저 표현을 정정할 게 하나 있어.

우리가 흔히 "스택이 힙보다 빠르다"고 하는 건, 사실 "스택 할당된 변수에 접근하는 것이 힙 할당 변수에 접근하는 것보다 빠르다"라고 해야 해.

이게 무슨 말이냐면, 스택 메모리 영역이 힙 메모리 영역보다 그 자체로 빠른 건 절대로 아니란 거야. 생각해보면 스택이나 힙이나 가상메모리 영역에 불과하고, 하드웨어로 내려가면 똑같이 RAM의 어딘가에 저장된 페이지에 불과하니까 둘의 속도가 다를 이유는 전혀 없어.


그런데 접근하는 속도는 왜 차이가 날까?

현재 쓰이는 절대다수의 CPU는 서브루틴 (어셈블리에서 함수를 일컫는 말 정도로 생각하면 돼)의 스택 base 주소 (가장 바닥 주소)를 저장하기 위한 레지스터를 따로 지정해두고 있고, 대부분 컴파일러들도 이걸 존중해서 함수를 호출할 때마다 이 레지스터에 새로운 함수의 스택 바닥 주소를 매번 저장하도록 어셈블리를 출력해.

존중한다고 한 이유는 사실 저 레지스터를 쓰는 게 강제사항은 아니라서 그런데, 중요한 건 아니니 패스.


게다가 컴파일러는 주어진 프로그램, 또는 함수들을 컴파일하는 과정에서 스택에 할당될 변수들은 정확히 어떤 크기고 어떤 순서로 할당될지를 알 수가 있어. 스택을 기준으로 상대 위치 (offset)을 정확하게 알 수 있다는 거야.

물론 프로그램을 실행하는 중에는 스택 레지스터에 항상 스택 바닥 주소가 저장돼있다는 것도 알고 있겠지?

이러면 스택에 할당된 변수들은 주소를 정말 간단하게 계산할 수 있어. 변수의 offset을 알고 있고 스택 바닥 주소를 알고 있다면, 그냥 이 둘을 더하면 해당 변수의 정확한 주소가 나오는 거잖아? (예를 들어 addr <- SP + 36)

물론 그 offset은 어셈블리에 하드코딩 하면 되는 거고.

즉 스택에 할당된 변수들은 add 이후 load만으로 변수의 값을 읽어들일 수 있다는 거야.


하지만 힙에 할당된 변수들은 그게 안 되겠지. 힙은 결국 OS가 관리하는 거니까, 어셈블리를 컴파일하는 단계에서는 절대로 이 변수가 힙의 어떤 offset에 할당될지 알 수 없어.

그래서 OS에서 할당받은 주소를 스택에 할당된 변수에 저장해두는 거고, 실제로 힙에 접근할 때는 add 이후 load로 변수의 주소값을 읽어들인 뒤에 다시 그 주소를 load하는 게 최선이라는 거지.

무슨 수를 써도 스택 할당 변수에 비해 load가 한 번 추가될 수밖에 없어.


여기에 덤으로, 스택에 할당된 변수들은 대부분 다닥다닥 붙어 있으니까 페이지 테이블 하나, 또는 캐시 라인 사이즈 안에 들어갈 가능성이 커. 반면 힙에 할당된 변수들은 OS가 임의로 할당해주니 이런 걸 기대하기 어렵겠지.

그래서 스택 할당 변수들은 실제 물리적인 접근 속도도 대체로 힙 할당 변수들보다 빠른 편이야.


주저리주저리 설명하긴 했지만, 원리가 이렇단 거고 실제 프로그래밍할 때는 그냥 스택이 힙보다 빠르다고 생각해도 별 문제는 없을 거야.

다만 조심해야 할 게, 이건 프로그래밍 언어 레벨에서는 전혀 다른 이야기가 돼.

흔히들 C/C++에서 스택과 힙의 차이라고 하는 것에 대해선 이 글의 댓글에서 storage duration에 대한 내용을 한번 읽어봐. 어쩌면 이 글보다 더 중요한 내용이라고 생각해.