以下の内容はhttps://yasukata.hatenablog.com/entry/2026/01/13/082346より取得しました。


Docker 環境で DPDK と AF_XDP を通して仮想 NIC を扱う場合の性能

DPDK と AF_XDP は、ユーザー空間で動作するプログラムが NIC を介した I/O を行う際に、カーネルの通信に関連する実装の大部分を迂回すること(カーネルバイパス)を可能にするもので、通信性能の向上に寄与することが知られています。

これは物理 NIC だけではなく、コンテナ環境で利用される仮想 NIC においても同様の効果が期待できます。

この記事では、DPDK と AF_XDP を使ってカーネルバイパスを行うように実装されたアプリケーションが Docker 環境で仮想 NIC を扱う場合の性能について簡単な計測を通して調べます。

具体的な仮想 NIC を扱う設定方法と性能計測方法は全て記事の末尾のコマンドリストとDockerfile および compose.yaml に記載します。

計測内容は Docker の標準的な設定で実行可能かつ一台のマシンで完結するものに限定したため、ノートパソコン上の Linux をインストールした仮想マシンなどでも実行可能です。

ソフトウェアの組み合わせ

仮想 NIC

DPDK と AF_XDP では扱いやすいコンテナ環境での利用が想定された仮想 NIC が異なります。

具体的には、DPDK は virtio に関連した実装が充実している(8. Virtio_user for Container Networking — Data Plane Development Kit 25.11.0 documentation)一方で、AF_XDP は veth との親和性が高いようです。

仮想スイッチ

Linux bridge(Linux カーネルの bridge 実装)は Linux が提供する機能である veth と組み合わせやすいですが、DPDK が扱う virtio との接続には Linux カーネルの作成する tap デバイスを挟む(9. Virtio_user as Exception Path — Data Plane Development Kit 25.11.0 documentation)ことによるコストがあります。

一方で、DPDK へ対応している Open vSwitch は DPDK を利用するアプリと virtio を通した接続が容易です(DPDK vHost User Ports — Open vSwitch 3.6.90 documentation)。

これらのことから、Linux bridge を利用する場合は veth を通して AF_XDP を使いやすく、Open vSwitch を利用する場合は virtio 経由で DPDK を使うアプリと組み合わせやすいと言えると思います。

組み合わせ

上記のことから性能計測には、DPDK と AF_XDP について、それぞれ以下の組み合わせを利用します。

フレームワーク 仮想 NIC 仮想スイッチ
DPDK virtio Open vSwitch
AF_XDP veth Linux bridge

コンテナ用ネットワーク構成ソフトウェア

veth などの仮想 NIC や bridge との紐付けはコンテナ用ネットワーク構成機能を実装したソフトウェアによって行われます。

今回は Docker の標準的な設定を利用するため、各コンテナに Linux bridge と紐付けられた veth が割り当てられる構成を利用します。

Docker はそのままでは Open vSwitch の立ち上げと仮想 NIC の紐付けを行わないため、今回の計測では Open vSwitch を実行する場合には compose.yaml から専用のコンテナを起動するとともに、プログラム実行コマンド側で仮想 NIC の紐付けのための調整を行うようにします。

性能計測

ワークロード

  • アプリケーションのワークロードは利用可能なベンチマークツールの関係から memcached サーバーとします。
  • サーバー側にはキーバリューペアは保存せず、クライアントは a というキーに対応するバリューを取得するようリクエストを送るようにします。サーバーは対応するデータは保存されていないという情報をレスポンスとして返します。
  • サーバーとクライアントは同一ホスト上の別々のコンテナで実行され、それらの間の通信は仮想スイッチが中継します。

計測に利用するソフトウェア

DPDK の場合

Open vSwitch は DPDK と組み合わせて利用できるようにコンパイルします(Open vSwitch with DPDK — Open vSwitch 3.6.90 documentation)。コンパイルの方法は Dockerfile.dpdk に記載されています。

DPDK と AF_XDP の場合の両方

これらをインストールした Docker イメージの作成方法は記事の末尾の Dockerfile.dpdkDockerfile.af_xdp に記載されています。

カーネルをバイパスしない場合

カーネルをバイパスしない一般的な構成の場合についての参考値を得るために、以下のプログラムを利用して計測を行います。

