关于网络:iouring-vs-epoll-谁在网络编程领域更胜一筹

62次阅读

共计 4870 个字符,预计需要花费 13 分钟才能阅读完成。

简介:从定量分析的角度,通过量化 io_uring 和 epoll 两种编程框架下的相干操作的耗时,来剖析二者的性能差别。

本文作者:王小光,「高性能存储技术 SIG」核心成员。

背景

io_uring 在传统存储 io 场景曾经证实其价值,但 io_uring 不仅反对传统存储 io,也反对网络 io。io_uring 社区有泛滥的开发者尝试将 io_uring 用于网络应用。咱们之前也在《你认为 io_uring 只实用于存储 IO?大错特错!》中也摸索过 io_uring 在网络场景的利用及其与传统网络编程基石 epoll 的比照,过后咱们的测试结果显示在 cpu 破绽缓解使能的前提下,io_uring 相比于 epoll 能够带来肯定的劣势,在 cpu 漏铜缓解未使能时,io_uring 相比于 epoll 没有劣势,可能还会存在性能降落。

在 io_uring 社区,对于 io_uring 和 epoll 孰优孰劣也始终存在争执,有些开发者声称 io_uring 能够取得比 epoll 更好的性能,有些开发者则声称二者性能持平或者 io_uring 甚至不如 epoll。相干的探讨十分多,具体可参见如下两例:

https://github.com/axboe/libu…

https://github.com/frevib/io_…

以上探讨从 2020 年 8 月始终继续到当初,其过程十分长也十分地强烈。能够看出 io_uring 和 epoll 在网络编程畛域孰优孰劣目前的确比拟难以达成共识。

目前很多业务想将 io_uring 在网络场景利用起来,但 io_uring 是否能比 epoll 带来性能晋升,大家或多或少存在些许疑难。为了彻底厘清这个问题,龙蜥社区高性能存储 SIG 尝试从定量分析的角度,通过量化 io_uring 和 epoll 两种编程框架下的相干操作的耗时,来剖析二者的性能差别。

评估模型

咱们依然选用 echo server 模型进行性能评估,server 端采纳单线程模型,同时为偏心比照,io_uring 不应用外部的 io-wq 机制(io_uring 在内核态保护的线程池,能够用来执行用户提交的 io 申请)。epoll 采纳 send(2) 和 recv(2) 进行数据的读写操作;而 io_uring 采纳 IORING_OP_SEND 和 IORING_OP_RECV 进行数据的读写操作。

联合 echo server 的模型,咱们剖析有四个因素会影响 io_uring 和 epoll 的性能,别离是:

1、零碎调用用户态到内核态上下文切换开销,记为 s;

2、零碎调用本身内核态工作逻辑开销,记为 w;

3、io_uring 框架自身开销,记为 o;

4、io_uring 的 batch 量,记为 n,epoll 版 echo server 因为间接调用 recv(2) 和 send(2), 其 batch 理论为 1。

同时在本文中咱们仅评估 io_uring 和 epoll 申请读写操作的开销,对于 io_uring 和 epoll 自身的事件告诉机制自身不做掂量,因为通过 perf 工具剖析,读写申请自身开销占据绝大部分。零碎调用用户态到内核态上下文切换开销能够通过专门的程序进行测量,因素 2、3、4 等能够通过掂量内核相干函数的执行工夫进行测量,用 bpftrace 进行剖析。

epoll 版 echo server 开销度量

从用户态视角,send(2) 或者 recv(2) 开销次要蕴含两个方面,零碎调用用户态到内核态上下文切换开销和零碎调用本身内核态工作逻辑开销,其中零碎调用自身工作逻辑的开销,send(2) 和 recv(2) 别离掂量 sys_sendto(), sys_recvfrom() 即可。

因为 epoll 场景下其零碎调用的 batch 为 1,因而 epoll 模型下收发申请的均匀耗时为 (s + w)。

io_uring 版 echo server 开销度量

io_uring 中 io_uring_enter(2) 零碎调用既能够用来提交 sqe,也能够用来 reap cqe,两种操作混合在一个零碎调用中,精确掂量 sqe 的提交收发申请的耗时比拟艰难。简略起见,咱们采纳跟踪 io_submit_sqes() 的开销来掂量 IORING_OP_SEND 和 IORING_OP_RECV 的开销,此函数被 io_uring_enter(2) 所调用。io_submit_sqes() 蕴含 send(2) 和 revc(2) 内核侧工作逻辑开销,及 io_uring 框架的开销,记为 t。

同时咱们采纳 io_uring 的 multi-shot 模式,从而确保 io_submit_sqes() 中的提交的 IORING_OP_SEND 和 IORING_OP_RECV 申请都能够间接实现,而不会用到 io_uring 的 task-work 机制。

因为 io_uring 场景下能够 batch 零碎调用的执行,因而 io_uirng 模型下收发申请的均匀耗时为 (s + t) / n。

理论度量

咱们测试环境 Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz,掂量 echo server 单链接性能数据。

用户态内核态零碎调用上下文切换开销

cpu 破绽对系统调用用户态内核态上下文切换的影响比拟大,在咱们的测试环境中:漏铜缓解使能时,零碎调用的上下文切换开销为 700ns 左右;漏铜缓解未使能时,零碎调用的上下文切换开销为 230ns 左右。

epoll 模型下 send(2)/recv(2) 内核侧开销

采纳 bpftrace 脚本别离掂量 sys_sendto(),sys_recvfrom() 即可。bpftrace 脚本如下:

