에듀테크 프로젝트 대시보드
제로카피 (Zero-Copy)
디스크의 파일을 네트워크로 그대로 흘려보낼 때, CPU가 데이터를 몇 번이나 베껴 쓰는가 — read()+write()부터 sendfile(), DMA 스캐터-개더까지.
"파일을 소켓으로 보낸다"는 단순한 일에도 전통적 방식은 데이터를
커널 버퍼 ↔ 유저 버퍼 사이로 CPU가 직접 2번 복사하고, read/write
호출마다 유저↔커널 모드를 오간다. 제로카피는 유저 공간을 건너뛰어 이 CPU 복사와
모드 전환을 없앤다. 대용량 파일 전송 서버(nginx, Kafka, 동영상 스트리밍)의 핵심 최적화.
파일 하나를 클라이언트에게 전송할 때 데이터는 아래 다섯 구간을 통과한다. 주황색은 커널 공간, 하늘색은 유저 공간, 회색은 하드웨어(DMA 엔진이 담당)이다.
malloc한 버퍼DMA(Direct Memory Access) 엔진은 CPU 없이 장치 ↔ 메모리 사이 데이터를 옮긴다. 그동안 CPU는
다른 일을 할 수 있다. 반면 커널 버퍼 → 유저 버퍼 복사는 CPU가 memcpy로
한 바이트씩 옮기는 순수 오버헤드다. 1GB 파일이면 그 큰 메모리를 두 번 왕복시키느라
CPU 사이클과 메모리 대역폭, 캐시가 통째로 낭비된다. 게다가 read()/write()
시스템 콜마다 유저→커널→유저로 모드를 전환(레지스터 저장·복원)해야 한다.
복사 4회(DMA 2 + CPU 2), 모드 전환 4회. App Buffer는 데이터를 그냥 통과시키기만 할 뿐 아무 일도 안 한다.
파일을 mmap으로 프로세스 주소 공간에 매핑하면, 유저 버퍼가 곧 커널 Read Buffer를
가리킨다. 디스크→App 복사가 사라져 CPU 복사가 1번(Read Buffer→Socket Buffer)으로 준다.
대신 매핑 비용이 있고, 전송 중 다른 프로세스가 파일을 자르면 SIGBUS가 날 수 있다.
시스템 콜 1번 → 컨텍스트 스위치 2번. 복사는 3회(DMA 2 + 커널 내부 CPU 1).
Java의 FileChannel.transferTo()가 내부적으로 이걸 쓴다.
NIC가 scatter-gather DMA를 지원하면, 커널은 Socket Buffer에 데이터를 복사하는 대신 "Read Buffer의 어디부터 몇 바이트"라는 디스크립터(위치·길이)만 적어둔다. 그러면 NIC의 DMA 엔진이 Read Buffer에서 직접 데이터를 긁어모아(gather) 전송한다. CPU 복사 0회, 복사는 DMA 2회뿐. 이것이 리눅스 2.4+의 진짜 zero-copy다.
| 방식 | CPU 복사 | DMA 복사 | 컨텍스트 스위치 | 유저 공간 경유 |
|---|---|---|---|---|
| read() + write() | 2 | 2 | 4 | O |
| mmap() + write() | 1 | 2 | 4 | 매핑 |
| sendfile() | 1 | 2 | 2 | X |
| sendfile() + SG-DMA | 0 | 2 | 2 | X |
sendfile. 브로커 처리량의 비결sendfile on; 지시어로 정적 파일 전송에 사용FileRegion, FileChannel.transferTo()sendfile 이득이 줄어든다참고: Linux man 2 sendfile, man 2 splice ·
IBM Developer "Efficient data transfer through zero copy".