암달의 법칙

낚시하는 곰·2025년 3월 22일

krafton jungle

목록 보기
20/52

암달의 법칙이란?

전체 프로그램 중에서 병렬화할 수 없는 부분이 전체 성능 향상의 한계를 결정한다는 법칙이야.

즉, 프로그램의 일부만 병렬로 빠르게 처리해도, 나머지 병렬화 불가능한 구간이 발목을 잡는다는 의미지.

기본 수식

Speedup(N) = 1 / [ (1 - P) + (P / N) ]
기호의미
N사용한 프로세서(코어) 수
P전체 프로그램 중 병렬화 가능한 비율 (0 ≤ P ≤ 1)
1 - P병렬화 불가능한 부분
Speedup(N)병렬화 후의 성능 향상 배수
  • 병렬화 가능한 비율 P가 아무리 높더라도, 병렬화 불가능한 부분이 존재하면 무한히 많은 코어를 써도 속도 향상에 한계가 있어.

  • 여기서 (1 - P)는 병렬화가 안 되는 부분 → 항상 한 코어에서만 실행됨.
    즉, 여기는 병목 구간이야.

  • 코어 수 N이 무한대로 갈 경우:

lim N→∞ Speedup(N) = 1 / (1 - P)

→ 병렬화가 95%만 가능해도, 최대 성능 향상은 1 / (1 - 0.95) = 20배가 한계

예: 전체 코드 중 80% 병렬화 가능(P = 0.8)

코어 수 (N)Speedup
11.00
21.66
42.50
83.33
5.00

→ 병렬화 비율이 높지 않으면, 코어를 늘려도 성능이 생각보다 많이 안 오름

병렬화되지 않는 구간은 아무리 코어를 많이 넣어도 절대 빨라지지 않는다

P = 0.5 → 병렬화 비율 50%

코어 수 N = 1,000

Speedup = 1 / [0.5 + (0.5 / 1000)] ≈ 1.998

→ 1000개 코어를 써도 고작 2배 빨라짐이게 바로 병렬화가 코어 수보다 중요한 이유야.

반대로 병렬화 비율(P)이 높으면, 코어가 적어도 빠르다

P = 0.99 (99% 병렬화)

N = 8

Speedup ≈ 1 / (0.01 + 0.99/8) ≈ 7.48배

→ 단 8코어로도 거의 이상적인 8배 가까운 성능 향상즉, 병렬화가 잘 돼 있으면, 코어 수는 적어도 큰 효과를 낼 수 있음

암달의 법칙이 왜 필요할까?

웹 서버 및 멀티코어 애플리케이션 스케일링

🔍 어디서?

  • 웹 서버 (NGINX, Apache)

  • Node.js, Java Spring 기반 애플리케이션

  • 대규모 멀티스레딩 구조

📌 어떻게 활용?

  • 서버 요청 처리 로직에서 병렬화 가능한 영역(예: 요청 파싱, DB 접근 등)과 불가능한 영역(로깅, 공용 리소스 접근 등)을 분석

  • 병렬 처리 설계 전/후의 성능 추정과 실제 속도 향상의 차이 분석에 활용

🎯 실제 영향

  • 병렬화된 워커 수 늘리기 전에, 스레드 안전성과 공유 리소스 병목 제거를 먼저 시도

  • “코어 수 늘리기 전에 구조 먼저 바꿔야 한다”는 결론을 도출

궁금증 해결하고 가기

기본 개념 비교

항목직렬 처리 (Sequential)병렬 처리 (Parallel)
실행 방식작업을 순서대로 하나씩 처리작업을 여러 개 동시에 처리
실행 흐름하나의 흐름 (Single thread/process)다중 흐름 (Multi-threaded or multi-core)
하드웨어 활용보통 1개 코어 사용여러 코어/CPU/GPU 사용
성능느리지만 단순하고 예측 가능빠르지만 동기화/경쟁 상태 관리 필요
예시파일 읽기 → 처리 → 저장이미지 여러 장 필터링, 동영상 렌더링 등

주요 차이점 요약

측면설명
속도병렬 처리 → 시간 단축 효과 큼 (특히 대용량 작업에 유리)
복잡성병렬 처리에는 스레드 관리, 동기화, 공유 리소스 관리 필요
정확성직렬 처리는 순차적이므로 동기화 문제 없음
확장성병렬 처리는 멀티코어, 클러스터, GPU 등으로 확장 가능

예시

빨래 돌리기

● 직렬 처리

