侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P3-02 · OVS-DPDK 调优实战:pmd-cpu-mask 与 tx-flush-interval

PacketForge
2026-08-18 / 0 评论 / 0 阅读 /
对应路线图:Phase 3 (Day 92-119) | DPDK 24.11.6 LTS | 内核对照 v6.6
上一篇:P3-01 OVS-DPDK 数据面 | 下一篇:P3-03 SONiC 控制面链路

一句话定位

OVS-DPDK 的性能不靠改代码,靠把线程钉对核、把发送攒够批:pmd-cpu-mask 决定"谁在哪个核上轮询",tx-flush-interval 决定"攒多少包再发一次"。本篇没有独立源码文件——它的"源码"是配置项语义 + 上一节读过的数据面代码,我如实标注出处。

pmd-cpu-mask:PMD 线程的出生证明

官方语义定义在 vswitchd/vswitch.xml:375-392(节选):

      <column name="other_config" key="pmd-cpu-mask">
        <p>
          Specifies CPU mask for setting the cpu affinity of PMD (Poll
          Mode Driver) threads.  Value should be in the form of hex string,
          similar to the dpdk EAL '-c COREMASK' option input or the 'taskset'
          mask input. The lowest order bit corresponds to the first CPU core.
          A set bit means the corresponding core is available and a pmd thread
          will be created and pinned to it. ...
        </p>
        <p>
          If not specified, one pmd thread will be created for each numa node
          and pinned to any available core on the numa node by default.
        </p>
      </column>

要点:mask 的每一位 = 一个 PMD 线程。0x6(二进制 0110)就是 core 1、2 各起一个 PMD;不指定则每 NUMA node 只给一个 PMD——多队列网卡会严重饿死,这是最常见的性能坑。

数据面依据:PMD 线程创建时按 core_id 绑核(dpif-netdev.c:6033-6034):

    ovs_numa_thread_setaffinity_core(pmd->core_id);
    dpdk_attached = dpdk_attach_thread(pmd->core_id);

每个 PMD 在 pmd_load_queues_and_ports(dpif-netdev.c:6035)拿到自己的 poll_list,主循环只轮询自己队列组——所以 PMD 数量和 RXQ 数量必须匹配(tests/pmd.at:137 有专门的分配测试用例)。

绑定纪律(Phase 1 P1-06 学过的规矩在这里兑现):PMD 核别给 vswitchd 主线程(vswitch.xml:363-367 警告多线程共享单核的后果);用 isolcpus 或 systemd AllowedCPUs 隔离;PMD 所在 socket 必须与它轮询的 NIC 一致,跨 socket 内存带宽腰斩。

tx-flush-interval:用一点延迟换吞吐

pmd-cpu-mask 与 tx-flush 两个调优旋钮

语义与效果(Documentation/intro/install/dpdk.rst:647-669):

The ``tx-flush-interval`` parameter can be used to specify the time in
microseconds OVS should wait between two send bursts to a given port (default
is ``0``). When the intermediate queue fills up before that time is over, the
buffered packet batch is sent immediately::

    $ ovs-vsctl set Open_vSwitch . other_config:tx-flush-interval=50
...
Low traffic (``packet rate < 1 / tx-flush-interval``) should not experience
any significant latency or throughput increase as packets are forwarded
immediately.

At intermediate load levels
(``1 / tx-flush-interval < packet rate < 32 / tx-flush-interval``) traffic
should experience an average latency increase of up to
``1 / 2 * tx-flush-interval`` and a possible throughput improvement.

Very high traffic (``packet rate >> 32 / tx-flush-interval``) should experience
the average latency increase equal to ``32 / (2 * packet rate)``. Most send
batches in this case will contain the maximum number of packets (``32``).

翻译成白话:TX 侧引入一个攒批窗口。窗口期内收到的包先堆在 tx_port 的输出队列,攒到一批 32 个(NETDEV_MAX_BURST)或窗口到期才调 rte_eth_tx_burst。低流量零影响(没包可攒,直接发);中流量每个包平均多等 1/2 * interval;高流量攒满 32 个才发,延迟上界 32/(2*rate)。官方给的经验值:PHY-VM-PHY 场景 50us 收益明显(dpdk.rst:671-674)。

数据面依据:flush_time 与 force

