
안녕하세요!
이번 포스트는 요새 면접 다니면서 부족하다고 느끼는
CS 위주 기술 면접에 대해 작성해보려고 합니다!
내가 보려고 만드는 기술 면접 리스트
< 컴퓨터 구조편! >
문제 리스트
- CPU와 메모리는 각각 어떤 역할을 하나?
- 프로그램은 어떻게 실행되는가?
- 메모리 계층 구조를 간단히 설명해보세요.
- 캐시 메모리는 왜 필요한가?
- 지역성(Locality)이란?
- 스택(Stack)과 힙(Heap)의 차이는?
- 콜 스택(Call Stack)이란?
- 스택 오버플로우가 발생하는 시점과 이유
- 메모리 누수는 왜 성능 문제로 이어지나요?
- 멀티코어 CPU가 프론트엔드에 주는 이점은?
- 컨텍스트 스위칭이란?
- 무거운 연산이 UI를 멈추게하는 이유
- Web Worker는 왜 필요한가요?
들어가기 전,
컴퓨터 구조라는 과목은, 말 그대로 컴퓨터의 구조와 관련된 학문으로 내가 공부하고 있는 프론트엔드를 넘어 웹 개발자라면 기본적으로 인지하고 있어야하는 내용입니다.
따라서, 공부한 내용은 컴퓨터 구조라는 과목의 기본적인 내용을 기반으로 나아가 프론트엔드 개발에 연결하여 면접을 준비하였습니다.
컴퓨터의 구조
컴퓨터는 복잡한 구조로 구성되어 있으며, 부품을 하나씩 설명하면 공부만 하다가 면접을 못 볼 것 같아 프로그램 실행을 위주로 공부하였습니다.
컴퓨터는 기본적으로 CPU, 메모리(RAM), 보조기억장치(SSD)로 구성(이외 부품은 생략)되어 있으며, 각 요소는 다음과 같은 역할을 가지고 있습니다.
CPU
- 명령어를 가져와 실행합니다.
- 연산(ALU) + 제어(Control Uni) 형태
- 저장은 거의 불가(레지스터만 존재)
* 레지스터
- CPU의 빠른 연산을 위한 접근이 가장 빠른 임시 저장공간(연산에 필요한 값만 저장)
- RAM으로 대체할 경우 CPU연산 속도가 값을 읽어오는 시간으로 인해 느려집니다.
메모리
- 실행 중인 코드와 데이터가 올라가는 공간
- CPU가 직접 접근 가능한 유일한 장소
보조기억장치
- 프로그램이 보관되는 곳
- CPU는 직접 접근 불가
프로그램 실행 과정
프로그램은 SSD, 즉 보조기억장치에 저장되어 있는 명령어들의 집합체입니다.
따라서 우리가 프로그램을 실행하면 다음과 같은 과정으로 실행됩니다.
- 보조 기억 장치에 있던 프로그램을 메모리에 올라감.
- CPU가 메모리에 있는 프로그램의 명령어를 순서대로 실행.
간단하게 표현해보면,
보조 기억 장치 -> 메모리 -> CPU 형태로 프로그램이 이동하여 실행되는 형태입니다.
예를 들면, 우리가 Chrome.exe라는 프로그램을 실행하면,
- 보조 기억 장치(SSD)에 저장되어 있는 Chrome.exe파일을 메모리(RAM)에 전달하여 저장.
- 메모리(RAM)에 저장되어 있는 Chrome.exe 프로그램을 CPU가 읽어 차례대로 명령어 실행.
그렇다면 CPU는 명령어를 어떻게 실행할까? 라는 의문이 듭니다.
CPU의 명령어 실행 사이클
CPU는 Fetch -> Decode -> Execute 형태의 사이클을 반복하며 명령어를 실행합니다.
- Fetch(가져오기)
- PC(Program Couter) 레지스터가 가리키는 주소에서 명령어를 메모리에서 가져옴.
- 가져온 명령어는 IR(Instruction Register)
- PC: 다음 실행할 명령어 주소(실행할 명령어 주소)
- IR: PC에 저장되어 있는 주소에 접근하여 현재 수행할 명령어를 가져와 저장(실행할 명령어가 저장된 레지스터)
- Decode(해석하기)
- Control Unit이 IR에 있는 명령어를 해석
- 명령어가 연산인지, 메모리 접근인지, 점프인지 판단
- 이는 명령어 형식(ISA)에 따라 해석합니다.
- Execute(실행)
- 명령어 종류에 따라 실행이 달라집니다.
- 연산 명령 -> ALU가 연산(ALU: 비트 연산 방식의 연산장치)
- 메모리 명령 -> 메모리 접근
- 분기 명령 -> PC 값 변경(다음 명령어 수행에 대한 명령어 - ex) 함수 실행)
- 이에 대한 결과는 레지스터나 메모리에 기록됩니다.
- PC 업데이트(암묵적인 단계)
- 일반 명령어: PC + 1 (또는 + 명령어 크기)로 다음 명령어로 흐름을 연결합니다.
- 점프 명령어: PC를 다른 주소로 변경하여 명령어 실행을 건너뜁니다.(if문, for문 등)
예를들어, 아래와 같은 JavaScript의 코드를 실행한다면 다음과 같은 흐름으로 수행됩니다.
// b와 c는 이미 선언되어 있다고 가정
a = b + c;
- b를 메모리에서 레지스터로 로드
- c를 메모리에서 레지스터로 로드
- ALU로 덧셈
- 결과를 레지스터에 저장
- 결과를 메모리에 다시 저장(a)
여기서 문제 리스트의 1번과 2번 답변을 찾을 수 있습니다.
1번 문제에 대한 답변
CPU와 메모리는 각각 어떤 역할을 하나?
CPU는 메모리에 저장된 명령어를 가져와 해석하고 실행하는 장치이며, 실행 과정에서 필요한 데이터는 메모리에서 가져와 CPU 내부의 레지스터에 저장한 뒤 연산을 수행합니다.
또한, 메모리는 CPU가 실행할 명령어와 데이터를 저장해두는 실행 공간입니다. CPU가 유일하게 접근할 수 있는 저장공간으로 CPU 명령어 수행을 지원하기 위한 목적의 저장공간으로 전원이 꺼지면 사라지는 휘발성의 특징을 가지고 있습니다.
2번 문제에 대한 답변
프로그램은 어떻게 실행되는가?
프로그램은 보조 기억 장치에 저장되어 있으며, 명령어의 집합입니다.
프로그램을 실행하면 보조 기억 장치에서 메모리로 프로그램을 로드한 후, CPU가 메모리에 접근하여 명령어를 가져와 해석하고 실행하며, 분기 명령에 따라 실행 흐름을 제어합니다.
메모리의 계층
여기까지 이해하셨다면, 이런 궁금증이 생길 수 있습니다.
메모리를 하나만 사용하면 안 되나요? 굳이 여러 개로 나눠놓은 이유가 있나요?
이 궁금증은 CPU의 명령어 수행 과정 속에서 답을 얻을 수 있습니다.
기본적으로 메모리(저장장치)는 전원이 꺼져도 유지되어야하는 영구 저장 기능이 필요합니다. 예를 들어, 프로그램을 설치했는데 전원을 껐다 키는 형태로 프로그램이 지워진다면 우리는 컴퓨터 사용에 많은 불편함을 느껴야할 것입니다.
하지만 이러한 비휘발성 저장장치는 속도가 느리기 때문에, 만약 실행을 위한 메모리까지 하나로 통합한다면 CPU가 명령어와 데이터를 처리할 때마다 느린 저장장치에 직접 접근해야 하는 상황이 발생합니다.
이 경우 CPU는 연산보다 메모리 접근을 기다리는 시간이 길어지고, 그 결과 전체 시스템 성능이 크게 저하됩니다.
따라서 저장용 장치와 실행용 메모리를 분리하여 성능과 사용성을 모두 만족시키는 구조가 필요합니다.
그러므로 CPU 연산속도의 효율성을 높이기 위해서 메모리를 CPU와의 거리, 속도, 비용에 따라 구분(계층화)하여 사용합니다.
위 사진은 메모리의 사용 빈도, 속도, 가격, 용량을 비교한 자료입니다.
자료에서 보듯이 레지스터는 CPU에 가장 가까운 메모리로 접근 속도가 가장 빠르며, 계층이 아래로 갈수록 메인 메모리, 로컬 저장장치(SSD), 외부 저장장치 순으로 접근 속도가 느려집니다.
이러한 계층 구조는 CPU가 메모리 접근으로 인해 대기하지 않도록, 자주 사용하는 데이터를 가까운 메모리에 두어 전체 성능을 향상시키기 위한 설계입니다.
3번 문제에 대한 답변을 여기서 찾을 수 있습니다.
3번 문제에 대한 답변
메모리 계층 구조를 간단히 설명해보세요.
CPU와 메모리 사이에는 큰 속도 차이가 있기 때문에, 모든 데이터를 빠른 메모리로 구성하는 것은 비용과 물리적 한계가 있습니다.
그래서 자주 사용하는 데이터는 레지스터나 캐시에, 그렇지 않은 데이터는 메인 메모리나 저장장치에 두는 계층 구조를 통해 평균 접근 시간을 줄이도록 설계되었습니다.
캐시 메모리
메모리 계층으로 CPU의 속도와 메모리 접근 속도를 완화할 수 있지만, 이전 명령어 실행 과정을 보면 여전히 메모리에서 레지스터로 명령어를 로드하는 과정은 느립니다.
이 문제를 해결하기 위해 캐시 메모리가 존재합니다.
캐시 메모리는 자주 쓰는 데이터를 CPU에 가까이 두어 메모리 접근 속도를 향상 시킵니다.

