io_uring을 사용해 cp보다 4배 빠른 파일 복사기 만들기

발행: (2026년 1월 8일 오전 02:45 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

소개

저는 Linux io_uring을 사용하여 머신러닝 데이터셋용 고성능 파일 복사기를 만들었습니다. 적절한 워크로드에서는 cp -r보다 4.2× 빠릅니다. 아래는 비동기 I/O가 도움이 되는 경우와 그렇지 않은 경우에 대한 교훈입니다.

일반적인 ML 데이터셋 크기

데이터셋파일 수일반적인 크기
ImageNet1.28 M100–200 KB JPEG
COCO330 K50–500 KB
MNIST70 K784 bytes
CIFAR‑1060 K3 KB

cp -r 로 복사하면 각 파일마다 여러 시스템 콜(open, read, write, close)을 순차적으로 처리해야 하기 때문에 매우 느립니다. 100 000개의 파일을 복사하면 400 000+ 시스템 콜이 연속적으로 실행되는 셈입니다.

io_uring이 도움이 되는 이유

  • 배치된 제출 – 수십 개의 작업을 큐에 넣고 하나의 시스템 콜로 제출합니다.
  • 비동기 완료 – 작업이 순서와 상관없이 완료되어 CPU가 계속 작업을 수행할 수 있습니다.
  • 제로‑카피 – 커널 파이프를 통해 파일 디스크립터 간에 데이터를 직접 splice하여 사용자 공간 버퍼를 사용하지 않습니다.

대신에:

open → read → write → close → repeat

우리는 이렇게 합니다:

submit 64 opens → process completions → submit reads/writes → batch everything

아키텍처 개요

┌──────────────┐     ┌─────────────────┐     ┌─────────────────────┐
│ Main Thread  │────▶│  WorkQueue   │────▶│  Worker Threads     │
│ (scanner)    │     │  (thread‑safe)│     │  (per‑thread uring) │
└──────────────┘     └─────────────────┘     └─────────────────────┘

각 파일은 상태 머신을 따라 진행됩니다:

OPENING_SRC → STATING → OPENING_DST → SPLICE_IN ⇄ SPLICE_OUT → CLOSING

핵심 설계 결정

  • 워커당 동시에 64개의 파일을 처리합니다.
  • 스레드별 io_uring 인스턴스(잠금 경쟁을 방지).
  • 연속 디스크 접근을 위한 inode 정렬.
  • 데이터 전송을 위한 splice 제로‑카피 (source → pipe → destination).
  • 4 KB 정렬 할당을 갖는 버퍼 풀(O_DIRECT와 호환).

벤치마크

NVMe (빠른 로컬 스토리지)

작업량cp -ruring‑syncSpeedup
100 K × 4 KB 파일 (400 MB)7.67 s5.14 s1.5×
100 K × 100 KB 파일 (10 GB)22.7 s5.4 s4.2×

클라우드 SSD (예: GCP Compute Engine)

작업량cp -ruring‑syncSpeedup
100 K × 4 KB 파일67.7 s31.5 s2.15×
100 K × 100 KB 파일139.6 s64.7 s2.16×

큰 파일일수록 빠른 스토리지에서 io_uring의 이점이 더 커집니다. 이는 CPU가 I/O를 기다리는 시간이 줄어들고 작업을 겹쳐 수행하는 시간이 늘어나기 때문입니다.

enum class FileState {
    OPENING_SRC,    // Opening source file
    STATING,        // Getting file size
    OPENING_DST,    // Creating destination
    SPLICE_IN,      // Reading into kernel pipe
    SPLICE_OUT,     // Writing from pipe to dest
    CLOSING_SRC,    // Closing source
    CLOSING_DST,    // Closing destination
    DONE
};

완료가 상태 전환을 구동합니다: 완료가 도착하면 해당 파일 컨텍스트를 찾아 그 상태를 진행합니다.

제로‑카피와 splice

// Splice from source into pipe
io_uring_prep_splice(sqe, src_fd, offset,
                     pipe_write_fd, -1,
                     chunk_size, 0);

// Splice from pipe to destination
io_uring_prep_splice(sqe, pipe_read_fd, -1,
                     dst_fd, offset,
                     chunk_size, 0);

데이터가 사용자 공간에 절대 닿지 않으며, 커널이 파일 디스크립터 간에 페이지를 직접 이동합니다.

인오드 정렬

std::sort(files.begin(), files.end(),
    [](const auto& a, const auto& b) { return a.inode 

벤치마크는 로컬 NVMe 드라이브와 GCP Compute Engine VM에서 Ubuntu 24.04, 커널 6.14를 사용하여 실행되었습니다.

Back to Blog

관련 글

더 보기 »