응답 지연 시간: 실제로 측정해보기

2026. 1. 1. 11:33·CS(Computer Science)

들어가며

가상 면접 사례로 배우는 대규모 시스템 설계 기초 의 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 캐시 효과)

예상 밖의 결과: 책에서는 디스크가 메모리보다 훨씬 느리다고 했는데, 실측에서는 큰 차이가 없었다. 이는 다음 이유 때문으로 추정된다:

  1. SSD의 성능: 책의 이론값은 HDD 기준일 가능성
  2. OS 파일 시스템 캐시: 첫 Setup에서 파일을 쓸 때 이미 OS가 캐시했을 수 있음
  3. 작은 파일 크기: 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 캐시면 거의 차이 없네!"

인상 깊었던 점

  1. SSD의 위력: HDD 시대의 이론값과 달리, 현대의 SSD는 작은 파일 읽기에서 메모리와 비슷한 성능을 보였다. 하지만 이는 OS 캐시 효과일 수도 있다.
  2. 동기화 비용은 여전히 저렴: 8.9ns는 매우 작지만, 고빈도 작업에서는 누적되면 무시 못할 오버헤드다.
  3. 압축은 트레이드오프: 네트워크 전송 시간을 줄이려고 압축하는데, 압축 비용 자체도 고려해야 한다. 1KB에 17.7μs라면 큰 데이터일수록 압축 시간도 증가한다.
  4. 측정의 중요성: 이론값과 실제값은 환경에 따라 크게 다를 수 있다. "성능 최적화"할 때 막연한 추측이 아니라 실제로 측정해보는 습관이 필요하다.

한계점

  • OS 파일 캐시 영향으로 디스크 성능이 과대평가되었을 가능성
  • localhost 네트워크는 실제 네트워크와 차이가 큼
  • 단일 스레드 측정만 진행 (멀티스레드에서는 결과가 다를 수 있음)

🔗GitHub 저장소

해당 글에 작성된 코드는 아래 GitHub 저장소에서 확인할 수 있다.

  • GitHub 저장소: latency-numbers-benchmark

References

  • JMH 공식 문서
  • Jeff Dean's Latency Numbers
  • 『가상 면접 사례로 배우는 대규모 시스템 설계 기초』 2장

'CS(Computer Science)' 카테고리의 다른 글

프로그래밍에서 소수점 반올림은 왜 오차가 생길까?  (0) 2025.12.03
'CS(Computer Science)' 카테고리의 다른 글
  • 프로그래밍에서 소수점 반올림은 왜 오차가 생길까?
zeromok
zeromok
  • zeromok
    지극히개발적인
    zeromok
  • 전체
    오늘
    어제
    • 전체글🦖
      • Java & Spring
      • CS(Computer Science)
      • Data Structure(자료구조)
      • 알고리즘
      • 독서
        • 가상 면접 사례로 배우는 대규모 시스템 설계 기초
      • 동시성 주문&재고 프로젝트
  • 블로그 메뉴

    • 홈
    • 방명록
    • GitHub
  • 링크

  • 인기 글

  • 태그

    플로이드워셜
    java
    알고리즘
    백준
    투포인터
    JMH
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
zeromok
응답 지연 시간: 실제로 측정해보기
상단으로

티스토리툴바