sk_dst_check引发的iperf3性能衰退

1. 问题场景

使用 iperf3 做 UDP 性能压测时:

  • 单核 CPU 可以发送出大约 2.7 Gbps 的 UDP 报文。
  • 使用 -P4 后,单核性能不足 2.4 Gbps,整体只有 9.44 Gbps,约有 10% 的衰退。
  • 使用 -P16 后,单核性能不足 1.3 Gbps,整体只有 19.8 Gbps,约有 50% 的衰退,问题严重。

注:CPU数量足够, 64核单NUMA

2. 问题结论

2.1. 触发条件

  • -P 参数会创建多个 UDP socket。
  • 这些 socket 都在 iperf3 运行的 CPU 上执行了 UDP connect。
  • 之后创建多个线程,每个线程使用一个 UDP socket 发包。

2.2. 结论

多核性能瓶颈

  • **发送udp报文时,sk_dst_check的引用计数操作引发多cpu争抢。
  • 引用计数(atomic/rcuref)的原子操作

导致:

  • 4 核以上线性增长明显下跌

2.2.1. 问题分析

过程分析

  1. UDP connect 后,内核会根据路由表的查找结果,缓存路由项 nhc(dst_entry)。
  2. 这个路由缓存是per-CPU的,被缓存在iperf所在的CPU A上。
  3. 多线程创建完成后,内核会自动调度,将线程放到其他CPU上运行;而UDP socket仍引用的是CPU A上缓存的rtable路由。
  4. 因此每个报文发送时,内核调用 sk_dst_check,造成多 CPU 之间争抢路由引用计数的 atomic 操作。

sk_dst_check 及配套函数:

  1. UDP 发包时,发送前通过 sk_dst_check 检查缓存的路由 nhc。
  2. UDP 发包时,每包查询路由,并对缓存的路由引用做 atomic 操作。
  3. 多进程发送时 atomic 加一,发送后 atomic 减一,从而触发性能瓶颈。
  4. 6.4 版本已做过优化,使用 rcuref_get 降低 ref 的代价,但性能仍有待提高。

2.2.2. 临时规避

使用 ip 命令清理掉缓存的路由结果,强制 socket 重新查找路由。

1
ip route flush cache

内核重新查找完路由后,会创建并使用当前CPU上的缓存;
这样可以避免16个CPU争抢同一个缓存的问题。

由于线程后续还可能被调度其他cpu上,
一旦线程被调度到其他CPU上,仍会使用上一轮CPU的缓存。
该方案并不完美,只能一定程度降低多核争抢的危害。

3. 问题复现

3.1. 复现环境

  • 内核:Linux 6.6(无多路径路由、无策略路由/规则、未涉及 netfilter/conntrack)
  • iperf3:iperf 3.21(cJSON 1.7.15,compiled with pthreads support)

3.2. 对比测试

3.2.1. 单线程发送 UDP

  1. 测试命令:iperf3 -u -c 1.1.1.37 -b 4G -t 120
  2. 性能:2.7 Gbps 稳定,RX 侧 0 丢包。
  3. client 端:SI 消耗单核 CPU 的 35%,iperf3 消耗 100%。
  4. server 端:SI 消耗单核 CPU 的 17%,iperf3 消耗 35%,未到瓶颈。

3.2.2. 16 线程发送(-P16)

  1. 性能在 20 Gbps 左右,有抖动。
  2. client 侧发送 CPU 100%,SI 有冲突,最高接近 50%,不是瓶颈。
    • sk_dst_check:9.29%,占比继续上升。
    • dst_release:5.68%
  3. server 侧:SI 最高 70%+,分散比 TX 侧队列稍好(9 个队列)。

3.2.3. 热点函数:sk_dst_check

1
2
3
4
5
+    9.84%     1.00%  [kernel]                 [k] __ip_append_data
+ 7.83% 7.83% [kernel] [k] sk_dst_check
+ 7.45% 0.15% [kernel] [k] handle_softirqs
+ 7.26% 0.01% [kernel] [k] net_rx_action
+ 7.25% 0.00% [kernel] [k] napi_poll

4. 问题分析

4.1. udp connect

udp connect 的细节将单独在后续章节(待补充)中讲述。

4.2. 函数:sk_dst_check

核心问题不在 sk_dst_check 本身,而在于 check 时会通过 sk_dst_get 获取一次 dst/rtable 这个结构体的引用。

1
2
3
4
5
6
 613 struct dst_entry *sk_dst_check(struct sock *sk, u32 cookie)
614 {
615 struct dst_entry *dst = sk_dst_get(sk);
...
625 return dst;
626 }

4.3. 函数:sk_dst_get

1
2
3
4
5
6
7
8
9
10
11
12
2149 static inline struct dst_entry *
2150 sk_dst_get(struct sock *sk)
2151 {
2152 struct dst_entry *dst;
2153
2154 rcu_read_lock();
2155 dst = rcu_dereference(sk->sk_dst_cache);
2156 if (dst && !rcuref_get(&dst->__rcuref))
2157 dst = NULL;
2158 rcu_read_unlock();
2159 return dst;
2160 }

4.4. 函数:dst_release

1
2
3
4
5
6
164 void dst_release(struct dst_entry *dst)
165 {
166 if (dst && rcuref_put(&dst->__rcuref))
167 call_rcu_hurry(&dst->rcu_head, dst_destroy_rcu);
168 }
169 EXPORT_SYMBOL(dst_release);

4.5. 内核优化

内核在 6.4 版本中已包含一个优化,将 atomic 操作替换成了 rcuref 操作。但在本测试场景下,使用 10 Gbps 以上带宽的流量时,CPU 争抢 rcuref 依然会占用约 10% 的开销。rcuref 为何仍有如此大的消耗,TODO。

4.5.1. patch: bc9d3a9f2afca

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=bc9d3a9f2afca189a6ae40225b6985e3c775375e

4.5.2. patch 核心改动

1
2
3
4
5
6
7
8
@@ -75,16 +76,16 @@ struct dst_entry {
__u32 tclassid;
#ifndef CONFIG_64BIT
struct lwtunnel_state *lwtstate;
- atomic_t __refcnt; /* 32-bit offset 64 */
+ rcuref_t __rcuref; /* 32-bit offset 64 */
#endif
netdevice_tracker dev_tracker;

4.5.3. patch 描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
net: dst: Switch to rcuref_t reference counting
Under high contention dst_entry::__refcnt becomes a significant bottleneck.

atomic_inc_not_zero() is implemented with a cmpxchg() loop, which goes into
high retry rates on contention.

Switch the reference count to rcuref_t which results in a significant
performance gain. Rename the reference count member to __rcuref to reflect
the change.

The gain depends on the micro-architecture and the number of concurrent
operations and has been measured in the range of +25% to +130% with a
localhost memtier/memcached benchmark which amplifies the problem
massively.

Running the memtier/memcached benchmark over a real (1Gb) network
connection the conversion on top of the false sharing fix for struct
dst_entry::__refcnt results in a total gain in the 2%-5% range over the
upstream baseline.

Reported-by: Wangyang Guo <wangyang.guo@intel.com>
Reported-by: Arjan Van De Ven <arjan.van.de.ven@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20230307125538.989175656@linutronix.de
Link: https://lore.kernel.org/r/20230323102800.215027837@linutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

sk_dst_check引发的iperf3性能衰退
https://martinbj2008.github.io/2026/09/30/iperf3-hot-function-sk-dst-check/
Author
Martinbj2008
Posted on
September 30, 2026
Licensed under