이 자료를 다시보면, L1, L2, L3 캐시가 존재하고, 이는 메인 메모리보다는 빠른 것을 알 수 있습니다.
따라서, 메인 메모리에 접근하여 명령어를 로드하는 것 대신 캐시 메모리에 접근하여 명령어를 로드하는 것이 더 빠르며, 이는 CPU의 실행 속도에 영향을 주어 전체 성능에 연결됩니다.
여기서 L1, L2, L3는 캐시 레벨로, L1 -> L3로 갈수록 속도가 느려지며 용량은 커집니다.
주요 특징
- L1: 코어 내부에 존재하며 가장 빠르지만 가장 작은 용량을 가지고 있음
- L2: L1보다는 느리지만 L1 보다 큰 용량을 가지고 있음
- L3: 여러 코어가 공유하며 L1, L2에 비해 가장 큰 용량을 가지고 있음
캐시 메모리는 CPU가 어떤 데이터가 필요할 때, 다음과 같은 과정으로 동작합니다.
- 캐시 확인
- 캐시 메모리에 데이터가 있다면 바로 사용(캐시 히트, 메인 메모리에 접근 없음)
- 캐시 메모리에 데이터가 없다면 메인 메모리에 접근 (캐시 미스)
- 가져온 데이터를 캐시에 저장(다음에 쓸 가능성이 높은 데이터)
캐시를 발견하면 캐시 히트, 발현하지 못하고 메인 메모리로 접근했다면 캐시 미스라고 불립니다.
캐시 미스가 여러 번 반복된다면, 불필요한 탐색이 추가되어 효율성이 떨어집니다.
그렇다면 캐시 메모리에 저장되는 기준은 무엇일까요?
지역성
지역성이란, 프로그램이 메모리를 사용하는데 있어 일정한 패턴이 존재한다는 특성을 말합니다.
캐시 메모리는 지역성(Locality)이라는 성질에 의해 데이터를 저장합니다.
시간 지역성(Time Locality)
- 최근에 사용한 데이터는 곧 다시 사용될 가능성이 높다.
프로그램은 특정 데이터를 한 번 사용한 뒤, 짧은 시간 안에 다시 사용하는 경우가 많아 캐시 메모리는 이 특성을 활용하여 최근에 접근한 데이터를 유지합니다.
공간 지역성(Space Locality)
- 어떤 데이터를 사용하면, 그 근처 데이터도 곧 사용될 가능성이 높다.
메모리 상에서 연속된 주소에 위치한 데이터는 함께 접근되는 경우가 많습니다. 캐시 메모리는 이 특성을 활용해 단일 데이터가 아닌, 연속된 블록 단위로 데이터를 저장합니다.
코드 예시
// 예시
for (let i = 0; i < 1000 ; i++) {
sum += arr[i];
}
시간 지역성은 방금 접근한 arr[i] 를 지속적으로 사용하여 최근 사용된 arr라는 배열이 지속적으로 사용되는 것을 말합니다.
공간 지역성은 arr라는 배열에 인덱스가 커지는 형태로 지속적으로 접근하니, 메모리상 연속된 주소를 한 번에 묶어 캐시에 올리는 형태입니다.
캐시 교체 정책 (Cache Replacement Policy)
캐시는 용량이 매우 제한적이기 때문에, 새로운 데이터를 저장하기 위해 기존 데이터를 제거해야합니다.
이를 위해 캐시 교체 정책이 사용됩니다. LRU (Least Recently Used)는 가장 오랫동안 사용되지 않은 데이터부터 제거하는 방식으로, “최근에 사용되지 않았다면, 앞으로도 사용될 가능성이 낮다”는 가정에 기반으로 지역성 원리와 잘 맞아 실제 시스템에서 가장 널리 사용됩니다.
이외에도 FIFO (First In First Out), LFU (Least Frequently Used), Random Replacement 등 다양한 방식이 있습니다.
그렇다면 이제 4번과 5번에 대한 답변을 작성할 수 있습니다.
4번 문제에 대한 답변
캐시 메모리는 왜 필요한가?
캐시 메모리는 CPU와 메인 메모리(RAM) 사이의 속도 차이를 줄이기 위해, 자주 사용될 가능성이 높은 데이터를 저장해 두는 고속 메모리입니다. 이를 통해 CPU가 메모리 접근으로 인해 대기하지 않도록 하여 전체 실행 성능을 향상시킵니다.
(+ 추가 가능)
캐시 히트는 CPU가 필요한 데이터를 캐시에서 바로 찾은 경우이고, 캐시 미스는 캐시에 데이터가 없어 메인 메모리까지 접근해야 하는 경우입니다. 캐시 히트는 빠른 실행을, 캐시 미스는 CPU 대기와 성능 저하를 유발합니다.
5번 문제에 대한 답변
지역성(Locality)이란?
지역성(Locality)이란 프로그램이 메모리를 접근할 때 일정한 패턴을 보이는 성질로, 최근에 사용한 데이터나 인접한 데이터가 다시 사용될 가능성이 높다는 특성을 의미합니다. 캐시 메모리는 이러한 지역성을 기반으로 동작하여 평균 메모리 접근 시간을 줄입니다.
스택(Stack)과 힙(Heap) 메모리
지금까지는 메모리 접근 속도를 개선하여 CPU의 실행 효율을 높이는 구조에 대해 살펴봤다면, 이제부터는 메모리 내부에서 데이터를 어떻게 효율적으로 관리하는지에 대해 알아보겠습니다.
실행 흐름과 데이터 생명주기를 관리하는 스택과 힙 메모리는 프론트엔드 개발자라면, 콜 스택과 힙 메모리 영역을 자주 들어봤을 것이라고 생각합니다.
JavaScript는 실행 전에 실행 컨텍스트를 생성하며, 이 과정에서 객체나 함수는 힙 메모리에 할당된다.
이후 실행 컨텍스트가 콜 스택에 올라가고, 실행 단계에서 실제 코드가 실행된다.
(만약 이 이야기가 이해하기 어렵다면, 이전 포스트를 보고오시는걸 추천드립니다.)
2026.01.16 - [WEB/JavaScript] - 실행 컨텍스트(Execution Context)
실행 컨텍스트(Execution Context)
📦 컨텍스트컨텍스트(Context)란 상황, 맥락, 문맥 상의 의미를 의미합니다. 이를 개발자답게 해석해보면, 텍스트의 내용뿐만 아니라, 텍스트가 사용된 상황, 이전 문맥, 이후 맥락 등을 모두 가지
blog.dev-sg.cloud
스택 메모리와 힙 메모리는 메인 메모리(RAM)를 논리적으로 나눈 영역으로, 이외에도 다른 메모리가 있지만 현재 포스트에서는 두가지만 다루겠습니다.
스택 메모리(Stack Memory)
스택 메모리(Stack Memory)는 함수 호출과 실행 흐름을 관리하기 위한 메모리 영역입니다.
프로그램 실행 중에는 함수 호출이 자주 일어나며, 함수 호출은 실행 중인 함수와 함수 종료 후 돌아갈 주소, 그리고 함수가 가지고 있는 지역 변수와 같은 내용을 다룹니다.
이 과정을 관리하기 가장 적절한 방법은 선입후출(LIFO)이며, 따라서 스택을 활용한 메모리로 함수 호출과 실행 흐름을 관리합니다.
스택 메모리에는 다음과 같은 내용이 저장됩니다.
- 함수 호출정보
- 지역 변수
- 매개변수
- 복귀 주소
이는 현재 실행 중인 함수의 상태라고도 볼 수 있습니다.
또한, 스택 메모리는 실행 흐름을 빠르고 예측 가능하게 관리하기 위해서 크기가 제한되어 있습니다.
함수 호출은 빈번하게 일어나므로 할당/해제가 매우 빨라야하므로 메모리 구조가 단순해야합니다. 이로 인해 메모리 관리 비용은 낮아지고, 실행 예측 가능성은 높아집니다.
하지만 무한 재귀, 과도한 호출 시 스택 오버플로우가 발생할 수 있습니다.
힙 메모리(Heap Memory)
힙 메모리(Heap Memory)는 실행 중 동적으로 생성되는 데이터를 저장하는 메모리 영역입니다.
주의할 점은, 스택 메모리는 자료구조 스택과 같이 push, pop 연산이 일어나지만, 힙은 우리가 아는 우선순위 힙과는 다른 내용입니다.
힙(heap)은 자유롭게 쌓아 올리고, 연속되지 않으며, 필요할 때 할당하고 필요 없어지면 해제하는 질서 없이 쌓여 있는 큰 메모리 영역을 의미합니다.
힙 메모리는 모든 데이터가 함수 실행이 끝날 때 사라지는 것을 방지하고자 다음과 같은 데이터를 관리합니다.
- 여러 함수에서 공유되는 객체
- 크기를 미리 알 수 없는 데이터
- 함수 실행이 끝나도 유지되어야하는 데이터
해당 데이터는 스택 메모리에 둘 경우, 함수 종료와 함께 사라지기 때문에 스택 메모리와는 따로 관리해야합니다.
또한 힙 메모리는 실행 중 동적으로 생성되는 데이터를 저장하고 관리하기에 데이터 크기와 수를 예측할 수 없습니다.
따라서 힙 메모리는 상대적으로 자유롭게 늘어날 수 있도록 유연하게 관리됩니다. 이로 인해, 메모리 활용 유연성은 높아지지만, Garbage Collector와 같은 메모리 해제 방식이 필요합니다.
스택 메모리와 힙 메모리를 비교하여 정리하면 다음과 같습니다.
| 관점 | 스택(Stack) | 힙(Heap) |
| 관리 대상 | 실행 흐름 | 데이터 |
| 목적 | 함수 호출 관리 | 동적 데이터 저장 |
| 생명주기 | 함수 실행 동안 | 참조가 존재하는 동안 |
| 구조 | LIFO | 자유 구조 |
| 해제 방식 | 자동 | Garbage Collector / 명시적 해제 |
여기서 6, 7, 8번 문제에 대한 답변을 찾을 수 있습니다.
6번 문제에 대한 답변
스택(Stack)과 힙(Heap)의 차이는?
스택은 함수 호출과 실행 흐름을 관리하는 메모리 영역이고, 힙은 실행 중 동적으로 생성되는 데이터를 저장하는 메모리 영역입니다.
스택에는 지역 변수와 복귀 주소가 저장되어 함수 실행이 끝나면 자동으로 해제되고, 힙에는 객체나 배열과 같이 크기와 수명이 정해지지 않은 데이터가 저장되어 참조가 남아 있는 동안 유지됩니다.
7번 문제에 대한 답변
콜 스택(Call Stack)이란?
콜 스택은 현재 실행 중인 함수들의 실행 컨텍스트를 스택 구조로 관리하는 영역입니다.
함수가 호출되면 실행 컨텍스트가 콜 스택에 쌓이고, 함수가 종료되면 제거되면서 실행 흐름이 이전 함수로 돌아갑니다.
이를 통해 프로그램의 실행 순서가 관리됩니다.
8번 문제에 대한 답변
스택 오버플로우가 발생하는 시점과 이유
스택 오버플로우는 함수 호출이 계속 쌓여 스택의 최대 크기를 초과할 때 발생합니다.
주로 종료 조건이 없는 재귀 호출이나 과도하게 깊은 함수 호출이 원인이며, 스택은 빠른 실행 흐름 관리를 위해 크기가 제한된 구조이기 때문에 한계를 넘으면 오류가 발생하게 됩니다.
(+추가 내용)
스택은 실행 흐름을 빠르고 단순하게 관리하기 위해 크기가 제한되며, 힙은 실행 중 동적으로 생성되는 데이터를 관리하기 위해 유연하게 설계되었습니다. 이로 인해 스택 오버플로우는 즉각적으로 발생하는 반면, 힙 문제는 점진적인 성능 저하로 나타나는 경우가 많습니다.
메모리 누수와 페이지 성능
메모리 누수
메모리 누수(Memory Leak)란, 더 이상 필요없는 객체가 참조로 인해 해제되지 않고 힙 메모리에 계속 남아있는 상태입니다.
힙 메모리는 동적인 메모리 영역으로, 프로그램 실행 중 생성되는 객체와 변수들이 저장되며 이 데이터는 더 이상 사용되지 않는다고 판단되면 메모리에서 해제(제거)되어야 합니다. JavaScript는 이 해제 과정을 GC(Garbage Collection)이 담당합니다.
메모리 누수의 문제점
GC는 힙 메모리를 탐색하며 더 이상 참조되지 않는 데이터를 찾아 메모리 할당을 해제합니다.
이 과정은 보통 메인 스레드를 일시 정지한 상태에서 실행되기에 만약, GC가 오래 실행될수록 프레임 드롭, 스크롤 끊김, 입력 지연 등 UI 성능 저하로 이어질 수 있습니다.
메모리 누수가 일어나면, 필요없는 객체가 해제되지 않아 힙 메모리가 증가하고, 힙에 저장할 수 있는 공간이 줄어듭니다.
또한 CPU가 캐시 메모리를 사용할 때, 불필요한 데이터로 인해 캐시 히트 비중이 줄어 캐시 효율이 저하되며, 메모리에 자주 접근 해야하여 접근 비용도 증가합니다.
GC는 검사해야하는 객체가 늘어나 실행시간이 오래걸리고, 이는 페이지 성능에 직접적인 영향을 줍니다.
메모리 누수가 페이지 성능에 끼치는 영향
웹 페이지를 중점적으로 보면, GC가 메인 스레드에서 작업하고 있는 모든 내용을 중단하고 메인 스레드를 점유하여 렌더링을 지연시킬 수 있습니다. 이는 사용자에게 보여지는 페이지가 멈춘 것처럼 보이거나 사용자 인터렉션 지연 등의 문제로 이어져 사용자 경험을 저하시키는 문제로 이어집니다.
심한 경우, 메모리 누수는 힙 메모리의 저장 공간 한계로 브라우저가 강제종료되거나 모바일의 경우 OS가 브라우저 탭을 강제 종료하게 되는 경우로 이어져 심각한 문제로 다가옵니다.
그렇다면 가장 궁금해지는 내용은, 메모리 누수는 언제 자주 발생할까? 라는 내용입니다.
메모리 누수가 자주 발생하는 상황
메모리 누수는 다음과 같은 상황에서 자주 발생합니다.
- 제거되지 않은 이벤트 리스너
- 해제되지 않은 타이머
- 전역 객체에 남은 참조
- DOM 제거 후 남은 JS 참조
- 클로저로 인한 참조 유지
따라서, 메모리에서 불필요한 데이터는 해제할 수 있도록 관리하여 메모리 누수를 방지하는 것이 중요합니다.
여기서 9번 질문에 답변할 수 있습니다.
9번 문제에 대한 답변
메모리 누수는 왜 성능 문제로 이어지나요?
메모리 누수는 사용되지 않는 객체가 힙 메모리에 계속 남아 메모리 사용량과 GC(가비지 컬렉션) 비용을 증가시키고, 결국 가비지 컬렉션 비용이 커져 메인 스레드가 자주 중단됩니다.
이로 인해 렌더링과 이벤트 처리가 지연되어 페이지 성능 저하와 UI 지연으로 이어질 수 있습니다.
멀티코어 CPU와 프론트엔드
CPU는 캐시 메모리나 메인 메모리로부터 명령어를 레지스터에 로드하고, 이를 실행합니다.
이때, 명령어를 실행하는 흐름(단위)을 스레드(Thread)라고하며, 스레드는 PC(Program Counter), 레지스터 상태, 스택 메모리를 가지며, 어떤 명령어를 어떤 순서대로 실행 중인가를 나타내는 실행 단위입니다.
스레드 개념을 도입하여 CPU의 동작과정을 다시 보면 다음과 같습니다.
- 스레드의 PC 확인
- PC가 가리키는 명령어를 가져와 실행
- PC를 변경
- 다음 명령어 실행
여기서 실행할 스레드를 결정하는 것은 운영체제(OS, Operation System)이며, OS가 다음 실행할 스레드를 결정하고 CPU 레지스터에 로드하면 CPU는 이를 실행하는 형태로 진행합니다.
컨텍스트 스위칭(Context Switching)
OS는 상황에 따라, 현재 CPU가 실행 중인 스레드를 다른 스레드로 전환할 수 있습니다.
이때 기존 스레드의 실행 상태(PC, 레지스터, 스택 포인터 등)를 저장하고 새로 실행할 스레드의 실행 상태를 CPU 레지스터에 복원하는 과정이 발생하며, 이 과정을 컨텍스트 스위칭(Context Switching)이라고 합니다.
컨텍스트 스위칭을 통해 하나의 CPU 코어에서도 여러 스레드가 동시에 실행되는 것처럼 보이게 만들 수 있지만,
이 과정에서 상태 저장/복원에 따른 비용이 발생합니다.
프론트엔드 환경에서의 컨텍스트 스위칭(Context Switching)
조금 더 이해를 쉽게 해보기 위해 프론트엔드 환경에 적용해보면, 브라우저의 메인 스레드가 다음과 같은 작업을 수행합니다.
- JavaScript 실행
- DOM 조작
- 스타일 계산
- 레이아웃 및 렌더링
- 페인팅 트리거
- 이벤트 콜백 처리
- 등등..
만약 브라우저의 메인 스레드에서 수행할 연산이 무거워지면, 해당 연산이 끝날 때까지 UI 렌더링 작업이 실행되지 못합니다.
이때, 사용자는 UI 렌더링이 이루어지지 않아 화면이 멈춘 것 처럼 보이거나, 클릭, 스크롤 등 사용자 입력이 느리게 적용되는 문제가 발생합니다.
이러한 문제를 해결하기 위해 웹 브라우저는 작업을 메인 스레드와 분리하여 처리할 수 있는 구조를 제공합니다.
대표적으로 Web Worker를 통해 무거운 연산을 별도의 스레드에서 수행하고, 메인 스레드에서는 UI 렌더링과 사용자 입력 처리에서 집중할 수 있도록 합니다.
따라서, 현재 사용 중인 브라우저는 화면 멈춤이 없이 부드러운 사용자 경험을 제공할 수 있습니다.
하지만 여기서 이런 궁금증이 생겼습니다.
브라우저의 메인 스레드가 있다면,
브라우저에서 CPU와 운영체제(OS) 역할은 누가하지?
이는 브라우저가 컴퓨터 위에서 동작하는 방식을 이해할 필요가 있습니다.
브라우저는 운영체제(OS) 위에서 하나의 프로그램으로 CPU 사용을 요청하여 동작합니다.
또한, 브라우저는 탭(Tab)이라고 하는 하나의 프로세스(Process)를 기반으로, 프로세스 내의 스레드들(메인 스레드, Web Worker 등)이 동작하는 형태입니다.
따라서, 브라우저에서 CPU와 운영체제(OS) 역할은 기존 컴퓨터에 설치된 CPU와 운영체제(OS)가 수행합니다.
브라우저는 프로세스의 집합으로, 운영체제(OS)에 CPU 사용을 요청하여 스레드 동작을 수행합니다.
*프로세스(Process)
- 프로세스(Process)는 실행 중인 프로그램을 의미하며, 보조 기억장치의 프로그램이 메모리에 올라와 CPU를 할당받아 실행되는 능동적인 작업 단위입니다.
- 프로세스는 하나 이상의 스레드로 구성되며, 다른 프로세스와 자원을 공유하지 않는 독립적인 메모리를 가지고 있습니다.
- 스레드는 프로세스 내의 자원을 활용합니다.
그럼 브라우저는 하나의 CPU(코어)를 활용하여 프로세스를 수행할까요?
멀티코어 CPU
CPU 코어 1개는 1개의 스레드를 동작시킬 수 있습니다.
다시 말해, "CPU 코어 === 작업을 수행할 손" 과 같은 개념입니다.
따라서, CPU 코어가 여러 개라면, 여러 개의 스레드를 동시에 작업할 수 있습니다. 브라우저는 메인 스레드와 더불어 Web Worker 스레드를 사용하여 무거운 연산이나 데이터를 처리합니다.
따라서 두 개의 스레드를 사용하는데 있어 두 개의 코어가 있으면 이를 병렬적으로 실행하여 성능을 향상시킬 수 있습니다.
이는 사용자 입장에서 부드러운 UI를 제공할 수 있습니다.
브라우저는 메인 스레드와 Web Worker 스레드 말고도 렌더링, 네트워크, 타이머, GPU 등 다양한 스레드가 존재하며, 앞서 소개한 내용과 같이 멀티코어 CPU를 활용하여 보다 부드러운 UI를 제공할 수 있습니다.
이제 이 내용을 기반으로 10, 11, 12, 13번 질문들을 해결할 수 있습니다.
10번 문제에 대한 답변
멀티코어 CPU가 프론트엔드에 주는 이점은?
멀티코어 CPU는 여러 작업을 병렬로 처리할 수 있어, 프론트엔드에서 UI 렌더링과 연산 작업을 분리해 실행할 수 있는 기반을 제공합니다.
이를 통해 메인 스레드가 차단되는 상황을 줄이고, 사용자 인터랙션과 화면 반응성을 개선할 수 있습니다.
11번 문제에 대한 답변
컨텍스트 스위칭이란?
컨텍스트 스위칭은 CPU가 현재 실행 중인 스레드의 실행 상태를 저장하고, 다른 스레드의 실행 상태를 복원하여 실행 대상을 전환하는 과정입니다. 이때, PC(프로그램 카운터), 레지스터 상태, 스택 포인터 등이 저장/복원 되며, 이 과정에는 일정한 비용이 발생합니다.
컨텍스트 스위칭을 이용하여 하나의 CPU 코어에서도 여러 스레드가 실행되는 것처럼 실행할 수 있지만, 전환 비용이 있기에 잦은 스위칭은 성능 저하로 이어질 수 있습니다.
12번 문제에 대한 답변
무거운 연산이 UI를 멈추게하는 이유
브라우저의 메인 스레드는 JavaScript 실행과 UI 렌더링을 함께 담당하며, CPU는 한 순간 하나의 스레드만 실행할 수 있습니다.
따라서 메인 스레드에서 무거운 연산이 수행되면, 메인 스레드가 연산을 점유하여 UI 작업이 대기 상태가 되어 연산이 끝날 때까지 렌더링과 이벤트 처리가 지연되어 화면이 멈춘 것처럼 보입니다.
13번 문제에 대한 답변
Web Worker는 왜 필요한가요?
Web Worker는 무거운 연산을 메인 스레드와 분리된 별도의 스레드에서 실행하여, UI 렌더링과 사용자 입력 처리가 차단되지 않도록 하기위해 필요합니다.
이를 통해 멀티코어 CPU 환경에서 연산과 UI를 병렬로 처리할 수 있고, 화면 반응성을 유지할 수 있습니다.
다만, Web Worker는 메인 스레드와 메모리를 직접 공유하지 않고, 메세지 기반으로 통신하기 때문에 사용 목적이 명확한 경우에 적합합니다.
질문-답변 요약
| 질문 | 답변 |
| CPU와 메모리는 각각 어떤 역할을 하나? | CPU는 메모리에 저장된 명령어를 가져와 해석하고 실행하는 장치이며, 실행 과정에서 필요한 데이터는 메모리에서 가져와 CPU 내부의 레지스터에 저장한 뒤 연산을 수행합니다. 또한, 메모리는 CPU가 실행할 명령어와 데이터를 저장해두는 실행 공간입니다. CPU가 유일하게 접근할 수 있는 저장공간으로 CPU 명령어 수행을 지원하기 위한 목적의 저장공간으로 전원이 꺼지면 사라지는 휘발성의 특징을 가지고 있습니다. |
| 프로그램은 어떻게 실행되는가? | 프로그램은 보조 기억 장치에 저장되어 있으며, 명령어의 집합입니다. 프로그램을 실행하면 보조 기억 장치에서 메모리로 프로그램을 로드한 후, CPU가 메모리에 접근하여 명령어를 가져와 해석하고 실행하며, 분기 명령에 따라 실행 흐름을 제어합니다. |
| 메모리 계층 구조를 간단히 설명해보세요. | CPU와 메모리 사이에는 큰 속도 차이가 있기 때문에, 모든 데이터를 빠른 메모리로 구성하는 것은 비용과 물리적 한계가 있습니다. 그래서 자주 사용하는 데이터는 레지스터나 캐시에, 그렇지 않은 데이터는 메인 메모리나 저장장치에 두는 계층 구조를 통해 평균 접근 시간을 줄이도록 설계되었습니다. |
| 캐시 메모리는 왜 필요한가? | 캐시 메모리는 CPU와 메인 메모리(RAM) 사이의 속도 차이를 줄이기 위해, 자주 사용될 가능성이 높은 데이터를 저장해 두는 고속 메모리입니다. 이를 통해 CPU가 메모리 접근으로 인해 대기하지 않도록 하여 전체 실행 성능을 향상시킵니다. (+ 추가 가능) 캐시 히트는 CPU가 필요한 데이터를 캐시에서 바로 찾은 경우이고, 캐시 미스는 캐시에 데이터가 없어 메인 메모리까지 접근해야 하는 경우입니다. 캐시 히트는 빠른 실행을, 캐시 미스는 CPU 대기와 성능 저하를 유발합니다. |
| 지역성(Locality)이란? | 지역성(Locality)이란 프로그램이 메모리를 접근할 때 일정한 패턴을 보이는 성질로, 최근에 사용한 데이터나 인접한 데이터가 다시 사용될 가능성이 높다는 특성을 의미합니다. 캐시 메모리는 이러한 지역성을 기반으로 동작하여 평균 메모리 접근 시간을 줄입니다. |
| 스택(Stack)과 힙(Heap)의 차이는? | 스택은 함수 호출과 실행 흐름을 관리하는 메모리 영역이고, 힙은 실행 중 동적으로 생성되는 데이터를 저장하는 메모리 영역입니다. 스택에는 지역 변수와 복귀 주소가 저장되어 함수 실행이 끝나면 자동으로 해제되고, 힙에는 객체나 배열과 같이 크기와 수명이 정해지지 않은 데이터가 저장되어 참조가 남아 있는 동안 유지됩니다. |
| 콜 스택(Call Stack)이란? | 콜 스택은 현재 실행 중인 함수들의 실행 컨텍스트를 스택 구조로 관리하는 영역입니다. 함수가 호출되면 실행 컨텍스트가 콜 스택에 쌓이고, 함수가 종료되면 제거되면서 실행 흐름이 이전 함수로 돌아갑니다. 이를 통해 프로그램의 실행 순서가 관리됩니다. |
| 스택 오버플로우가 발생하는 시점과 이유 | 스택 오버플로우는 함수 호출이 계속 쌓여 스택의 최대 크기를 초과할 때 발생합니다. 주로 종료 조건이 없는 재귀 호출이나 과도하게 깊은 함수 호출이 원인이며, 스택은 빠른 실행 흐름 관리를 위해 크기가 제한된 구조이기 때문에 한계를 넘으면 오류가 발생하게 됩니다. (+추가 내용) 스택은 실행 흐름을 빠르고 단순하게 관리하기 위해 크기가 제한되며, 힙은 실행 중 동적으로 생성되는 데이터를 관리하기 위해 유연하게 설계되었습니다. 이로 인해 스택 오버플로우는 즉각적으로 발생하는 반면, 힙 문제는 점진적인 성능 저하로 나타나는 경우가 많습니다. |
| 메모리 누수는 왜 성능 문제로 이어지나요? | 메모리 누수는 사용되지 않는 객체가 힙 메모리에 계속 남아 메모리 사용량과 GC(가비지 컬렉션) 비용을 증가시키고, 결국 가비지 컬렉션 비용이 커져 메인 스레드가 자주 중단됩니다. 이로 인해 렌더링과 이벤트 처리가 지연되어 페이지 성능 저하와 UI 지연으로 이어질 수 있습니다. |
| 멀티코어 CPU가 프론트엔드에 주는 이점은? | 멀티코어 CPU는 여러 작업을 병렬로 처리할 수 있어, 프론트엔드에서 UI 렌더링과 연산 작업을 분리해 실행할 수 있는 기반을 제공합니다. 이를 통해 메인 스레드가 차단되는 상황을 줄이고, 사용자 인터랙션과 화면 반응성을 개선할 수 있습니다. |
| 컨텍스트 스위칭이란? | 컨텍스트 스위칭은 CPU가 현재 실행 중인 스레드의 실행 상태를 저장하고, 다른 스레드의 실행 상태를 복원하여 실행 대상을 전환하는 과정입니다. 이때, PC(프로그램 카운터), 레지스터 상태, 스택 포인터 등이 저장/복원 되며, 이 과정에는 일정한 비용이 발생합니다. 컨텍스트 스위칭을 이용하여 하나의 CPU 코어에서도 여러 스레드가 실행되는 것처럼 실행할 수 있지만, 전환 비용이 있기에 잦은 스위칭은 성능 저하로 이어질 수 있습니다. |
| 무거운 연산이 UI를 멈추게하는 이유 | 브라우저의 메인 스레드는 JavaScript 실행과 UI 렌더링을 함께 담당하며, CPU는 한 순간 하나의 스레드만 실행할 수 있습니다. 따라서 메인 스레드에서 무거운 연산이 수행되면, 메인 스레드가 연산을 점유하여 UI 작업이 대기 상태가 되어 연산이 끝날 때까지 렌더링과 이벤트 처리가 지연되어 화면이 멈춘 것처럼 보입니다. |
| Web Worker는 왜 필요한가요? | Web Worker는 무거운 연산을 메인 스레드와 분리된 별도의 스레드에서 실행하여, UI 렌더링과 사용자 입력 처리가 차단되지 않도록 하기위해 필요합니다. 이를 통해 멀티코어 CPU 환경에서 연산과 UI를 병렬로 처리할 수 있고, 화면 반응성을 유지할 수 있습니다. 다만, Web Worker는 메인 스레드와 메모리를 직접 공유하지 않고, 메세지 기반으로 통신하기 때문에 사용 목적이 명확한 경우에 적합합니다. |
느낀점
프론트엔드는 브라우저, 혹은 애플리케이션에서 사용자에게 직접적으로 닿아 있는 역할이지만, 그만큼 컴퓨터의 내부적인 내용과는 거리가 있다고 은연중에 생각하고 있어 컴퓨터 구조라는 내용이 프론트엔드라는 직무와는 조금 멀게 느껴진 것도 사실입니다.
하지만 이번 기회를 통해, 현재 공부하고 있는 브라우저라고 하는 내용 또한 컴퓨터라고하는 하나의 구조에서 돌아가는 프로그램이므로 컴퓨터 구조의 중요성을 깨닳을 수 있었습니다.
또한, 자주 듣던 "JavaScript는 싱글 스레드이다.", "브라우저에서 멀티 스레드 구조를 제공해서 비동기를 처리할 수 있다."와 같은 개념들을 직접적으로 공부할 수 있어 좋은 경험이 되었습니다.
'CS' 카테고리의 다른 글
| [개념] HTTP 메소드 정리 (0) | 2024.06.22 |
|---|
