Introduction
datacenter access link bandwidth는 급격히 증가한 반면에, 다른 모든 host resource(core speeed, cache size, NIC buffer size)는 대게 발전이 없었다. 따라서 전통적인 Linux network stack에서 CPU inefficiency를 이해하는 것은 중요하다.
Linux network stack의 overhead를 분석한 이전 연구들도 존재하지만, 이 연구들은 data center network에 존재하는 long flow, 혹은 short와 long을 섞은 mixture를 고려하지 않았다.
저자들이 밝힌 주요점들은 아래와 같다.
short flows + low-bandwidth link 조건에서 진행되었던 선행 연구들에서는 protocol processing이 main bottleneck이었으나, long flows + high-bandwidth link 상황에서는 receiver-side의 packet processing, 즉 kernel buffer로부터 application buffer로 data를 copy 하는 과정이 main bottleneck이 된다.
The reducing gap between bandwidth-dealy product(BDP) and cache sizes leads to suboptimal throughput
대다수의 system에서는 NIC이 L3 cache에 data를 DMA로 쓰게끔 하는 Direct Cache Access (DCA, Intel DDIO) 가 기본으로 켜져 있다. DDIO로 인해 data copy overhead로 감소될 것 같으나 NIC이 L3 캐시에 data를 바로 쓰더라도, 이를 CPU가 빠르게 처리하지 못해 data가 L3 cache에 머무르는 시간이 증가하는 상황이 발생한다. (packet이 app에 의해 처리되기까지의 시간인 host latency도 증가). 즉 host processing 과정에서 bottleneck이 생기게 되는데 NIC이 계속 data를 cache에 밀어넣으면 네트워크에 떠 있는 data의 양, BDP 값이 cache size보다 커지면서(RTT가 커짐) cache에 있는 data를 overwirte 하게 된다. 결국 data가 app에 의해 처리되기 전에 overwrite 되고 app 입장에선 cache에서 data를 찾을 수 없으므로 (cache miss) core당 throughput이 감소하는 현상을 관찰할 수 있다.
Host resource sharing considered harmful
여러 flow가 host resource를 공유하는 상황도 문제가 된다. 예를 들어, 같은 NUMA node에 있는 여러 flow가 같은 L3 캐시를 공유하는 상황에서, 한 flow가 cache에 쓴 data가 다른 flow에 의해 pollute 될 수 있다. 혹은 여러 flow가 NIC의 queue를 공유하게 되면서 같은 flow에 속하는 packet들이 queue에 순차적으로 도착하지 않을 수 있다. 이 경우 커널의 GRO 기능을 통해 같은 flow에 속하는 여러 packet들을 aggregate 할 수 있는 기회가 줄어드므로 per-byte processing overhead가 커진다. (== 동일한 양의 데이터를 처리하는데 CPU가 더 많은 연산을 하게 된다.)
The need to revisit host layering and packet processing pipelines
long flow와 short flow가 같은 core에 배정될 경우, long flow는 data copy overhead가 크고, short flow는 packet processing overhead가 큰데, 오늘날의 network stack은 이를 고려하지 못하고 같은 pipeline으로 처리해 버리니 throughput-per-core가 낮아질 수밖에 없다. 따라서 flow에 따라 처리 방식을 다르게 할 수 있도록 application-aware packet processing pipeline이 필요하다.
PRELIMINARIES
End-to-End Data Path
Sender-side.
- Application Layer
- application이 wrtie system call 호출
- Socket Interface
- kernel이 socket buffers (skbs) 생성 및 초기화
- user buffer에서 kernel buffer로 data copy
- TCP/IP Protocol Stack
- skb에 TCP/IP 헤더 추가
- 방화벽 등 적용
- XPS (Transmit Packet Steering)
- skb를 처리할 cpu core 지정 (보통 이 application이 돌아가고 있는 cpu core로 지정)
- 해당 core와 매핑된 NIC Tx queue n을 찾음
(각 CPU Core는 XPS 설정에 따라 특정 NIC Tx Queue n과 매핑되어 있고,
해당 Tx Queue는 하나의 QDISC(software queue)와,
하나의 Tx Ring Buffer(DRAM circular buffer)와 1:1로 연결됨)
- skb를 해당 Tx queue와 연결된 QDSIC에 enqueue
- Queing Discipline
- GSO
- qdsic에서 skb를 dequeue, 이 skb를 Driver TX에게 전달 (NIC이 TSO를 지원한다고 가정)
- Drvier TX
- skb의 data가 위치한 메모리를 DMA 가능한 물리 주소로 변환
- 해당 주소와 메타데이터를 Tx descriptor에 기록
- Tx descriptor를 NIC의 Tx ring buffer(=DRAM Circular Buffer, Tx queue와 연결된 buffer)에 enqueue
- NIC에게 알림
- NIC
- Tx descriptor를 읽고, DMA로 payload를 fetch
- MTU 단위로 segmentation 진행 (=TSO, 각 segment에 TCP/IP header가 복사됨)
- 각 packet을 전송
receiver side.
- NIC
- 송신 측 NIC에서 전송된 packet이 수신 측 NIC의 Rx queue n에 도착
- Rx queue n과 연결된 Rx Ring Buffer에서 NIC이 빈 descriptor를 선택
- DMA를 통해 Rx descriptor가 가리키는 DRAM 주소에 packet 저장
(NIC은 하나 이상의 Rx queue를 갖고 있고, 각 Rx queue는 내부적으로 Rx Descriptor Ring Buffer와 연결돼 있음)
(Direct Cache Access를 지원하는 cpu에선 cache에 저장)
- NIC이 interrupt를 처리할 cpu core 결정
- RSS(Receive Side Steering): hash 기반
- aRFS(accelerated RFS): RFS by NIC
- SKB 이동 없음
- NAPI 처리 루틴에서 skb의 일부(특히 헤더)가 l3 캐시로 올라옴
- GRO, 방화벽, TCP 처리, read 복사 등에서 SKB 안의 헤더/데이터를 다시 읽을 때 cache hit
- interrupt 발생
- IRQ Handler
- NIC의 Rx queue에 수신된 packet이 있다는 사실만 처리
- 해당 packet을 나중에 softirq context에서 처리하도록 NAPI poll을 softirq 큐에 등록
- RX NAPI
- softirq context에서 NAPI poll 함수 실행
- Rx Ring Buffer를 순회하며, packet이 들어있는 descriptor를 확인
- Rx descriptor가 가리키는 메모리에서 packet을 읽어와 skb 생성
- GRO
- softirq context에서 실행되는 커널 기능
- 생성된 여러 skb가 같은 flow에 속하고(=헤더 접근, src IP, dst IP, src port, dst port, protocol가 모두 동일), 순서가 연속되면 하나의 skb로 병합
- RPS(Receive Packet Steering)/RFS(Receive Flow Steering) (SW 기반)
- 수신된 skb이 어떤 flow에 속하는지 다시 분석하여 이를 처리할 core 선택
- RPS: hash 기반
- RFS: application이 돌고 있는 core로 steer (temporal locality)
- TCP/IP Protocol Stack
- 방화벽 등 처리
- 수신한 pck의 TCP/IP 헤더 및 순서, checksum 등 확인
- Socket Interface
- 유효한 skb만 socket buffer에 enqueue
- Application Layer
- application이 read system call 호출
- kernel buffer에서 user buffer로 payload copy
Measurement Methodology
Testbed setup.
- 두 server를 100Gbps link로 직접 연결
- 각 서버는 4-socket NUMA-enabled Intel Xeon Gold 6128 (cpu 4개, cpu 1개당 core 6개)
Experimental scenarios.
- 5 standard traffic pattern (single/one-to-one/incast/outcast/all-to-all)
- flows (long/short/mix)
Performance metrics.
- total throughput
- throughput-per-core
- sampling based CPU utilization (주기적으로 cpu가 지금 어떤 함수를 실행 중인지 count)
LINUX NETWORK STACK OVERHEADS
실험 전제.
- aRFS가 비활성화 되었을 경우, IRQ를 처리할 core가 hash에 의해 결정되므로 nondeterministic 함
- derministic 결과를 위해 app core와 다른 NUMA node에 있는 core가 IRQs를 처리한다고 통제.
Single Flow
- 모든 optimization을 다 적용하더라도 single core의 throughput은 ~42Gpbs가 한계.
- data copy와 skb allocation(MTU-sized skb 만들고, merge하고)으로 인해 receiver overhead 큼
- 모든 optimization이 비활성화되었을 경우, tcp/ip processing에 소모되는 cpu cycle이 가장 많음
- sender side - TSO로 완화 가능
- receiver side - GRO로 완화 가능
- TCP receive window size와 NIC Rx descriptor 수가 커지면 cache miss rate가 증가함
- 이유 1. BDP values > L3 cache capacity
- BDP = access link bandwidth * latency
- large TCP Rx buffer
→ delayed application read
→ L3 캐시에 있던 payload가 evict
- 이유 2. suboptimal cache utilization
- large Rx descriptor → 이해가 안 됨 data가 더 분산되어 쓰일 수 있는 거 아닌가?
- 이미 엄청난 양의 데이터가 들어오고 있다는 가정 하라, large Rx descriptor를 사용하더라도 감당을 못 하는 것임. 그렇다면 small Rx descriptor일 때는 cache evict가 안 발생하냐? small Rx descriptor일 땐 sender가 빨리빨리 읽어가서 괜찮음 (sender가 읽어가는 속도와 연관된 부분은 더 공부해야 할 듯)
One-to-One
- reduced effects of optimization
- increased flows
→ NIC L3 cache에 data들이 overwrite
→ reduced L3 cache locality
- scheduling overhead
- network saturation
→ throughput이 모든 core에 fairly share
→ receiver가 idle 해짐 (빈번히 sleep)
→ increased context switching
- reduced memory allc/dealloc overhead
- core마다 pageset이 존재 + less trafic
→ skb를 위해 alloc된 page가 금방 사용되고 금방 free 됨
→ global-free list까지 가서 free page를 fetch 해 오지 않아도 됨
Incast
- NIC L3 cache contention
- 여러 sender threads의 CPU scheduling overhead
Outcast
- CPU-efficient sender-side processing pipeline
- TSO
- aRFS
- 같은 sender core가 같은 flow를 반복 처리
→ skb를 처리할 때 필요한 data 구조가 그 core의 l3 캐시에 위치
→ DRAM까지 가지 않고 l3 캐시에서 hit
→ cpu overhead 감소
→ throughput-per-core 증가
All-to-All
- non-NIC-local NUMA node의 lack of cache locality
- one-to-one case와 마찬가지로 GRO가 묶을 수 있는 packet 수가 줄어듬
Impact of In-network Congestion
- decreased total throughput, increased throughput-per-core (loss rate 0 to 0.00015)
- packet loss rate 증가
→ sender가 전송 속도를 늦춤 (TCP congestion control)
→ receiver 측이 idel + data가 빨리 몰리지 않음
→ cache hit이 좋아짐
- 전체적으로 봤을 땐 총 시간 동안 처리 양이 줄지만, core 단위로 봤을 땐 처리 시간 대비 총 처리양이 증가하게 됨
- netdevice subsystem overhead
- 빈번한 packet 재전송에 cpu cycle이 많이 소모되어 data copy 하는 데 쓰이는 cycle을 줄어듬
- sender overhead
- receiver-side: packet loss 발생 시 중복된 ACKs만 여러 개 전송
- sender-side: congestion control + retransmission of the lost packet → higher overhead
Impact of Flow Size
- extremely short flows: reduced effects of DCA
- small flows에 대해선 data copy보다 TCP/IP processing overhead가 더 큼
- NIC-local NUMA nodes나 NIC-remote NUMA nodes나 throughput-per-core가 비슷함
- orchestrating host resources between long and short flows
- long flow는 NIC-local NUMA, short flow는 NIC-remote NUMA nodes에서 scheduling 하는 것이 performance 측면에서 좋음
- avoid mixing long and short flows on the same core
- l3 cache pollution
- context switching
- host resource contention
Impact of IOMMU
- IOMMU: DMA를 수행하는 장치가 사용하는 virtual address를 physical address로 변환해 주는 HW
- increased memory management overhead
- NIC이 Rx descriptor를 확인하고, IOVA에 DMA write 시도
→ IOMMU가 page table look up
→ physical address로 변환
→ cpu가 data copy까지 완료
→ driver가 해당 page를 unmap (IOMMU entry 제거)
FUTURE DIRECTIONS
- Zero-copy mechanisms
- sender-side: NIC이 직접 application buffer에 DMA write
- receiver-side: mmap, NIC이 DMA로 write한 data를 app이 자신의 userspace에서 virtual address로 바로 접근할 수 있도록 physical address를 mapping
- application layer change overhead
- mmap을 통해 app은 NIC이 DMA한 raw packet을 봄
→ 이 raw packet은 어떠한 TCP/IP 상태 관리도 되어있지 않음 (packet reassembly, checksum 검증 등)
→ userspace에서 network protocol을 모두 다시 구현해야 함
- receiver-driven transport protocol design
- dynamic and application-aware host network stack