关注了就能看到更多这么棒的文章哦~
Smarter IRQ suspension in the networking stack
By Jonathan Corbet
February 11, 2025
Gemini-1.5-flash translation
https://lwn.net/Articles/1008399/
实现高性能网络,非常依赖深度的调优工作;处理每个数据包的时间可能以纳秒(nanoseconds)来衡量,因此必须小心避免任何可能降低处理速度的操作。最近,一个合并到 6.13 版本的补丁集受到了相当多的关注,据称,它可以将数据中心的处理效率(进而也实现功耗节省)提高多达 30%。这个由 Joe Damato 和 Martin Karsten 贡献的更改,是对现有优化技术的一个相对较小的调整;它表明了优化高带宽服务器需要多么的细致。
在一个简单的配置中,网络接口会接收一个数据包,将其放置在内存(RAM)的某个位置,然后中断中央处理器(CPU),通知内核数据包已准备好进行处理。当网络速度较慢时,这种方式效果很好,但在当前的数据中心中,这显然不是最佳选择。年长的读者会记得,过去每封电子邮件都很重要,所以我们配置系统在收到消息时发出蜂鸣声(中断)提醒。但是这种中断是有代价的,所以我们现在不再这样做了。相反,我们会偶尔检查电子邮件,因为我们知道有大量新的网络钓鱼垃圾邮件在等着我们。
内核的网络协议栈(networking stack)长期以来也采用了类似的方法。当流量很大时,内核会告诉网络接口停止中断。相反,内核会偶尔轮询(poll)网络接口,以赶上基本上确定已经在等待处理的新数据包。以这种方式屏蔽中断,允许内核批量处理数据包,而不会被中断;这有助于提高吞吐量(throughput)。
用户空间轮询
尽管如此,仍然有进一步改进的空间。默认情况下,内核在软件中断(software-interrupt)例程中执行此轮询,该例程与使用传入数据的应用程序异步运行。中断处理程序和应用程序可能会经常同时运行;由于它们都在处理相同的网络流,因此可能导致锁竞争和缓存未命中(cache misses)。如果处理一个数据包的时间预算是以纳秒来衡量的,那么即使是一个缓存未能命中也可能导致超出时间预算。
为了解决这个问题(这个问题只影响负载最重的服务器,但是这类服务器有很多),轮询的责任可以一直推到用户空间(user space)。如果应用程序选择了一种特殊的“希望进行持续轮询”(preferred busy polling)模式,它会向内核郑重承诺,它将经常轮询传入的网络流并处理已到达的数据包。内核将通过关闭自己的基于软件中断的数据包处理来响应。相反,这种处理可以在应用程序轮询时完成,这样它就不会与应用程序的用户空间处理竞争。这种轮询可以产生极小的数据包处理延迟,但也会增加中央处理器的使用率,尤其是在没有数据包等待时,应用程序会消耗中央处理器的资源进行轮询,但却找不到任何工作可做。
为了最大限度地减少中央处理器的使用率问题(并可能允许中央处理器在空闲时进入低功耗状态),系统可以返回到中断驱动模式。如果不清楚下一个数据包何时到达,内核可以简单地停止轮询,导致应用程序阻塞(block),并请求中断。这里有一个权衡:在一个适度繁忙的系统中,很有可能在切换到中断驱动模式后立即收到一个数据包。通常,最好等待一段时间再这样做。
为此,网络协议栈有两个参数,可供高性能应用程序自行调整。 napi_defer_hard_irqs
是在应用程序被阻塞并启用中断之前,允许应用程序在没有接收到任何数据的情况下轮询的次数;这将使系统在传入数据包流中的微小间隙中保持在轮询模式。然而,即使经过这么多次尝试,也不会立即启用中断;因为这会导致在第一个数据包到达时产生中断,而此时中断处理程序几乎没有工作要做。最好等待更长的时间让流量累积。因此,另一个参数 gro_flush_timeout
告诉内核在重新启用数据包接收中断之前应该等待多长时间(以纳秒为单位)。
gro_flush_timeout
旋钮(knob)还具有第二个功能:它是一种安全因素,用于指定应用程序应至少执行一次轮询的时间段。如果在这个超时(timeout)周期到期之前没有发生轮询,内核会认为应用程序已经分心并忘记了保持轮询的承诺;然后,它会重新启动软件中断处理,将轮询责任重新掌握在自己手中。
一个新的旋钮
gro_flush_timeout
的这种双重角色是新补丁集解决的问题的根源。它的值设置了轮询停止时响应延迟的下限;如果将其设置为过大的值,则在较慢的期间内,响应时间将会受到影响。暂停以累积流量有利于提高吞吐量,但是暂停太长时间会产生延迟。相反,如果将此值设置得太小,则超时会在应用程序处理数据包时触发;这将导致软件中断处理同时发生,从而影响性能。通常没有一个值可以完美地兼顾这两种角色。
答案是通过引入另一个旋钮来分离这些角色: irq_suspend_timeout
,它也以纳秒为单位指定。当应用程序以首选忙轮询模式运行并接收数据时, irq_suspend_timeout
的值将用于确定内核应等待应用程序的下一次轮询多长时间,然后才能得出必须恢复软件中断处理的结论,而不是使用 gro_flush_timeout
。每次应用程序轮询更多数据并成功检索更多数据进行处理时,都会重置此超时。
当轮询返回而没有找到任何数据时,状态会发生变化;此时,内核会恢复到较旧的模式,允许 napi_defer_hard_irqs
进行空轮询,然后再启动 gro_flush_timeout
延迟,然后重新启用中断。换句话说,新的超时仅在数据包持续到达时才适用。
这种机制允许将 irq_suspend_timeout
设置为一个相对较长的值,因为它仅在繁忙时段(应用程序正在主动处理数据时)适用。同时, gro_flush_timeout
(仅在传入流量中出现暂停时适用)可以设置为一个相对较短的值,这样一旦新数据到达,处理将迅速重新开始。承诺的结果是,在高流量时具有高吞吐量,而在流量减少时具有低延迟,同时还允许中央处理器在这些较慢的时段内进入睡眠状态(或执行其他工作)。
补丁集中包含的基准测试结果似乎支持了这一承诺。当在新模式下运行时,系统能够提供一致的(且相对较低的)延迟,就像它在完全忙等待模式下运行一样,但是中央处理器的利用率更接近于完全中断延迟的情况。这就是节省电力的说法来源;服务器能够提供所需的服务水平,而不会浪费大量的中央处理器时间来进行争用或进行忙等待。显然,这一更改可以消除用户空间网络解决方案相对于内核的大部分性能优势。
显然,这个新的旋钮不是大多数用户(甚至是运行服务器的用户)想要摆弄的东西。启用首选忙轮询是一种平衡行为,需要大量注意力来找到相关参数的正确值,并且需要持续监控以确保系统以最佳方式运行。添加一个新的旋钮会使事情变得更加复杂。但是,对于运行着难以想象数量的服务器并试图从每个服务器中获得尽可能多的性能的组织来说,对网络协议栈的这种相对简单的调整可能会产生巨大的影响。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~