
싱글코어 vs 멀티코어: 코어가 많으면 무조건 빠를까? (완전정복)
코어가 8개면 컴퓨터가 8배 빨라질까요? 암달의 법칙부터 동시성(Concurrency)과 병렬성(Parallelism)의 차이, 하이퍼스레딩의 비밀, 그리고 크롬이 RAM을 많이 먹는 이유까지. 4부작 심층 가이드.

코어가 8개면 컴퓨터가 8배 빨라질까요? 암달의 법칙부터 동시성(Concurrency)과 병렬성(Parallelism)의 차이, 하이퍼스레딩의 비밀, 그리고 크롬이 RAM을 많이 먹는 이유까지. 4부작 심층 가이드.
맥북 배터리는 왜 오래 갈까? 서버 비용을 줄이려면 AWS Graviton을 써야 할까? 복잡함(CISC)과 단순함(RISC)의 철학적 차이를 정리해봤습니다.

AI 시대의 금광, 엔비디아 GPU. 도대체 게임용 그래픽카드로 왜 AI를 돌리는 걸까? 단순 노동자(CUDA)와 행렬 계산 천재(Tensor)의 차이로 파헤쳐봤습니다.

빠른 SSD를 샀는데 왜 느릴까요? 1차선 시골길(SATA)과 16차선 고속도로(NVMe). 인터페이스가 성능의 병목이 되는 이유.

LP판과 USB. 물리적으로 회전하는 판(Disc)이 왜 느릴 수밖에 없는지, 그리고 SSD가 어떻게 서버의 처리량을 100배로 만들었는지 파헤쳐봤습니다.

