iperf3性能热点函数sk_dst_check问题分析

1. 问题场景

使用iperf3 做udp性能压测时候,

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

2. 问题结论

2.1. 结论

多核性能瓶颈:dst路由锁, 限制了线性增长。4核以上,线性增长明显下跌。

2.1.1. 触发条件

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

2.1.2. 简要分析

  1. udp connect后, 内核会根据路由表查找结果,缓存路由结果nhc
  2. 这个路由结果是per cpu的,因此执行了iperf所在cpu A上。
  3. 多线程创建完成后,内核后自动调度,把线程放到CPU B上运行,B上使用的udp socket的缓存还是执行CPU A下缓存的结果。
  4. 结果:这样多个线程使用多个udp socet, 都使用了CPU A下缓存的结果。
  5. 因此每个报文发送时候, 内核调用sk_dst_check, 造成多cpu间争抢路由的atomic操作。

sk_dst_check及配套函数:

  1. udp发包时候,发送前atk_dst_check检查缓存的之前查询路由nhc
  2. udp发包时候,每包查询路由,并把缓存的路由atomic,
  3. 多进程发atomic+, 发送后atomic-,触发性能瓶颈。
  4. 6.4版本已经做过了优化,使用rcuref_get降低ref的代价,性能仍然有待提高。

2.1.3. 临时规避

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

1
ip route flush cache

可以避免16个cpu争抢一个的问题, 但是因为各个线程是会被cpu在多个核直接来回调度的,
因此会造成, 当前CPU上,重新查找完,使用本cpu的cache。
线程会被调度到其他cpu上, 仍然使用上一轮cpu的cache。
不是非常完美, 但是可以规避这个危害。

3. 详细测试

3.1. 测试环境:

  • 内核:
    Linux 6.6 (No multipath routing, no policy routing/rules, no netfilter/conntrack involved)
  • 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.7G 稳定,RX侧0丢包
  3. client端:SI消耗单核cpu的35%, **iperf3****消耗100%。**
  4. server端:SI 消耗单核cpu的17%,iperfi消耗35%。未到瓶颈 

3.2.2. 16线程发送: -P16

  1. 性能在20G左右, 有抖动。
  2. client侧发送cpu 100%, SI有冲突,最高接近50%,不是瓶颈。
    1. sk_dst_check: 9.29%, 占比继续上升。
    2. 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单独放到X里讲述。

4.2. 函数:sk_dst_check

核心问题不在sk_dst_check,而在于check时,会通过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操作替换成了rcuf操作。
但是在这个测试场景下, 使用10G以上带宽的流量,cpu争抢rcf,依然会占用10%的消耗。
rcuf为什么还这么大消耗,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>

iperf3性能热点函数sk_dst_check问题分析
https://martinbj2008.github.io/2026/09/30/iperf3-hot-function-sk-dst-check/
Author
Martinbj2008
Posted on
September 30, 2026
Licensed under