tx-flush-interval 进 dp->tx_flush_interval 原子变量(dpif-netdev.c:284,默认值 DEFAULT_TX_FLUSH_INTERVAL = 0,dpif-netdev.c:106;配置解析在 dpif-netdev.c:3902-3906)。PMD 把包排进某端口的输出队列时设置到期时间(dpif-netdev.c:4391-4392):

    atomic_read_relaxed(&pmd->dp->tx_flush_interval, &tx_flush_interval);
    // ...
    p->flush_time = pmd->ctx.now + tx_flush_interval;

真正发出去的是 dp_netdev_pmd_flush_output_packets(dpif-netdev.c:4414-4431):

    HMAP_FOR_EACH (p, node, &pmd->send_port_cache) {
        if (!dp_packet_batch_is_empty(&p->output_pkts)
            && (force || pmd->ctx.now >= p->flush_time)) {
            output_cnt += dp_netdev_pmd_flush_output_on_port(pmd, p);
        }
    }

要么 force、要么到期。force 的两个时机:本轮收到包后立即 flush(dpif-netdev.c:4483);主循环发现本轮无包、准备睡 PMD 前强制 flush(dpif-netdev.c:6119-6126,max_sleep && sleep_time ? true : false)——绝不让包在输出队列里陪着 PMD 睡觉。

底层仍是批量发送(netdev-dpdk.c:3394-3418)

netdev_dpdk_eth_send(struct netdev *netdev, int qid,
                     struct dp_packet_batch *batch, bool concurrent_txq)
{
    struct rte_mbuf **pkts = (struct rte_mbuf **) batch->packets;
    // ...
    cnt = netdev_dpdk_common_send(netdev, batch, &stats);

    dropped = netdev_dpdk_eth_tx_burst(dev, qid, pkts, cnt);

攒批的意义就在 rte_eth_tx_burst 的批量摊薄:一次发送 32 个包和发 1 个包的 MMIO/描述符写回成本差不了太多——批越大,每包成本越低。这就是 tx-flush-interval 用延迟换吞吐的微观机制。

🐧 内核对照:内核怎么攒批

环节内核网络栈OVS-DPDK
攒批机制NAPI budget 收一批 + qdisc 自然攒包TX 侧显式 tx_flush_interval 攒批窗口
触发发送qdisc 出队(net_tx_action 软中断)flush_time 到期或 force(dpif-netdev.c:4426)
低流量中断驱动,来包就发无包可攒,立即发(dpdk.rst:658-660)
延迟模型qdisc 排队延迟 + BQL平均 1/2 * interval(dpdk.rst:662-665)
每包成本skb 构造 + qdisc 锁mbuf 批量 rte_eth_tx_burst

内核靠中断 + qdisc 天然批量,代价是每包软中断和 skb 构造;DPDK 主动让出微秒级延迟换纯批量发送。思路一致:批量摊薄固定成本。

面试 3 问

  1. pmd-cpu-mask=0x6 会创建几个 PMD 线程? → 2 个(core 1、2),最低位对应 core 0(vswitch.xml:383-386);不设置则每 NUMA node 默认只有 1 个 PMD(vswitch.xml:389-391)。
  2. tx-flush-interval 为什么能提吞吐? → 攒批到 32 个再 rte_eth_tx_burst,摊薄每次发送的固定开销;代价是中流量每包平均多等 1/2 * interval(dpdk.rst:662-665)。
  3. PMD 要睡之前,输出队列里的包怎么办? → 主循环强制 force=true flush(dpif-netdev.c:6124-6126),包不会陪着 PMD 睡 max_sleep。

🔧 动手实验

# 1. 两个 PMD 核 + 两个隔离核
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=0x18
# 2. 验证队列分配(每个 PMD 该看到一半 RXQ)
ovs-appctl dpif-netdev/pmd-rxq-show
# 3. 打流对比 tx-flush-interval=0 vs 50 的 PPS/延迟
ovs-vsctl set Open_vSwitch . other_config:tx-flush-interval=50
# 4. 观察攒批效果
ovs-appctl dpif-netdev/pmd-stats-show
ovs-appctl dpif-netdev/pmd-perf-show   # tx batches 直方图

产出 perf-data/P3/02-pmd-tune.md:不同 PMD 核数 × tx-flush-interval 的 PPS/平均延迟矩阵。


下一篇:P3-03 · SONiC 控制面链路:APPL_DB → ASIC_DB → syncd

0

评论 (0)

取消