1. 세탁기 돌림 → 끝날 때까지 기다림
2. 빨래 널기
3. 다림질

→ 한 작업이 끝나야 다음으로 넘어가니까 전체 시간 소요가 큼

● 병렬 처리

1. 세탁기 돌리는 동안 → 다른 세탁기에서 두 번째 빨래
2. 첫 세탁 끝나면 널기 + 두 번째 세탁 기다리는 동안 다림질 시작

→ 여러 작업을 동시에 수행 → 전체 시간 대폭 단축

이미지 파일 100개 리사이징

● 직렬 처리

for image in image_list:
    resize(image)  # 한 번에 하나씩 처리

→ 총 100개의 작업을 하나하나 순서대로 처리→ 총 걸린 시간 = 이미지 1개 처리 시간 × 100

● 병렬 처리

with ThreadPoolExecutor(max_workers=8) as executor:
    executor.map(resize, image_list)  # 동시에 8개씩 처리

→ 최대 8개씩 동시에 리사이징→ 전체 시간은 거의 1/8 수준으로 단축 가능

음식 요리하기

● 직렬 처리

1. 밥 짓기 (30분)
2. 국 끓이기 (20분)
3. 반찬 만들기 (15분)

→ 총 65분 소요

● 병렬 처리 (요리사 3명)

- 요리사 1: 밥
- 요리사 2: 국
- 요리사 3: 반찬

→ 총 30분이면 전부 완료

→ 각각의 작업이 서로 독립적이고, 동시에 수행 가능 → 병렬 처리

그렇다면 동기 vs 비동기 vs 직렬성 vs 병렬성!!

4가지 개념 정리

구분의미
동기(Synchronous)결과가 나올 때까지 기다림, 다음 작업 못 함
비동기(Asynchronous)기다리지 않고 바로 다음 작업 수행, 결과는 나중에 처리
직렬(Sequential)작업을 하나씩 순서대로 처리
병렬(Parallel)여러 작업을 동시에 처리 (멀티코어, 멀티스레드 등)

개념 간 조합

처리 방식동기비동기
직렬순차적으로 기다림순차적으로 호출하되 기다리진 않음
병렬동시에 실행되지만 결과 기다림동시에 실행 + 결과 기다리지 않음

→ 헷갈리지 않게 하기 위해, 같은 예시를 4가지 방식으로 비교해볼게.


✅ 1. 동기 + 직렬 처리

물 끓이기 → 면 넣기 → 다 익을 때까지 기다리기 → 다음 그릇 시작

for i in range(3):
    boil_water()
    cook_noodles()
    serve()
  • 라면 하나 끓일 때마다 끝까지 기다린 뒤 다음 라면 시작
  • 총 3배의 시간 소요됨
  • 느리지만 순서 확실, 단순함

✅ 2. 비동기 + 직렬 처리

세 개의 라면 끓이기 시작만 하고, 끝날 때까지 기다리진 않음, 대신 타이머 등록해두고 하나씩 처리

for i in range(3):
    async_start_boiling(i)   # 바로 다음 라면 시도
  • 동시에 실행되진 않지만, 각 작업에서 기다리지 않음
  • 비동기 호출 + 순차적 트리거 구조
  • 보통 **이벤트 기반 언어 (JS 등)**에서 자주 쓰임

✅ 3. 동기 + 병렬 처리

3개의 냄비로 라면을 동시에 끓이되, 각 냄비가 다 익을 때까지 기다림

threads = []
for i in range(3):
    t = Thread(target=sync_cook)
    t.start()
    threads.append(t)

for t in threads:
    t.join()  # 결과 기다림
  • 여러 코어 or 스레드를 이용한 병렬 처리
  • 하지만 각 작업이 완료될 때까지 기다림
  • CPU 작업 병렬 처리에 많이 쓰임 (멀티코어)

✅ 4. 비동기 + 병렬 처리

세 냄비에 동시에 끓이고, 다 익었는지 확인은 타이머/콜백으로 나중에 처리

async def main():
    tasks = [async_cook(i) for i in range(3)]
    await asyncio.gather(*tasks)
  • 비동기로 동시에 여러 작업 시작 → 각 작업 완료될 때 콜백/await
  • 효율성 최고, 대기 IO 많을 때 유리 (네트워크, DB 등)
  • Python asyncio, JavaScript의 Promise 구조가 대표적
profile
취업 준비생 낚곰입니다!! 반갑습니다!!

0개의 댓글