基本的に各プログラムは一つのスレッドで実行して最大1CPU コアを利用するようにしますが、memtier_benchmark を利用する場合は十分な負荷をかけられるように4スレッドを利用することで最大4つの CPU コアを利用できるようにします。

下準備
  • DPDK の場合:huge page の設定と Open vSwitch 用のカーネルモジュールのインストールが必要です。コマンドは記事末尾に記載しています。
  • AF_XDP の場合:AF_XDP を利用する TCP スタックとカーネルTCP/IP スタックが TCP 通信を行う場合には、veth の送信チェックサムオフロードを無効に設定する必要がありました。今回は記事末尾に記載するように systemd を通して設定するようにしました。

性能計測に利用した環境

ノートパソコン上で動作する仮想マシンを利用しました。

計測結果

以下が計測の結果です。テーブル全体を見るには画面のサイズによっては横スクロールが必要かもしれません。

通し番号 サーバー クライアント 仮想スイッチ 利用 CPU コア数 スループット(リクエスト毎秒) 記事末尾の compose.yaml へのリンク
1 memcached (1 core) memtier_benchmark (4 cores) Linux bridge 5 436073 compose.1.yaml
2 memcached (1 core) bench-iip on AF_XDP (1 core) Linux bridge 2 612398 compose.2.yaml
3 mimicached on DPDK (1 core) bench-iip on DPDK (1 core) Open vSwitch (1 core) 3 3112032 compose.3.yaml
4 mimicached on AF_XDP (1 core) memtier_benchmark (4 cores) Linux bridge 5 1072698 compose.4.yaml
5 mimicached on AF_XDP (1 core) bench-iip on AF_XDP (1 core) Linux bridge 2 1646433 compose.5.yaml
  • 通し番号1と2の比較:サーバーがカーネルをバイパスしない公式の memcached 実装でも、クライアント側が AF_XDP を利用すると、少ない CPU コア数で高い性能が達成できる様子が見られました。
  • 通し番号3:DPDK を利用している場合に5つの計測の中で最大の性能が見られました。
  • 通し番号3と5の比較:注意点として、AF_XDP の最大性能よりも DPDK の場合の方が最大性能が高くなっていますが、AF_XDP の場合は CPU コアを2つしか利用しないのに対して、DPDK の場合は Open vSwitch のために CPU コアを1つ多く利用しているので、消費されている CPU リソースで換算すると、少なくともこのワークロードにおいては必ずしも AF_XDP が DPDK と比べて大幅に非効率とは言い難いと思われます。
  • 通し番号1と4の比較:サーバーが AF_XDP を利用すると、クライアントがカーネルをバイパスしない memtier_bechmark の場合にも性能が2倍以上になる様子が見られました。
  • 通し番号1と5の比較:サーバーとクライアント両方が AF_XDP を利用した構成では、一般的なカーネルをバイパスしない構成と比較して、3.7 倍以上の性能が見られました。

性能計測に利用したコマンドと Docker 関連ファイル

性能計測に利用したコマンドと Docker 関連のファイルを記載します。

なお、再現実験をされる場合には自己責任でお願いいたします。root 権限が必要なコマンドも含まれておりますので、ご注意ください。

この記事の作者は、この記事によって引き起こされた全ての問題についての責任を負いません。

下準備用コマンド

DPDK と Open vSwitch 利用のためのコマンド
sudo sh -c "echo 2048 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages"
sudo modprobe openvswitch

AF_XDP を利用するアプリとカーネルTCP スタックを利用するアプリが通信するための設定

カーネルTCP スタックを利用するアプリ側で veth の送信時チェックサムオフロードを無効になっている必要があります。

以下の内容を /etc/systemd/network/50-veth-disable-txcsum.link というファイルに保存します。

[Match]
Driver=veth

[Link]
TransmitChecksumOffload=no

上の設定を以下のコマンドでロードします。

sudo systemctl restart systemd-networkd

Dockerfile

Dockerfile.dpdk

以下のソフトウェアをダウンロードしてコンパイルおよびインストールします。

特に DPDK はコンパイル時にマシンごとに利用可能な CPU 命令などに合わせて最適化を行うため、コンパイル済みのプログラムをダウンロードするのではなく、ソースコードからビルドします。

FROM ubuntu:24.04

ENV TOPDIR=/
WORKDIR ${TOPDIR}

