C++ 컴파일 시간 최적화 | 모듈·PCH·ccache·분산 빌드 [#51-5]
이 글의 핵심
빌드 속도 향상: C++20 모듈, 미리 컴파일된 헤더, ccache, distcc, 병렬 빌드. 10만 줄 이상의 C++ 프로젝트에서 이런 경험을 해보셨나요? | 상황 | 증상 | 영향 | |------|------|------|.
들어가며: “빌드가 30분 걸려요. 매번 커밋할 때마다 기다리기 힘들어요”
문제 시나리오: 대규모 C++ 프로젝트의 빌드 지옥
10만 줄 이상의 C++ 프로젝트에서 이런 경험을 해보셨나요?
| 상황 | 증상 | 영향 |
|---|---|---|
| 헤더 하나 수정 | 전체 리빌드 20분+ | 작은 수정에도 긴 대기 |
| CI 파이프라인 | 빌드 타임아웃, 병목 | 배포 지연, 비용 증가 |
| 팀원 10명 동시 빌드 | 빌드 서버 과부하 | 개발 속도 저하 |
<iostream> 포함 | 거의 모든 TU가 재컴파일 | 불필요한 의존성 폭발 |
핵심 원인:
- 헤더 의존성 폭발:
#include가 전이적으로 퍼져 한 헤더 수정 시 수백 개 TU 재컴파일 - 템플릿 인스턴화: 매 TU마다 동일한 템플릿이 반복 인스턴스화
- 순차 빌드: 단일 코어만 활용
- 캐시 미사용: 동일 코드를 매번 처음부터 컴파일
이 글의 해결책:
- C++20 모듈: 헤더 대신 모듈로 의존성 격리
- PCH(미리 컴파일된 헤더): 공통 헤더를 한 번만 컴파일
- ccache: 컴파일 결과 캐시로 재빌드 가속
- distcc: 분산 컴파일로 여러 머신 활용
- 병렬 빌드:
-j로 멀티코어 활용
요구 환경: C++17 이상 (모듈은 C++20), CMake 3.16+, GCC 11+ 또는 Clang 14+, ccache, distcc(선택)
이 글을 읽으면:
- 빌드 시간 병목의 원인을 파악할 수 있습니다.
- 모듈·PCH·ccache·분산 빌드를 실전에서 적용할 수 있습니다.
- CI/CD 파이프라인을 최적화할 수 있습니다.
목차
- 빌드 시간 병목 분석
- C++20 모듈로 의존성 격리
- PCH(미리 컴파일된 헤더)
- ccache로 재빌드 가속
- 병렬 빌드와 CMake 설정
- distcc 분산 컴파일
- 자주 하는 실수와 해결법
- 성능 최적화 팁
- 프로덕션 패턴
- 실전 예제: 종합 적용
1. 빌드 시간 병목 분석
빌드 파이프라인 이해
flowchart LR
subgraph Source["소스 단계"]
A[.cpp 파일]
B[#include 처리]
end
subgraph Compile["컴파일 단계"]
C[전처리]
D[파싱]
E[최적화]
F[코드 생성]
end
subgraph Link["링크 단계"]
G[오브젝트 병합]
H[실행 파일]
end
A --> B --> C --> D --> E --> F --> G --> H
병목 지점:
- 전처리·파싱: 헤더가 많을수록 급격히 증가 (대부분의 시간)
- 최적화:
-O2/-O3사용 시 증가 - 링크: 대형 바이너리에서 증가
헤더 의존성 폭발 시각화
flowchart TB
subgraph Before["#include 체인 (main.cpp)"]
M[main.cpp]
H1[common.h]
H2[utils.h]
H3[config.h]
H4[iostream]
H5[string]
H6[vector]
H7[algorithm]
M --> H1 --> H2 --> H3
H1 --> H4 --> H5 --> H6 --> H7
end
subgraph Impact["config.h 수정 시"]
I[재컴파일 대상]
I1[main.cpp]
I2[foo.cpp]
I3[bar.cpp]
I4[... 50+ TU]
end
핵심: config.h 하나 수정 → 이를 직접·간접 포함하는 모든 TU 재컴파일.
빌드 시간 측정
# 시간 측정 (GNU time)
/usr/bin/time -v make -j8 2>&1 | grep -E "Elapsed|Maximum resident"
# CMake + Ninja 상세 타이밍
cmake --build build -- -j8 -v 2>&1 | tee build.log
# 컴파일만 측정 (링크 제외)
# GCC: -ftime-report
g++ -c -ftime-report -O2 main.cpp 2>&1 | tail -20
2. C++20 모듈로 의존성 격리
모듈 vs 헤더
| 항목 | #include 헤더 | C++20 모듈 |
|---|---|---|
| 파싱 횟수 | TU마다 반복 | 한 번만 (BMI 캐시) |
| 전이적 노출 | 전체 노출 | export된 것만 |
| 템플릿 인스턴스 | TU마다 반복 | 한 번만 |
| 빌드 순서 | 순서 무관 | 모듈 의존성 순서 필요 |
모듈 기본 예제
math.mpp (모듈 구현):
// math.mpp - C++20 모듈
module;
#include <cmath> // 모듈 선언 전에만 전통적 #include
export module math;
export double sqrt_safe(double x) {
return x >= 0 ? std::sqrt(x) : 0.0;
}
export template<typename T>
T clamp(T v, T lo, T hi) {
return v < lo ? lo : (v > hi ? hi : v);
}
main.cpp (모듈 사용):
import math; // #include 대신 import
#include <iostream>
int main() {
std::cout << sqrt_safe(2.0) << '\n';
std::cout << clamp(42, 0, 100) << '\n';
return 0;
}
CMake에서 모듈 빌드
# CMakeLists.txt - C++20 모듈 지원
cmake_minimum_required(VERSION 3.28)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 모듈 파일을 먼저 컴파일 (의존성 순서)
add_library(math MODULE math.mpp)
target_compile_features(math PUBLIC cxx_std_20)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE math)
주의: GCC 14+, Clang 18+, MSVC 2022 17.5+에서 모듈 지원이 안정화됨. 이전 버전은 실험적.
모듈 전환 전략
flowchart LR
A[기존 헤더] --> B{의존성 적음?}
B -->|Yes| C[모듈로 전환]
B -->|No| D[PCH 우선 적용]
C --> E[점진적 확대]
D --> E
권장 순서: 의존성이 적은 유틸리티·공통 타입부터 모듈로 전환.
3. PCH(미리 컴파일된 헤더)
PCH란?
자주 쓰는 헤더들을 한 번만 컴파일해 .gch(GCC) 또는 .pch(Clang/MSVC)로 저장하고, 각 TU에서 이 바이너리를 재사용합니다.
sequenceDiagram
participant TU as Translation Unit
participant PCH as PCH 캐시
participant Compiler as 컴파일러
Note over TU,Compiler: PCH 없을 때
TU->>Compiler: stdafx.h 파싱 (매 TU마다)
Compiler->>Compiler: 반복 작업
Note over TU,Compiler: PCH 있을 때
PCH->>Compiler: stdafx.h.gch 로드 (한 번만 생성)
TU->>Compiler: 나머지 코드만 파싱
PCH 헤더 작성
pch.h (공통 포함 헤더):
// pch.h - 미리 컴파일할 헤더
#ifndef PCH_H
#define PCH_H
// 자주 쓰는 표준 라이브러리 (변경 거의 없음)
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <chrono>
// 프로젝트 공통 헤더
#include "config.h"
#include "types.h"
#endif
GCC에서 PCH 생성 및 사용
# 1. PCH 생성
g++ -x c++-header -std=c++20 -O2 -o pch.h.gch pch.h
# 2. 소스 컴파일 시 PCH 사용
g++ -std=c++20 -O2 -include pch.h -c main.cpp -o main.o
CMake에서 PCH 자동화
# CMakeLists.txt - PCH 지원
cmake_minimum_required(VERSION 3.16)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
# PCH 타겟
add_library(pch_header INTERFACE)
target_precompile_headers(pch_header INTERFACE
<iostream>
<string>
<vector>
<memory>
"config.h"
)
add_executable(app main.cpp foo.cpp bar.cpp)
target_link_libraries(app PRIVATE pch_header)
target_precompile_headers(app REUSE_FROM pch_header)
target_precompile_headers (CMake 3.16+): PCH를 자동 생성·연결합니다.
PCH 적용 시 주의사항
- 컴파일 옵션 일치: PCH 생성 시
-O2,-DDEBUG등이 TU와 완전히 동일해야 함 - 헤더 순서: PCH 헤더는 파일 최상단에 포함 (그 전에 다른
#include금지) - 변경 빈도: 자주 바뀌는 헤더는 PCH에 넣지 말 것 (PCH 재생성 비용)
4. ccache로 재빌드 가속
ccache 동작 원리
flowchart TB
subgraph Input["입력"]
S[소스 + 컴파일 옵션]
end
subgraph CCache["ccache"]
H[해시 계산]
L{캐시<br/>존재?}
R[캐시에서 복원]
C[실제 컴파일]
S2[캐시 저장]
end
S --> H --> L
L -->|Hit| R
L -->|Miss| C --> S2
R --> O[오브젝트 출력]
C --> O
동작: 소스+옵션 해시로 캐시 조회 → Hit이면 컴파일 생략, Miss면 컴파일 후 저장.
ccache 설치 및 설정
# Ubuntu/Debian
sudo apt install ccache
# macOS
brew install ccache
# PATH에 ccache 우선 배치
export PATH="/usr/lib/ccache:$PATH"
# 또는
export CC="ccache gcc"
export CXX="ccache g++"
~/.ccache/ccache.conf (또는 환경변수):
# 캐시 크기 (기본 5GB)
max_size = 10G
# 압축 (디스크 절약, 약간의 CPU 비용)
compression = true
compression_level = 6
# 디버그 정보 무시 (다른 경로에서 빌드해도 Hit)
hash_dir = false
# 로그 (디버깅용)
log_file = /tmp/ccache.log
CMake + ccache
# CMakeLists.txt
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# 또는 빌드 시
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -B build
cmake --build build -j8
ccache 통계 확인
ccache -s
# 출력 예:
# cache hit (direct) 1234
# cache hit (preprocessed) 56
# cache miss 89
# cache hit rate 93.54 %
5. 병렬 빌드와 CMake 설정
병렬 빌드 기본
# make: -j N (N = 병렬 작업 수)
make -j$(nproc)
# Ninja (CMake 기본 생성기)
ninja -j8
# CMake
cmake --build build -- -j8
CPU 코어 수에 맞춰 자동 설정
# CMakeLists.txt
include(ProcessorCount)
ProcessorCount(N)
if(NOT N EQUAL 0)
set(CMAKE_BUILD_PARALLEL_LEVEL ${N})
endif()
# 환경변수로 Ninja에 전달
export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)
cmake --build build
Unity Build (Jumbo Build)
여러 .cpp를 하나로 합쳐 컴파일 횟수를 줄입니다. 링크 시간은 늘어날 수 있음.
# CMakeLists.txt
set(CMAKE_UNITY_BUILD ON)
add_executable(app main.cpp foo.cpp bar.cpp baz.cpp)
# → 내부적으로 하나의 unity_*.cpp로 합쳐서 컴파일
주의: static 변수 이름 충돌, 매크로 전역 오염 등에 유의.
6. distcc 분산 컴파일
distcc 개요
여러 머신에 컴파일 작업을 분산합니다. 로컬 전처리 + 원격 코드 생성.
flowchart TB
subgraph Client["클라이언트 머신"]
S[소스]
P[전처리]
D[distcc]
end
subgraph Workers["워커 머신들"]
W1[Worker 1]
W2[Worker 2]
W3[Worker 3]
end
S --> P --> D
D --> W1
D --> W2
D --> W3
W1 --> O[오브젝트]
W2 --> O
W3 --> O
distcc 설정
서버 (워커):
# distccd 실행 (포트 3632)
distccd --daemon --allow 192.168.1.0/24 --listen 0.0.0.0
클라이언트:
# 사용할 호스트 목록
export DISTCC_HOSTS="localhost 192.168.1.10 192.168.1.11"
export CC="distcc gcc"
export CXX="distcc g++"
# CMake
cmake -DCMAKE_C_COMPILER_LAUNCHER="distcc" \
-DCMAKE_CXX_COMPILER_LAUNCHER="distcc" \
-B build
cmake --build build -j16
ccache + distcc 조합
export CC="ccache distcc gcc"
export CXX="ccache distcc g++"
# ccache → distcc → gcc 순서로 래핑
7. 자주 하는 실수와 해결법
문제 1: PCH 사용 시 “file not found” 또는 “invalid precompiled header”
원인: PCH 생성 옵션과 TU 컴파일 옵션이 다름.
# ❌ 잘못된 예: PCH는 -O0, TU는 -O2
g++ -x c++-header -O0 -o pch.h.gch pch.h
g++ -O2 -include pch.h -c main.cpp # 에러!
해결:
# ✅ 옵션 일치
g++ -x c++-header -std=c++20 -O2 -DNDEBUG -o pch.h.gch pch.h
g++ -std=c++20 -O2 -DNDEBUG -include pch.h -c main.cpp
문제 2: PCH 헤더 앞에 다른 #include가 있음
원인: PCH는 반드시 첫 번째 포함되어야 함.
// ❌ 잘못된 예
#include "other.h" // PCH보다 먼저!
#include "pch.h"
// ✅ 올바른 예
#include "pch.h"
#include "other.h"
문제 3: ccache Hit이 안 됨
원인: __FILE__, __DATE__, __TIME__ 등이 소스에 포함되어 매번 다른 입력으로 인식.
// ❌ ccache 무효화
std::cout << "Built at " << __DATE__ << " " << __TIME__;
해결: 빌드 타임스탬프는 링크 단계에서 주입하거나, 별도 생성 파일로 분리.
// ✅ 버전 정보를 별도 .cpp에서
// version.cpp - 빌드 스크립트가 생성
const char* build_time = "2026-04-05 12:00:00";
문제 4: 모듈 빌드 순서 오류
원인: 모듈 B가 모듈 A를 import하는데, A가 아직 빌드되지 않음.
# ✅ 의존성 순서 보장
add_library(mod_a MODULE a.mpp)
add_library(mod_b MODULE b.mpp)
target_link_libraries(mod_b PRIVATE mod_a) # mod_a 먼저 빌드
문제 5: distcc “compiler not found”
원인: 워커에 동일한 컴파일러·버전이 없음.
해결: 워커에 같은 툴체인 설치, DISTCC_VERBOSE=1로 경로 확인.
# 워커에서
which g++
# 클라이언트와 동일한 경로/버전이어야 함
문제 6: Unity Build에서 ODR 위반
원인: 여러 cpp를 합치면서 static 변수나 익명 네임스페이스가 중복.
// ❌ foo.cpp
static int counter = 0;
// ❌ bar.cpp (Unity로 합쳐지면)
static int counter = 0; // ODR 위반!
해결: static 대신 익명 네임스페이스, 또는 Unity Build 비활성화.
// ✅
namespace {
int counter = 0;
}
8. 성능 최적화 팁
팁 1: 헤더 정리
// ❌ 불필요한 포함
#include <iostream> // cout만 쓰는데
#include <vector> // 사용 안 함
// ✅ 필요한 것만
#include <ostream> // std::ostream
#include "my_types.h"
forward declaration 활용:
// ✅ 헤더에서
class Bar; // forward declaration
void use_bar(Bar& b); // Bar 정의 불필요
// .cpp에서
#include "bar.h"
void use_bar(Bar& b) { /* ... */ }
팁 2: 템플릿 분리
// ❌ 헤더에 모든 구현
template<typename T>
void process(T x) {
// 긴 구현...
}
// ✅ 선언만 헤더, 구현은 .tpp
// util.hpp
template<typename T> void process(T x);
// util.tpp
#include "util.hpp"
template<typename T>
void process(T x) { /* ... */ }
필요한 타입만 .cpp에서 명시적 인스턴스화:
// util_instances.cpp
#include "util.tpp"
template void process<int>(int);
template void process<double>(double);
팁 3: 디버그 빌드 최적화
# Debug에서도 PCH 사용
set(CMAKE_PCH_INSTANTIATE_TEMPLATES OFF)
target_precompile_headers(app PRIVATE pch.h)
팁 4: 증분 빌드 최대화
- 빌드 디렉토리 고정:
build/한 곳에 유지해 ccache Hit 극대화 - 경로 정규화:
hash_dir = false로 절대 경로 차이 무시 - 타임스탬프 분리: 소스는 Git, 빌드 아티팩트는 별도 저장
팁 5: CI에서 ccache 활용
# GitHub Actions 예시
- uses: actions/cache@v4
with:
path: ~/.ccache
key: ccache-${{ runner.os }}-${{ hashFiles('**/CMakeLists.txt', '**/*.cpp') }}
restore-keys: ccache-${{ runner.os }}-
- run: |
ccache -s # 캐시 통계
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -B build
cmake --build build -j4
9. 프로덕션 패턴
패턴 1: 계층별 빌드 전략
flowchart TB
subgraph Layer1["1층: 안정 라이브러리"]
L1[표준 라이브러리]
L2[서드파티]
end
subgraph Layer2["2층: 프로젝트 코어"]
M1[모듈/PCH]
end
subgraph Layer3["3층: 애플리케이션"]
A1[앱 코드]
end
L1 --> L2 --> M1 --> A1
- 1층: PCH 또는 모듈로 한 번만 빌드
- 2층: 변경 적은 코어를 모듈화
- 3층: 자주 바뀌는 앱 코드는 ccache·병렬에 의존
패턴 2: 빌드 프로파일
# CMakePresets.cmake 또는 프로파일
set(CMAKE_BUILD_TYPE "Release")
set(USE_PCH ON)
set(USE_CCACHE ON)
set(CMAKE_CXX_COMPILER_LAUNCHER "ccache")
패턴 3: 모니터링
# 빌드 시간 추적
echo "$(date -Iseconds)" >> build_times.log
/usr/bin/time -a -o build_times.log -f "Elapsed: %E\n" cmake --build build -j8
패턴 4: 점진적 모듈 전환
- 1단계: 새 코드만 모듈로 작성
- 2단계: 의존성 적은 유틸을 모듈로 전환
- 3단계: 핵심 라이브러리 모듈화
- 4단계: 레거시 헤더는 PCH로 유지
10. 실전 예제: 종합 적용
프로젝트 구조
my_project/
├── CMakeLists.txt
├── src/
│ ├── pch.h
│ ├── main.cpp
│ ├── foo.cpp
│ └── bar.cpp
└── build/
CMakeLists.txt (종합)
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
# ccache
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# PCH
add_library(pch_header INTERFACE)
target_precompile_headers(pch_header INTERFACE
<iostream>
<string>
<vector>
"config.h"
)
# 병렬 빌드
include(ProcessorCount)
ProcessorCount(N)
if(NOT N EQUAL 0)
set(CMAKE_BUILD_PARALLEL_LEVEL ${N})
endif()
# 실행 파일
add_executable(app
src/main.cpp
src/foo.cpp
src/bar.cpp
)
target_link_libraries(app PRIVATE pch_header)
target_precompile_headers(app REUSE_FROM pch_header)
빌드 스크립트
#!/bin/bash
# build.sh
set -e
# ccache 활성화
export CCACHE_DIR="${CCACHE_DIR:-$HOME/.ccache}"
mkdir -p "$CCACHE_DIR"
# distcc (선택)
# export DISTCC_HOSTS="localhost 192.168.1.10"
cmake -B build \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
echo "=== ccache 통계 ==="
ccache -s
성능 비교 (예시)
| 설정 | 클린 빌드 | 증분 빌드 (1파일 수정) |
|---|---|---|
| 기본 (-j1) | 180초 | 45초 |
| -j8 | 35초 | 12초 |
| -j8 + ccache | 35초 (첫) | 2초 |
| -j8 + ccache + PCH | 28초 | 2초 |
| -j16 + distcc | 18초 | 3초 |
구현 체크리스트
프로덕션 적용 시 확인할 항목:
-
-j$(nproc)또는CMAKE_BUILD_PARALLEL_LEVEL설정 - ccache 설치 및
CMAKE_CXX_COMPILER_LAUNCHER=ccache적용 - PCH에 자주 쓰는 표준·프로젝트 헤더만 포함 (변경 잦은 헤더 제외)
- PCH 생성 옵션과 TU 컴파일 옵션 일치 확인
- CI에서 ccache 캐시 디렉토리 유지 (
actions/cache등) - distcc 사용 시 워커에 동일 컴파일러·버전 설치
- 모듈 도입 시 CMake 3.28+ 및 GCC 14+/Clang 18+ 확인
- 빌드 시간 로깅으로 개선 효과 측정
컴파일러별 지원 현황
| 기능 | GCC 14 | Clang 18 | MSVC 2022 17.5 |
|---|---|---|---|
| C++20 모듈 | ✅ | ✅ | ✅ |
| PCH | ✅ .gch | ✅ .pch | ✅ .pch |
#pragma once | ✅ | ✅ | ✅ |
target_precompile_headers | ✅ | ✅ | ✅ |
추가 문제 시나리오
시나리오 A: CI에서 매번 클린 빌드
- 증상: PR마다 15분 이상 빌드, 캐시 없음
- 해결: ccache 캐시를 아티팩트로 저장·복원,
CMakeLists.txt·소스 해시를 캐시 키로 사용
시나리오 B: 팀원마다 빌드 시간이 다름
- 증상: 일부는 5분, 일부는 20분
- 원인: SSD vs HDD, ccache 미설정,
-j값 차이 - 해결: 공통
build.sh·CMake 프리셋으로 환경 통일
시나리오 C: 헤더 하나 수정에 전체 리빌드
- 증상:
config.h수정 시 50개 이상 TU 재컴파일 - 해결: config를 모듈로 분리하거나, PCH에서 제외하고 필요한 cpp에서만 포함
정리
| 기법 | 적용 시점 | 효과 | 난이도 |
|---|---|---|---|
| 병렬 빌드 (-j) | 항상 | 2~8배 | 쉬움 |
| ccache | 항상 | 증분 5~20배 | 쉬움 |
| PCH | 헤더 많은 프로젝트 | 1.2~2배 | 중간 |
| 모듈 | C++20, 새/리팩터 코드 | 1.5~3배 | 어려움 |
| distcc | 다중 머신 환경 | 2~4배 | 중간 |
| Unity Build | TU 수가 매우 많을 때 | 1.5~2배 | 주의 |
핵심 원칙:
- 측정 먼저: 병목 구간을 프로파일링
- 쉬운 것부터:
-j, ccache → PCH → 모듈 - CI 연동: ccache 캐시 유지, 병렬 빌드
- 점진적 적용: 한 번에 다 바꾸지 말고 단계별 도입
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. 대규모 프로젝트 빌드 시간 단축, CI/CD 파이프라인 최적화, 개발 생산성 향상 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
한 줄 요약: 모듈·PCH·ccache·분산 빌드를 조합하면 빌드 시간을 크게 줄일 수 있습니다.