새벽 배포 작업을 하다가 문득 궁금해졌습니다.
"내 맥북은 8코어니까, 예전 싱글코어 컴퓨터보다 8배 빠른 건가?"
만약 그렇다면 왜 부팅 속도는 8배가 아니고, npm install은 여전히 기다려야 할까요?
단순히 "코어가 많으면 좋다"고 알고 있지만, 정작 "언제, 어떻게, 왜" 좋은지는 정확히 모르는 경우가 많습니다. 심지어 현업 개발자들조차 동시성(Concurrency)과 병렬성(Parallelism)을 혼동하여 코드 리뷰에서 곤란을 겪곤 합니다.
저도 처음엔 이런 질문을 던졌습니다. "MacBook Pro M1 Max 10코어를 샀는데, 왜 내 Node.js 서버는 코어 1개만 쓸까?" Activity Monitor를 열어보니 10개 코어 중 하나만 빨간색으로 타오르고 나머지는 놀고 있더군요. 결국 이거였다고 받아들였습니다. 하드웨어를 샀다고 소프트웨어가 자동으로 빨라지는 게 아니구나.
오늘, 우리는 이 거대한 주제를 4개의 파트로 나누어 완전히 파고들어봤다.
이 차이를 가장 잘 설명하는 모델은 "주방"입니다.
이 비유를 처음 받아들였을 때 머릿속에 확 들어왔습니다. 그전까지는 "코어 = 성능"이라고 단순하게 생각했거든요. 하지만 주방에서 셰프를 100명 채용한다고 해서 1인분 요리 속도가 100배 빨라지지 않는 것처럼, 코어를 늘린다고 모든 작업이 선형으로 빨라지진 않습니다.
실제로 제가 운영하던 API 서버가 있었습니다. AWS EC2 t2.micro(1코어)에서 c5.2xlarge(8코어)로 업그레이드했는데, 처리량(Throughput)이 고작 2배밖에 안 늘었습니다. 왜? Node.js는 기본적으로 싱글 스레드니까요. 코어 8개를 사 놓고 1개만 쓴 셈입니다.
CPU 내부로 들어가 봅시다.
CPU 사양표를 보면 "4코어 8스레드"라고 쓰여 있습니다. 아니, 입(코어)은 4개인데 손(스레드)이 8개라고요? 이게 가능한가요? 이것이 인텔의 SMT(Simultaneous Multithreading) 기술입니다.
처음에 이 개념을 이해했을 때 "아, 그래서 내 인텔 노트북이 듀얼코어인데 작업관리자에선 CPU 4개가 보였구나"라고 무릎을 쳤습니다. 하이퍼스레딩은 마치 한 명이 두 접시를 동시에 들고 오는 웨이터 같습니다. 물리적으로 손이 4개인 건 아니지만, 동선을 최적화해서 왕복 횟수를 줄이는 거죠.
그런데 이게 항상 좋은 건 아닙니다. Linux 서버 관리자들이 보안상 하이퍼스레딩을 꺼버리는 경우가 많습니다. 왜냐하면 2018년 Spectre/Meltdown 취약점이 하이퍼스레딩을 악용했기 때문이죠. "스레드 A가 처리하던 암호키를 스레드 B가 옆에서 훔쳐볼 수 있다"는 게 보안팀의 판단이었습니다.
최신 스마트폰(ARM)과 인텔 12세대 이후 CPU는 코어 크기가 다릅니다.
Apple M1 칩을 보면 이 전략이 극대화되어 있습니다. 4개의 고성능 코어(Firestorm)와 4개의 효율 코어(Icestorm)로 구성되어 있죠. 제 MacBook Air M1으로 Final Cut Pro로 4K 영상 인코딩을 돌릴 때는 P-Core 4개가 풀가동됩니다. 하지만 그냥 VSCode로 코딩만 할 때는 E-Core 1개만 깨어나고 나머지는 잠듭니다. 그래서 배터리가 15시간씩 가는 거였습니다.
정리해본다면, 이 아키텍처는 "자동차의 하이브리드 엔진"과 비슷합니다. 고속도로에선 V6 엔진(P-Core), 시내에선 전기모터(E-Core). Intel 12세대부터 이 설계를 채택했는데, 사실 ARM이 10년 전에 이미 발명한 기술입니다.
멀티코어의 환상을 깨부수는 잔혹한 법칙이 있습니다. Gene Amdahl이 1967년에 만들었습니다.
"임산부를 9명 모아놓는다고 해서, 1달 만에 아기가 나오지는 않는다."
프로그램의 어떤 부분은 절대로 쪼갤 수 없습니다(Sequential Part).
아무리 코어가 무한대로 많아도, 이 직렬 구간 때문에 전체 성능 향상에는 한계가 있습니다.
공식: Speedup = 1 / ((1 - P) + (P / N))
(P: 병렬화 가능한 비율, N: 코어 수)
만약 내 프로그램의 50%가 직렬 구간(P=0.5)이라면, 코어를 1억 개(N=∞)를 갖다 박아도 속도는 최대 2배밖에 안 빨라집니다. 이것이 우리가 코어 수에 집착하기보다 "어떻게 병렬화 가능한 부분을 늘릴까?"를 고민해야 하는 이유입니다.
저는 이 법칙을 배우고 나서야 제 서버 성능이 왜 선형으로 늘지 않았는지 이해했습니다. 제 Express.js API 서버를 분석해보니:
결국 병렬화 가능한 비율이 80%였고, 암달의 법칙에 따르면 무한대 코어를 써도 최대 5배까지만 빨라진다는 계산이 나왔습니다. 실제로 8코어에서 측정하니 3.2배 속도 향상. 이론과 거의 일치하더군요.
이 법칙이 와닿았던 이유는, 하드웨어를 사는 게 아니라 소프트웨어를 리팩토링해야 한다는 걸 깨달았기 때문입니다.
하지만 1988년에 John Gustafson이 암달의 법칙에 반론을 제기했습니다. "문제 크기(Problem Size)를 키우면 어떨까?"
암달의 법칙은 "같은 작업을 더 빠르게"에 초점을 맞췄지만, 구스타프손은 "주어진 시간에 더 큰 작업을"이라는 관점을 제시했습니다.
예를 들어:
과학 시뮬레이션(기상 예측, 분자 동역학)이나 AI 훈련 같은 분야에서는 구스타프손의 법칙이 더 현실적입니다. "어차피 밤새 돌릴 거면, 코어를 늘려서 더 정밀한 모델을 돌리자"는 발상이죠.
제가 실제로 이걸 경험한 적이 있습니다. 머신러닝 모델 훈련할 때 AWS p3.8xlarge(32 vCPU, Tesla V100 4개)를 쓴 적이 있는데, "훈련 시간 단축"보다는 "배치 크기(Batch Size)를 키워서 더 좋은 모델 만들기"에 집중했습니다. 8시간은 어차피 걸리는데, 그 시간에 더 큰 데이터셋을 먹이는 거죠.
개발자라면 이 코드를 이해해야 합니다.
import threading
import multiprocessing
import time
def work():
# 단순 반복 작업 (CPU Bound)
s = 0
for i in range(100000000):
s += i
# 1. Threading (동시성) - GIL 때문에 사실상 싱글코어
t1 = threading.Thread(target=work)
t2 = threading.Thread(target=work)
t1.start(); t2.start()
# 결과 - 2명이서 하는데 오히려 느려질 수도 있음 (Context Switching 비용)
# 2. Multiprocessing (병렬성) - 진짜 멀티코어
p1 = multiprocessing.Process(target=work)
p2 = multiprocessing.Process(target=work)
p1.start(); p2.start()
# 결과 - 진짜로 2배 빨라짐 (코어 2개 사용)
동시성(Concurrency)은 "논리적"으로 동시에 처리되는 것처럼 보이는 것이고, 병렬성(Parallelism)은 "물리적"으로 동시에 처리되는 것입니다.
Python의 GIL(Global Interpreter Lock) 때문에 threading은 CPU 작업에선 무용지물입니다. 하지만 I/O 작업(파일 읽기, 네트워크 요청)에선 threading이 여전히 유용합니다.
저는 Node.js로 백엔드를 주로 개발하는데, Node.js는 싱글 스레드 기반입니다. V8 엔진이 메인 스레드 1개로 모든 JavaScript 코드를 실행하죠. 8코어 서버를 사 놓고 1코어만 쓰는 상황이 발생합니다.
이걸 해결하는 게 Cluster 모듈입니다.
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`마스터 프로세스 ${process.pid} 실행 중`);
// CPU 코어 수만큼 워커 생성
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`워커 ${worker.process.pid} 종료됨`);
cluster.fork(); // 죽으면 자동으로 재생성
});
} else {
// 워커 프로세스들은 TCP 연결 공유
http.createServer((req, res) => {
res.writeHead(200);
res.end('안녕하세요\n');
}).listen(8000);
console.log(`워커 ${process.pid} 시작됨`);
}
이 코드를 8코어 서버에서 돌리면:
실제로 제 프로덕션 서버에 이걸 적용했을 때 QPM(Queries Per Minute)이 1,200에서 7,500으로 뛰었습니다. 하드웨어 교체 없이 코드 20줄 추가만으로요.
코어 개수 얘기가 나왔으니, GPU 얘기를 안 할 수가 없습니다. CPU가 8코어라면, GPU는 몇 코어일까요?
NVIDIA RTX 4090의 경우 16,384개의 CUDA 코어를 가지고 있습니다. CPU 입장에서 보면 "어떻게 코어가 만 개가 넘어?"라고 생각할 수 있는데, 이 코어들은 CPU 코어와 본질이 다릅니다.
CPU 코어는 미슐랭 셰프 (복잡한 요리 가능) GPU 코어는 감자 깎는 아르바이트생 (단순 반복 작업만 가능)
이 비유가 제일 와닿았습니다. GPU 코어 1개는 CPU 코어처럼 복잡한 논리 분기(if문 백만 개)나 재귀(Recursion)를 빠르게 처리할 수 없습니다. 하지만 "1000x1000 행렬의 각 원소에 3.14를 곱하기" 같은 단순한 작업을 만 개를 동시에 처리하는 건 CPU보다 수백 배 빠릅니다.
그래서:
제가 YOLOv5 객체 인식 모델을 돌려봤을 때:
62배 차이. 코어 개수 차이(3584/16=224)보다는 적지만, 여전히 압도적입니다.
코어가 많아지면 행복할 줄 알았는데, 프로그래머에겐 지옥이 열렸습니다.
제가 실제로 겪은 데드락 사례를 공유합니다.
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def task1():
with lock_a:
print("Task1이 Lock A 획득")
time.sleep(0.1) # 타이밍 문제 시뮬레이션
with lock_b:
print("Task1이 Lock B 획득")
def task2():
with lock_b:
print("Task2가 Lock B 획득")
time.sleep(0.1)
with lock_a:
print("Task2가 Lock A 획득")
t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)
t1.start()
t2.start()
# 결과 - 프로그램이 영원히 멈춤
이 코드를 처음 실행했을 때 "왜 안 끝나지?"라고 10분 동안 멍하니 기다렸습니다. CPU 사용률은 0%. 아무 에러도 안 남. 그냥 조용히 죽어있습니다.
데드락의 4가지 조건:
이 4개가 동시에 성립해야 데드락이 발생합니다. 하나라도 막으면 해결됩니다.
제 해결책:
def task1():
locks = sorted([lock_a, lock_b], key=id) # 항상 같은 순서로 획득
with locks[0]:
with locks[1]:
print("안전하게 실행")
"순환 대기"를 막았습니다. 이제 데드락이 안 생깁니다.
이론을 배우고 실제로 적용한 사례를 정리해본다면:
multiprocessing.Pool(8) 사용 → 7분 소요 (6.4배 향상)하드웨어는 이제 "공짜 성능 향상"을 멈췄습니다. (무어의 법칙의 종료와 발열의 벽). 이제 소프트웨어 개발자가 똑똑해져야 합니다.
코어는 많아졌습니다. 이제 그걸 100% 활용하는 건 여러분의 코드에 달렸습니다.
결국 제가 받아들인 진실은 이겁니다. "8코어 맥북을 샀다고 모든 게 8배 빨라지진 않는다. 하지만 코드를 제대로 짜면 7배는 빨라진다."
그리고 그 1배의 차이는 암달의 법칙과 동기화 오버헤드가 가져가는 세금입니다. 하드웨어 성능을 끝까지 쥐어짜는 건, 결국 소프트웨어 엔지니어의 몫이라는 걸 이해했습니다.
| 특성 | 싱글코어 (Single Core) | 멀티코어 (Multi Core) | 하이퍼스레딩 (Hyper-Threading) |
|---|---|---|---|
| 비유 | 천재 셈프 1명 | 일반 셰프 4명 | 손이 엄청 빠른 셰프 1명 (손 4개) |
| 강점 | 복잡한 순차 작업 (게임 로직) | 단순 반복 대량 작업 (렌더링, 서버) | 자원 효율성 극대화 (가성비) |
| 약점 | 한 번에 하나만 가능 (멀티태스킹 불가) | 소프트웨어 지원 없으면 무용지물 | 물리 코어 2개보다는 느림 |
| 위험 | 없음 | 데드락, 경쟁 상태 (Race Condition) | 캐시 오염 (Cache Thrashing) |
| OS 인식 | CPU 1개 | CPU N개 | CPU 2개 (논리적) |
| 핵심 법칙 | 무어의 법칙 (과거) | 암달의 법칙 (현재) | - |
It's a question every developer asks at least once.
"My new laptop has 8 cores. My old one had 1. Logic says it should be 8 times faster, right?"
But why isn't boot time 8x faster? Why does npm install still take forever?
We blindly believe "More Cores = Better," but we don't understand "When, How, and Why." Even seasoned developers often confuse Concurrency with Parallelism, leading to embarrassing moments in technical discussions.
I asked myself the same question when I bought my MacBook Pro M1 Max with 10 cores. "Why is my Node.js server only using one core?" I opened Activity Monitor and saw one core glowing red while the other nine were practically asleep. That's when it hit me: Buying hardware doesn't automatically make software faster.
Today, we will dissect this massive topic into 4 parts:
The best model to understand CPU cores is a Kitchen.
When I first understood this analogy, everything clicked. Before that, I naively thought "Cores = Performance" in a linear way. But just like hiring 100 chefs doesn't make a single dish 100x faster, adding cores doesn't linearly speed up all tasks.
I ran into this firsthand with an API server I was operating. I upgraded from AWS EC2 t2.micro (1 core) to c5.2xlarge (8 cores), expecting massive gains. Throughput only doubled. Why? Because Node.js is single-threaded by default. I paid for 8 cores but only used 1.
Let's dive into the CPU.
You often see specs like "4 Cores, 8 Threads." Wait, 4 Mouths but 8 Hands? How is that possible? This is SMT (Simultaneous Multithreading).
When I first grasped this concept, I finally understood why my dual-core Intel laptop showed 4 CPUs in Task Manager. Hyper-Threading is like a waiter carrying two plates at once. They don't physically have 4 hands, but optimizing their route reduces trips.
But it's not always beneficial. Many Linux server admins disable Hyper-Threading for security reasons. After the 2018 Spectre/Meltdown vulnerabilities, it became clear that Hyper-Threading could be exploited. "Thread A's encryption key could be snooped on by Thread B" was the security team's conclusion.
Modern CPUs (Apple M1, Intel 12th Gen) use heterogeneous cores.
The Apple M1 chip is the perfect example of this strategy. It has 4 high-performance cores (Firestorm) and 4 efficiency cores (Icestorm). When I encode 4K video in Final Cut Pro on my MacBook Air M1, all 4 P-Cores max out. But when I'm just coding in VSCode, only 1 E-Core wakes up, and the rest sleep. That's why the battery lasts 15 hours.
This architecture is like a hybrid car engine. V6 on the highway (P-Cores), electric motor in the city (E-Cores). Intel adopted this design from the 12th generation onwards, but ARM invented it 10 years earlier.
There is a brutal law that shatters the multi-core fantasy. Gene Amdahl formulated it in 1967.
"You cannot produce a baby in 1 month by getting 9 pregnant women together."
Some parts of a program are strictly sequential.
No matter how many cores you add, these Sequential Parts limit the maximum speedup.
Formula: Speedup = 1 / ((1 - P) + (P / N))
(P = Parallelizable portion, N = Number of cores)
If 50% of your program is serial (P=0.5), even with 1 billion cores (N=∞), the maximum speedup is only 2x. This is why we should focus on "Increasing the Parallelizable Fraction" instead of obsessing over core count.
I finally understood why my server performance didn't scale linearly after learning this law. I analyzed my Express.js API server:
So the parallelizable fraction was 80%, and according to Amdahl's Law, even with infinite cores, the max speedup would be 5x. In practice, on an 8-core server, I measured 3.2x speedup. Almost exactly what the theory predicted.
This law clicked for me because it made me realize: I shouldn't buy better hardware. I should refactor the software.
But in 1988, John Gustafson challenged Amdahl's Law. "What if we increase the problem size?"
Amdahl focused on "Doing the same work faster," Gustafson suggested "Doing more work in the same time."
For example:
In fields like scientific simulation (weather forecasting, molecular dynamics) or AI training, Gustafson's Law is more realistic. "If it's going to run overnight anyway, let's use more cores to train a more complex model."
I experienced this firsthand when training machine learning models. On AWS p3.8xlarge (32 vCPU, 4x Tesla V100 GPUs), I focused not on "reducing training time" but on "increasing batch size to build a better model." If it's going to take 8 hours anyway, feed it a larger dataset.
Developers need to understand this code.
import threading
import multiprocessing
import time
def work():
# CPU-bound task
s = 0
for i in range(100000000):
s += i
# 1. Threading (Concurrency) - Python GIL limits this to 1 core
t1 = threading.Thread(target=work)
t2 = threading.Thread(target=work)
t1.start(); t2.start()
# Result: Might be SLOWER than single thread due to context switching overhead.
# 2. Multiprocessing (Parallelism) - True Multi-core
p1 = multiprocessing.Process(target=work)
p2 = multiprocessing.Process(target=work)
p1.start(); p2.start()
# Result: Truly 2x faster (Uses 2 cores).
Concurrency is about "Dealing with lots of things at once" (Structure). Parallelism is about "Doing lots of things at once" (Execution).
Python's GIL (Global Interpreter Lock) renders threading useless for CPU-intensive tasks. But for I/O tasks (file reading, network requests), threading is still valuable.
I mostly develop backends in Node.js, which is single-threaded. The V8 engine runs all JavaScript code on one main thread. You can have an 8-core server but only use 1 core.
The solution is the Cluster module.
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master process ${process.pid} running`);
// Fork workers equal to CPU count
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // Auto-respawn
});
} else {
// Workers share TCP connection
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
Running this on an 8-core server:
When I applied this to my production server, QPM (Queries Per Minute) jumped from 1,200 to 7,500. No hardware upgrade. Just 20 lines of code.
Since we're talking about core count, we can't ignore GPUs. If a CPU has 8 cores, how many does a GPU have?
The NVIDIA RTX 4090 has 16,384 CUDA cores. From a CPU perspective, "How can something have over ten thousand cores?" But these cores are fundamentally different from CPU cores.
CPU cores are Michelin Chefs (can handle complex recipes). GPU cores are Potato Peelers (can only do simple repetitive tasks).
This analogy nailed it for me. A single GPU core can't handle complex logic branches (millions of if-statements) or recursion like a CPU core. But for tasks like "Multiply each element of a 1000x1000 matrix by 3.14," doing 10,000 simultaneously is hundreds of times faster than a CPU.
So:
When I ran the YOLOv5 object detection model:
62x difference. Less than the core count ratio (3584/16=224), but still overwhelming.
More cores bring more problems.
I'll share a real deadlock I encountered.
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def task1():
with lock_a:
print("Task1 acquired Lock A")
time.sleep(0.1) # Simulate timing issue
with lock_b:
print("Task1 acquired Lock B")
def task2():
with lock_b:
print("Task2 acquired Lock B")
time.sleep(0.1)
with lock_a:
print("Task2 acquired Lock A")
t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)
t1.start()
t2.start()
# Result: Program hangs forever
When I first ran this code, I stared at it for 10 minutes thinking "Why isn't it finishing?" CPU usage: 0%. No errors. Just silently dead.
The 4 conditions for deadlock:
All 4 must be true simultaneously for deadlock to occur. Break one, solve it.
My solution:
def task1():
locks = sorted([lock_a, lock_b], key=id) # Always acquire in same order
with locks[0]:
with locks[1]:
print("Safe execution")
I broke "Circular Wait." No more deadlock.
After learning the theory, here are actual implementations:
multiprocessing.Pool(8) → 7 minutes (6.4x improvement)Hardware has stopped giving us "Free Speedups" (Moore's Law is dead, and we've hit the thermal wall). Now Software Developers must be smart.
The cores are there. Using them is up to your code.
The truth I've accepted is this: "Buying an 8-core MacBook doesn't make everything 8x faster. But writing code properly can make it 7x faster."
And that missing 1x? That's the tax taken by Amdahl's Law and synchronization overhead. Squeezing every drop of hardware performance is ultimately the software engineer's responsibility.