# DPDK
ENV DPDK_VER=25.11
ENV DPDK_DIR=${TOPDIR}/dpdk
ENV DPDK_SRC_DIR=${DPDK_DIR}/dpdk-${DPDK_VER}
ENV DPDK_BUILD_DIR=${DPDK_SRC_DIR}/build
ENV DPDK_INSTALL_DIR=${DPDK_DIR}/install
ENV DPDK_MESON_LIBDIR=lib/dpdk
RUN apt update; apt install -y gcc make meson python3-pyelftools libnuma-dev
ADD https://fast.dpdk.org/rel/dpdk-${DPDK_VER}.tar.xz ${DPDK_DIR}/
RUN tar xvf ${DPDK_SRC_DIR}.tar.xz -C ${DPDK_DIR}
RUN if [ "`uname -m`x" = "aarch64x" ]; then meson -Dplatform=generic --prefix=${DPDK_INSTALL_DIR} --libdir=${DPDK_MESON_LIBDIR} ${DPDK_BUILD_DIR} ${DPDK_SRC_DIR}; elif  [ "`uname -m`x" = "x86_64x" ]; then meson --prefix=${DPDK_INSTALL_DIR} --libdir=${DPDK_MESON_LIBDIR} ${DPDK_BUILD_DIR} ${DPDK_SRC_DIR}; else echo "unhandled CPU type: `uname -m`"; exit 1; fi
RUN ninja -C ${DPDK_BUILD_DIR}
RUN ninja -C ${DPDK_BUILD_DIR} install

# DPDK-compatible Open vSwitch
ENV OVS_VER=3.6.1
ENV OVS_DIR=${TOPDIR}/ovs
ENV OVS_SRC_DIR=${OVS_DIR}/ovs-${OVS_VER}
ENV OVS_INSTALL_DIR=${OVS_DIR}/install
RUN apt update; apt install -y autoconf libtool-bin pkg-config
ADD https://github.com/openvswitch/ovs/archive/refs/tags/v${OVS_VER}.tar.gz ${OVS_DIR}/
RUN tar xvf ${OVS_DIR}/v${OVS_VER}.tar.gz -C ${OVS_DIR}
RUN cd ${OVS_SRC_DIR}; ./boot.sh; export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:${DPDK_INSTALL_DIR}/${DPDK_MESON_LIBDIR}/pkgconfig; export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${DPDK_INSTALL_DIR}/${DPDK_MESON_LIBDIR}; ./configure --with-dpdk=static CFLAGS="-g -O3 -march=native" --prefix=${OVS_INSTALL_DIR} --localstatedir=${OVS_INSTALL_DIR}/var --sysconfdir=${OVS_INSTALL_DIR}/etc; make; make install

# benchmark tool using DPDK
RUN apt update; apt install -y pkg-config unzip
ADD https://github.com/yasukata/bench-iip/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv bench-iip-master bench-iip; rm master.zip
ADD https://github.com/yasukata/iip/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv iip-master ./bench-iip/iip; rm master.zip
ADD https://github.com/yasukata/iip-dpdk/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv iip-dpdk-master ./bench-iip/iip-dpdk; rm master.zip
RUN echo "" > ./bench-iip/iip-dpdk/build.mk
RUN cd bench-iip; CFLAGS=`PKG_CONFIG_PATH=${DPDK_INSTALL_DIR}/${DPDK_MESON_LIBDIR}/pkgconfig pkg-config --cflags libdpdk` LDFLAGS=`PKG_CONFIG_PATH=${DPDK_INSTALL_DIR}/${DPDK_MESON_LIBDIR}/pkgconfig pkg-config --libs libdpdk` IOSUB_DIR=./iip-dpdk make

# memcached-compatible server using DPDK
ADD https://github.com/yasukata/mimicached/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv mimicached-master mimicached; rm master.zip
ADD https://github.com/yasukata/memcached-protocol-parser/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv memcached-protocol-parser-master mimicached/memcached-protocol-parser; rm master.zip
RUN ln -s /bench-iip/iip /mimicached; cd mimicached; CFLAGS=`PKG_CONFIG_PATH=${DPDK_INSTALL_DIR}/${DPDK_MESON_LIBDIR}/pkgconfig pkg-config --cflags libdpdk` LDFLAGS=`PKG_CONFIG_PATH=${DPDK_INSTALL_DIR}/${DPDK_MESON_LIBDIR}/pkgconfig pkg-config --libs libdpdk` IOSUB_DIR=../bench-iip/iip-dpdk make