BEGIN
{
  @start = 0;
  @send_time = 0;
  @send_count = 0;
}
kprobe:__sys_sendto
/comm == "epoll_echo_serv"/
{@start = nsecs;}
kprobe:__sys_recvfrom
/comm == "epoll_echo_serv"/
{@start = nsecs;}
kretprobe:__sys_sendto
/comm == "epoll_echo_serv"/
{if (@start > 0) {
    @delay = nsecs - @start;
    @send_time = @delay + @send_time;
    @send_count = @send_count + 1;
  }
}
kretprobe:__sys_recvfrom
/comm == "epoll_echo_serv"/
{if (@start > 0) {
    @delay = nsecs - @start;
    @send_time = @delay + @send_time;
    @send_count = @send_count + 1;
  }
}
interval:s:5
{printf("time: %llu\n", @send_time / @send_count);
  @send_time = 0;
  @send_count = 0;
}

在单连贯,包大小 16 字节场景下,epoll 版的 echo_server 的 tps 在 1000 左右,其 recv(2) 和 send(2) 的内核侧逻辑均匀开销如下:

time: 1489、time: 1492、time: 1484、time: 1491、time: 1499、time: 1505、time: 1512、time: 1528、time: 1493、time: 1509、time: 1495、time: 1499、time: 1544

从上述数据能够看出,send(2) 和 recv(2) 的内核侧均匀开销在 1500ns 左右,因而:

1)cpu 破绽缓解,send(2) 和 recv(2) 的均匀开销为 s=700ns,w=1500ns,总共 (s+w) = 2200ns。

2)cpu 破绽为缓解,send(2) 和 recv(2) 的均匀开销为 s=230ns,w=1500ns,总共 (s+w) = 1730ns。

io_uring 模型下 io_uring_enter(2) 内核侧开销

采纳 bpftrace 脚本别离掂量 io_submit_sqes() 开销即可。

BEGIN
{
  @start = 0;
  @send_time = 0;
  @send_count = 0;
}
kprobe:io_submit_sqes
/comm == "io_uring_echo_s"/
{
  @start = nsecs;
  @send_count = @send_count + arg1;
}
kretprobe:io_submit_sqes
/comm == "io_uring_echo_s"/
{if (@start > 0) {
    @delay = nsecs - @start;
    @send_time = @delay + @send_time;
  }
}
interval:s:5
{printf("time: %llu\n", @send_time / @send_count);
  @send_time = 0;
  @send_count = 0;
}

运行相似上述 epoll 同样的测试,数据为:

time: 1892、time: 1901、time: 1901、time: 1882、time: 1890、time: 1936、time: 1960、time: 1907、time: 1896、time: 1897、time: 1911、time: 1897、time: 1891、time: 1893、time: 1918、time: 1895、time: 1885

从上述数据能够看出,io_submit_sqes() 的内核侧均匀开销在 1900ns 左右,留神此时的 batch n=1,且该开销包含收发申请的内核态工作逻辑开销及 io_uring 框架开销。

1)cpu 破绽缓解,用户态察看到的 io_uring_enter(2) 均匀开销为 t=1900ns,n=1,s=700ns,总共 (t+s) / n = 2600ns。

2)cpu 破绽未缓解,用户态察看到的 io_uring_enter(2) 的均匀开销为 t=1900ns,n=1,s=230ns,总共 (t+s) / n = 2130ns。

留神因为咱们理论只 trace io_submit_sqes,而 io_uring_enter(2) 零碎调用是调用 io_submit_sqes 的,因而 io_uring_enter(2) 的理论开销是必定大于 (t+s) / n。

数据量化剖析

从上述数据发现,cpu 破绽的确对系统调用的性能影响较大,尤其对于小数据包的场景,咱们别离探讨下:

cpu 破绽缓解未使能

epoll: s+w, io_uring: (t+s) / n

能够看出在此种状况下,因为 t 大于 w, 即便扩充 batch,io_uring 的性能也不如 epoll。

cpu 破绽缓解使能

epoll: s+w, io_uring: (t+s) / n

能够看出在此种状况下,因为 s 比拟大,当 batch 比拟低时,io_uring 不如 epoll,但当 batch 比拟大时,io_uring 场景下零碎调用上下文切换开销被极大摊薄,此时 io_uring 的性能是优于 epoll。在咱们的理论测试中,1000 连贯时,io_uring 的的吞吐要比 epoll 高 10% 左右,根本合乎咱们的建模。

论断

从咱们的量化剖析能够看出 io_uring 与 epoll 孰优孰劣齐全由评估模型中定义的 4 个变量决定:

epoll: s + w

io_uring: (t + s) / n

如果某个变量占主导地位,则性能数据会截然不同。举个例子,假如零碎调用上下文切换开销 s 很大,而且 io_uring batch n 也很大,则 io_uring 在此种场景下的性能必定是会比 epoll 好;再比方零碎内核侧开销 w 很大,此时 io_uring 和 epoll 性能会靠近。

因而 io_uring 和 epoll 孰优孰劣,取决于其利用场景,咱们倡议的最佳实际是基于业务实在网络模型,将其简化为 echo server 模型,运行咱们的度量脚本,从而能够评估二者在实在环境的性能,以领导实在利用开发。同时上述度量数据也为咱们的性能优化提供方向,咱们能够尽可能的缩小某一变量的开销,从而进步性能,比方能够进一步优化 io_uring 框架的开销。

高性能存储技术 SIG 介绍

高性能存储技术 SIG:致力于存储栈性能开掘化,打造规范的高性能存储技术软件栈,推动软硬件协同倒退。

原文链接
本文为阿里云原创内容,未经容许不得转载。

正文完
 0