들어가며
가상 면접 사례로 배우는 대규모 시스템 설계 기초 의 2장 "모든 프로그래머가 알아야 할 응답 지연 시간 표" 를 보고 막연히 "디스크가 메모리보다 느리다" 는 건 알았지만, 실제로 얼마나 차이 나는지 궁금해졌다.
그래서 직접 자바로 측정해보았다.
측정 환경
- JMH (Java Microbenchmark Harness) 1.37
- Java 17
- OS: MacOS
- CPU: Apple M1
- Disk: SSD
측정 항목
책에 나온 연산 중 자바 애플리케이션 레벨에서 측정 가능한 것들로만 골랐다.
- 뮤텍스 락/언락
- 메모리 1MB 순차 read
- 디스크 1MB 순차 read
- 1KB GZIP 압축
- 네트워크 왕복 지연 (localhost)
측정 불가능한 것들: L1/L2 캐시 접근, 분기 예측 오류, 주 메모리 참조등은 CPU 내부 하드웨어 이벤트라 JVM 레벨에서 직접 측정할 수 없다.
측정 방법
JMH 기본 설정
@BenchmarkMode(Mode.AverageTime) // 평균 실행 시간 측정
@OutputTimeUnit(TimeUnit.NANOSECONDS) // 나노초 단위 출력
@Warmup(iterations = 3, time = 1) // 워밍업 3회 (JIT 최적화)
@Measurement(iterations = 5, time = 1) // 실제 측정 5회
@Fork(1) // 새 JVM 1회
워밍업이 필요한 이유
자바는 JIT(Just-In-Time) 컴파일러를 사용한다. 처음에는 바이트코드를 인터프리터 방식으로 실행하다가, 자주 실행되는 코드(Hot Spot)를 발견하면 기계어로 컴파일해서 최적화한다.
- 콜드 스타트: JIT 컴파일 전, 인터프리터로 실행 (느림)
- 웜 스타트: JIT 컴파일 후, 기계어로 실행 (빠름)
워밍업 없이 측정하면 초기의 느린 실행이 평균을 왜곡시킨다. 프로덕션 환경은 오래 돌아가므로 항상 웜 스타트 상태다. 따라서 벤치마크도 워밍업 후 측정해야 실제 성능을 반영한다.
측정 결과