以下のコマンドでビルドします。

docker build -f Dockerfile.dpdk -t experiment-dpdk:1.0 .

Dockerfile.af_xdp

以下のソフトウェアをダウンロードしてコンパイルおよびインストールします。

FROM ubuntu:24.04

WORKDIR /

# benchmark tool using AF_XDP
RUN apt update; apt install -y unzip gcc make libnuma-dev libxdp-dev
ADD https://github.com/yasukata/bench-iip/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv bench-iip-master bench-iip; rm master.zip
ADD https://github.com/yasukata/iip/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv iip-master ./bench-iip/iip; rm master.zip
ADD https://github.com/yasukata/iip-af_xdp/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv iip-af_xdp-master ./bench-iip/iip-af_xdp; rm master.zip
RUN mv /bench-iip/iip-af_xdp/main.c /bench-iip/iip-af_xdp/_main.c; echo "#define helper_ip4_get_connection_affinity __helper_ip4_get_connection_affinity" > /bench-iip/iip-af_xdp/main.c; echo "#include \"_main.c\"" >> /bench-iip/iip-af_xdp/main.c; echo "#undef helper_ip4_get_connection_affinity" >> /bench-iip/iip-af_xdp/main.c; echo "static uint16_t helper_ip4_get_connection_affinity(uint16_t protocol, uint32_t local_ip4_be, uint16_t local_port_be, uint32_t peer_ip4_be, uint16_t peer_port_be, void *opaque) { return 0; (void) protocol; (void) local_ip4_be; (void) local_port_be; (void) peer_ip4_be; (void) peer_port_be; (void) opaque; (void) __helper_ip4_get_connection_affinity; }" >> /bench-iip/iip-af_xdp/main.c
RUN cd bench-iip; IOSUB_DIR=./iip-af_xdp make

# memcached-compatible server using AF_XDP
ADD https://github.com/yasukata/mimicached/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv mimicached-master mimicached; rm master.zip
ADD https://github.com/yasukata/memcached-protocol-parser/archive/refs/heads/master.zip ./
RUN unzip ./master.zip; mv memcached-protocol-parser-master mimicached/memcached-protocol-parser; rm master.zip
RUN ln -s /bench-iip/iip /mimicached; ln -s /bench-iip/iip-af_xdp /mimicached; cd mimicached; IOSUB_DIR=./iip-af_xdp make

以下のコマンドでビルドします。

docker build -f Dockerfile.af_xdp -t experiment-af_xdp:1.0 .

compose.yaml

compose.1.yaml
services:
  memcached:
    image: memcached:1.6.40-alpine3.23
    command:
      - --memory-limit=1024
      - --threads=1
  memtier_benchmark:
    depends_on:
      - memcached
    image: redislabs/memtier_benchmark:2.2.0
    command: -h memcached -p 11211 -P memcache_text -t 4 -c 64 --key-maximum 1 --ratio=0:1 --test-time=10

compose.2.yaml
services:
  memcached:
    image: memcached:1.6.40-alpine3.23
    command:
      - --memory-limit=1024
      - --threads=1
  client:
    depends_on:
      - memcached
    image: experiment-af_xdp:1.0
    cap_add:
      - SYS_NICE
      - SYS_ADMIN
      - NET_ADMIN
      - IPC_LOCK
      - BPF
    command: sh -c "/bench-iip/a.out -l 1 -i eth0 -- -s `getent hosts memcached | awk '{ print $1 }'` -p 11211 -m \"```echo -e 'get a\\r\\n\0'```\" -c 64"

