アメリカの学会 ACM で発表された論文「QUIC is not Quick Enough over Fast Internet」を読んだので、ブログ記事にしておきます。
- QUIC is not Quick Enough over Fast Internet | Proceedings of the ACM Web Conference 2024
https://dl.acm.org/doi/10.1145/3589334.3645323
論文の結論を先に書いておくと、
- 500Mbps を越える高速かつ高品質なネットワーク接続環境での話(有線接続とか WiFi 7 とか 5G モバイルとかを想定)。
- 大きなファイル (~1GB) のダウンロードに要する時間とかで、体感できる差が観測できる(9秒 vs 18秒)。
- クライアント側の OS (Linux/macOS/Windows) やソフトウェア (cURL/Chromium/Firefox) に関わらず同様に差が観測できる。
- サーバ側のソフトウェア (OpenLiteSpeed/Nginx-quic) に関わらず同様に差が観測できる(OS は Ubuntu 18.04)。
- QUIC が遅い原因は、「UDP であること」「プロトコルスタックがユーザ空間で実装されていること」である。つまり、QUIC を QUIC たらしめている特徴がそのまま原因になっている。
論文の概要
論文の概要(グラフとか)は、Gigazine さんの記事を参照してください(手抜きですみません):
- HTTP/3として知られるQUICは高速インターネット環境ではHTTP/2に大敗することが判明 - GIGAZINE
https://gigazine.net/news/20240910-http2-faster-than-http3-quic/
論文では、Ubuntu 18.04 のデスクトップ PC を2台用意して、ほぼ Ethernet 直結、という構成で、いろいろ評価しています。モバイル回線などを想定した帯域制限は、Linux の tc
コマンドを使って模擬しています。モバイル回線の模擬では、歩行パターンと自動車走行パターンの2パターンを用いています。
論文では、いろいろなクライアントプログラム(=ユーザ空間のプロトコルスタックの実装)を使って評価しています:
- cURL
quic_client
by Chromium Project- Chromium
- Google Chrome
- Microsoft Edge
- Opera
- Firfox
いずれのクライアントプログラムでも、「CPU 負荷が高い」「ファイルダウンロードが遅い」という傾向は変わらず。
また、Linux (Ubuntu) だけでなく、macOS や Windows でも評価してみたが、傾向は変わらず。
以下、原因の解析です。
パケットトレース (tcpdump) を見てみると、以下のことがわかった:
- Linux kernel で処理された「受信パケット数」が一桁多い (744K vs 58K)。これは、NIC の TCP オフロード機能で複数の TCP セグメントが1回にまとめられた影響。
- ACK が返ってくるまでの round-trip time (RTT) が一桁遅い (16.2ms vs 1.9ms)。ping RTT は 0.23ms なので、クライアント側での ACK 送信処理の時間が支配的な要因。
クライアント側での CPU 負荷が高い原因について、Linux kernel のプロファイリングを解析したところ、以下のことがわかった:
- Linux カーネル内部でのパケット受信処理
netif_receive_skb
の呼び出し回数が一桁多い (231K vs 15K) - システムコールの回数が一桁多い (17K vs 4K)
- カーネル空間からユーザ空間にデータをコピーする処理
copy_user_enhanced_fast_string
の呼び出し回数が多い (4K vs 3K)
クライアントプログラム (Chromium) のプロファイリングを解析したところ、以下のことがわかった:
- パケットデータをソケットから読み出す処理が重い (0.248sec vs 0.037sec)
- パケットのペイロードを解析する処理が重い (0.310sec vs 0.084sec)
- ACK を返す処理が重い (2.972sec vs 0sec)
このような傾向が出る原因は、ひとつは、「TCP ではなく UDP を使っている」からで、
- TCP は単純な byte stream なので複数の TPC セグメントをまとめて処理するのが簡単だが、QUIC のように multiplex している UDP ではそもそも困難。
- したがって、TCP のように NIC のプロトコルオフロード機能が効かない。すべてのパケットを1個ずつ処理しないといけない。
もうひとつの原因は、「プロトコルスタックがユーザ空間で実装されている」からで、
- 1パケットごとにカーネル空間からユーザ空間にデータコピーを行っているので、重い。
- ACK を返す処理は、TCP ならカーネル内で効率的に折り返せるが、QUIC ではユーザ空間で行うので、重い。
というストーリーになっています。
以上の結果から、論文では、以下のような改善策を提案しています:
- NIC のプロトコルオフロード機能(複数の UDP パケットをまとめる処理)を活用する。ただし、このオフロード機能が使えるかはクライアントのプラットホームに依るので、一般化は困難。
- QUIC の UDP パケットに適した NIC プロトコルオフロード機能を実装する(受信側で複数パケットをまとめる処理とか、送信レート制御とか)。
- クライアント側での QUIC 実装ロジックを工夫する(“delayed QUIC ACK” を実装するとか、
recvmsg
の代わりにrecvmmsg
を呼ぶとか)。 - クライアント側をマルチスレッド処理にして、複数の QUIC 接続を使って並列ダウンロードする。
感想
QUIC が遅い原因は、「UDP であること」「プロトコルスタックがユーザ空間で実装されていること」ということで、つまり、QUIC を QUIC たらしめている特徴がそのまま原因になっています。したがって、今回の測定結果を劇的に改善するのは、難しそうです。
UDP パケットの処理を高速化するには NIC のオフロード機能が有効と言っていますが、そもそも UDP の場合はオフロード機能を実装するのが難しいとも言っていて、まぁ、一般的な NIC に実装されそうにはありません。
プロトコルスタックがユーザ空間で実装されているのでオーバヘッドが大きい、という点については、改善できる点はあるにしても、本質的にはどうにもならなさそうです。個人的には、HPC 向け高速ネットワークの InfiniBand などで採用されている User-level 通信が使えれば、カーネルを介すオーバヘッドはなくなるので、思想的には最適解な気がします。ただ、一般的な Ethernet NIC に実装されるのは難しそうですが・・・。
あと、1点気になるところとして、「サーバとクライアントとのネットワーク距離が遠い場合には、また違った傾向が出てくるのでは?」と思いました。論文では言及がありませんでしたが、ACK が返ってくるタイミングや、送信レート制御処理などに影響があるので、もしかしたら別の知見が得られるかも??
いずれにしても、QUIC プロトコルに関する新しい知見で、おもしろい論文でした。
p.s. この論文、arXiv でもダウンロードできちゃうみたいですが、読むならちゃんと ACM で買いましょう。無料ユーザ登録して、15ドルで買えます。