결과 분석
1. 뮤텍스 락/언락: ~8.942 ns
@Benchmark
public void mutexLockUnlock() {
synchronized (lock) {
// 빈 블록 - 락 획득/해제 시간만 측정
}
}
측정한 것: synchronized 키워드로 락 획득 -> 즉시 해제
왜 빈 블록인가: 락 자체의 순수한 오버헤드만 보려고. 코드가 있으면 그 실행 시간까지 포함된다.
실무 의미:
- 멀티스레드 환경에서
synchronized사용 시 기본 비용 - 락 안에서 10ns짜리 작업을 하면 실제로는 약 19ns 소요
- 초당 100만 건 처리하는 시스템이라면 약 9ms의 오버헤드 발생
2. 메모리 1MB 순차 read: ~666793.944 ns
@Benchmark
public long sequentialMemoryRead() {
long sum = 0;
for (byte b : memoryData) { // 1MB 배열 순회
sum += b;
}
return sum;
}
측정한 것: RAM에서 1MB 데이터를 순차적으로 읽기
sum을 계산하는 이유: JVM이 "결과를 안 쓰네?" 하고 최적화로 제거하는 것을 방지. 실제로 메모리를 읽도록 강제.
캐시 효과:
- 순차 접근이라 L1/L2 캐시가 효율적으로 동작
- CPU prefetcher가 다음 데이터를 미리 가져옴
실무 의미:
- 대용량 배열 처리 시 순차 접근이 유리한 이유
- 메모리에서 읽는 건 빠르지만, 1MB도 무시 못할 시간
3. 디스크 1MB 순차 read: ~749600.453 ns
@Benchmark
public long sequentialDiskRead() throws IOException {
byte[] data = Files.readAllBytes(diskFile);
long sum = 0;
for (byte b : data) {
sum += b;
}
return sum;
}
측정한 것: 디스크(SSD/HDD)에서 1MB 파일 읽기
왜 느린가:
- 디스크 seek time (헤드 이동)
- 데이터 전송 시간 (디스크 → 메모리)
- 시스템 콜 오버헤드
주의사항: OS가 파일을 캐시할 수 있어서 두 번째 실행부터는 메모리에서 읽을 수도 있다.
메모리 vs 디스크:
- 메모리: 667 μs
- 디스크: 750 μs
- 약 1.1배 차이 (SSD + OS 캐시 효과)
예상 밖의 결과: 책에서는 디스크가 메모리보다 훨씬 느리다고 했는데, 실측에서는 큰 차이가 없었다. 이는 다음 이유 때문으로 추정된다:
- SSD의 성능: 책의 이론값은 HDD 기준일 가능성
- OS 파일 시스템 캐시: 첫 Setup에서 파일을 쓸 때 이미 OS가 캐시했을 수 있음
- 작은 파일 크기: 1MB는 최신 SSD에게 매우 작은 크기
4. 1KB GZIP 압축: ~17660.563 ns
@Benchmark
public byte[] gzipCompression() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(baos)) {
gzip.write(uncompressedData);
}
return baos.toByteArray();
}
측정한 것: 1KB 데이터를 GZIP으로 압축하는 CPU 연산
실무 의미:
- HTTP 응답 압축 시 고려해야 할 오버헤드
- 1KB당 17.7μs면, 100KB는 약 1.77ms
- 네트워크 전송 시간을 줄이려고 압축하는데, 압축 비용 자체도 고려 필요
5. 네트워크 왕복 (localhost): ~23644.446 ns
@Benchmark
public boolean networkRoundTrip() throws IOException {
InetAddress localhost = InetAddress.getByName("localhost");
return localhost.isReachable(1000);
}
측정한 것: localhost로 ICMP ping (loopback)
한계:
- 실제 네트워크가 아닌 컴퓨터 내부 통신
- 2KB 전송을 정확히 측정한 건 아님
- 개념 확인용
실제 네트워크라면: 같은 데이터센터 내에서도 수백 μs ~ 수 ms, 지리적으로 멀면 수십~수백 ms
비교표
| 연산 | 이론(책) | 실측 (필자의 환경) | 비고 |
|---|---|---|---|
| 뮤텍스 락/언락 | ~100 ns | 8.9 ns | |
| 메모리 1MB 읽기 | ~250 μs | 667 µs | |
| 디스크 1MB 읽기 | ~30 ms (HDD) | 750 μs (SSD) | |
| 1KB 압축 | ~3 μs (Zippy) | 17.7 μs (GZIP) |
분석
- 뮤텍스가 더 빠른 이유: M1의 강력한 단일 스레드 성능과 최신 JVM 최적화
- 메모리가 더 느린 이유: 1MB 전체를 순회하며 sum 계산하는 추가 연산 포함
- 디스크가 훨씬 빠른 이유: SSD의 성능 + OS 캐시 효과 + 작은 파일 크기
- 압축이 더 느린 이유: Zippy(Snappy)는 속도 중심, GZIP은 압축률 중심
마무리
측정 전 vs 측정 후
측정 전: "디스크가 메모리보다 느리다는 건 알지" 측정 후: "SSD + OS 캐시면 거의 차이 없네!"
인상 깊었던 점
- SSD의 위력: HDD 시대의 이론값과 달리, 현대의 SSD는 작은 파일 읽기에서 메모리와 비슷한 성능을 보였다. 하지만 이는 OS 캐시 효과일 수도 있다.
- 동기화 비용은 여전히 저렴: 8.9ns는 매우 작지만, 고빈도 작업에서는 누적되면 무시 못할 오버헤드다.
- 압축은 트레이드오프: 네트워크 전송 시간을 줄이려고 압축하는데, 압축 비용 자체도 고려해야 한다. 1KB에 17.7μs라면 큰 데이터일수록 압축 시간도 증가한다.
- 측정의 중요성: 이론값과 실제값은 환경에 따라 크게 다를 수 있다. "성능 최적화"할 때 막연한 추측이 아니라 실제로 측정해보는 습관이 필요하다.
한계점
- OS 파일 캐시 영향으로 디스크 성능이 과대평가되었을 가능성
- localhost 네트워크는 실제 네트워크와 차이가 큼
- 단일 스레드 측정만 진행 (멀티스레드에서는 결과가 다를 수 있음)
🔗GitHub 저장소
해당 글에 작성된 코드는 아래 GitHub 저장소에서 확인할 수 있다.
- GitHub 저장소: latency-numbers-benchmark
References
- JMH 공식 문서
- Jeff Dean's Latency Numbers
- 『가상 면접 사례로 배우는 대규모 시스템 설계 기초』 2장
'CS(Computer Science)' 카테고리의 다른 글
| 프로그래밍에서 소수점 반올림은 왜 오차가 생길까? (0) | 2025.12.03 |
|---|