compose.3.yaml
services:
  ovs:
    image: experiment-dpdk:1.0
    cap_add:
      - SYS_NICE
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - /dev/hugepages:/dev/hugepages
      - ./tmp-sock:/ovs/install/var/run/openvswitch
    command: sh -c "rm /ovs/install/var/run/openvswitch/br0.*; rm /ovs/install/var/run/openvswitch/db.sock; rm /ovs/install/var/run/openvswitch/ovs-vswitchd.*; rm /ovs/install/var/run/openvswitch/ovsdb-server.*; rm /ovs/install/var/run/openvswitch/vhost*; /ovs/install/share/openvswitch/scripts/ovs-ctl start; /ovs/install/bin/ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true; /ovs/install/share/openvswitch/scripts/ovs-ctl stop; /ovs/install/share/openvswitch/scripts/ovs-ctl start; /ovs/install/bin/ovs-vsctl del-br br0; ovs/install/bin/ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev; /ovs/install/bin/ovs-vsctl add-port br0 dport0 -- set Interface dport0 type=dpdkvhostuserclient options:vhost-server-path=/ovs/install/var/run/openvswitch/vhost0; /ovs/install/bin/ovs-vsctl add-port br0 dport1 -- set Interface dport1 type=dpdkvhostuserclient options:vhost-server-path=/ovs/install/var/run/openvswitch/vhost1; /ovs/install/bin/ovs-vsctl show; tail -f /dev/null"
  mimicached:
    depends_on:
      - ovs
    image: experiment-dpdk:1.0
    cap_add:
      - SYS_NICE
      - IPC_LOCK
    volumes:
      - /dev/hugepages:/dev/hugepages
      - ./tmp-sock:/var/run/dpdk-app
    command: sh -c "sleep 2; LD_LIBRARY_PATH=/dpdk/install/lib/dpdk /mimicached/a.out -l 1 --proc-type=primary --file-prefix=pmd1 --vdev=net_virtio_user0,path=/var/run/dpdk-app/vhost0,server=1 --no-pci --single-file-segments -- -a 0,10.100.0.20 -e 0 -- -p 11211 -m 10000 -z 100000"
  client:
    depends_on:
      - ovs
    image: experiment-dpdk:1.0
    cap_add:
      - SYS_NICE
      - IPC_LOCK
    volumes:
      - /dev/hugepages:/dev/hugepages
      - ./tmp-sock:/var/run/dpdk-app
    command: sh -c "sleep 2; LD_LIBRARY_PATH=/dpdk/install/lib/dpdk /bench-iip/a.out -l 2 --proc-type=primary --file-prefix=pmd2 --vdev=net_virtio_user1,path=/var/run/dpdk-app/vhost1,server=1 --no-pci --single-file-segments -- -a 0,10.100.0.10 -e 0 -- -s 10.100.0.20 -p 11211 -m \"```echo -e 'get a\\r\\n\0'```\" -c 64"
    tty: true

compose.4.yaml
services:
  mimicached:
    image: experiment-af_xdp:1.0
    cap_add:
      - SYS_NICE
      - SYS_ADMIN
      - NET_ADMIN
      - IPC_LOCK
      - BPF
    command: sh -c "/mimicached/a.out -l 0 -i eth0 -- -p 11211 -m 1024 -z 100000"
  memtier_benchmark:
    depends_on:
      - mimicached
    image: redislabs/memtier_benchmark:2.2.0
    command: -h mimicached -p 11211 -P memcache_text -t 4 -c 64 --key-maximum 1 --ratio=0:1 --test-time=10

compose.5.yaml
services:
  mimicached:
    image: experiment-af_xdp:1.0
    cap_add:
      - SYS_NICE
      - SYS_ADMIN
      - NET_ADMIN
      - IPC_LOCK
      - BPF
    command: sh -c "/mimicached/a.out -l 0 -i eth0 -- -p 11211 -m 1024 -z 100000"
  client:
    depends_on:
      - mimicached
    image: experiment-af_xdp:1.0
    cap_add:
      - SYS_NICE
      - SYS_ADMIN
      - NET_ADMIN
      - IPC_LOCK
      - BPF
    command: sh -c "/bench-iip/a.out -l 1 -i eth0 -- -s `getent hosts mimicached | awk '{ print $1 }'` -p 11211 -m \"```echo -e 'get a\\r\\n\0'```\" -c 64"

実行は以下のコマンドで行います。実行時には以下の FILE の箇所を compose.1.yaml のようにファイル名と置き換えます。

docker compose -f FILE up

まとめ

Docker 環境で、標準的なコンテナ用ネットワーク構成を大幅に変更しないまま、DPDK と AF_XDP を利用したアプリケーションが仮想 NIC を扱う場合の性能について簡単な計測を行いました。




以上の内容はhttps://yasukata.hatenablog.com/entry/2026/01/13/082346より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14