容器中的 Slow YaST

我们注意到,在容器中运行 YaST 服务管理器模块时,模块启动速度比直接在宿主机系统中运行慢约 3 倍。启动大约需要一分钟,这太长了…

Root 用户 vs 非 Root 用户

这实际上不会最终影响 YaST,但这是一个有趣的差异,可能对某些人有用。

如果你运行这个

# time -p ruby -e '10000.times { system("/bin/true") }'
real 2.90
user 2.45
sys 0.54

并且以 root 用户身份运行完全相同的命令

# sudo time -p ruby -e '10000.times { system("/bin/true") }'
real 9.92
user 5.89
sys 4.16

需要的时间大约是三倍! :open_mouth:

但最终结果表明,原因是 Ruby 使用优化的 vfork() 系统调用,而不是传统的 fork()。但由于存在一些安全隐患,不应在以 root 用户身份运行时使用它,在这种情况下,Ruby 使用标准的(较慢的)fork() 调用。更多详细信息请参见 Ruby 源代码

所以最终,实际上并非因为以 root 用户身份运行而慢 3 倍,而是相反,因为以非 root 用户身份运行而快 3 倍。但由于我们几乎总是以 root 用户身份运行 YaST,因此我们无法使用这个技巧…

Cheetah vs SCR

好的,但为什么服务管理器启动速度慢得多?

在 YaST 中,还有其他运行进程的方法。你可以使用 SCR 组件(YCP 时代的遗留组件)或 Cheetah Ruby gem

Cheetah

让我们尝试一下 Cheetah 在不同用户下运行时的表现

# time -p ruby -r cheetah -e '10000.times { Cheetah.run("/bin/true") }'
real 17.83
user 10.41
sys 8.73
# sudo time -p ruby -r cheetah -e '10000.times { Cheetah.run("/bin/true") }'
real 15.74
user 9.51
sys 7.47

数字大致相同,原因是 Cheetah 总是使用较不优化的 fork() 调用,因此没有区别,谁运行脚本无关紧要。

嗯,也许我们可以改进 Cheetah gem 以使用 vfork() 技巧… :thinking:

SCR

SCR 中的 .target.bash 代理也使用 fork,因此用户是谁无关紧要。

基准测试

传统的 YaST SCR 组件是用 C++ 实现的,调用需要通过 YaST 组件系统,而 Cheetah gem 使用原生 Ruby 代码。此外,SCR 使用 system() 调用,它使用中间 shell 进程,而 Cheetah 使用 exec(),它直接执行命令。

因此,最好比较这两种选项,看看它们的性能如何。为此,我们编写了一个小的 cheetah_vs_scr.rb 基准测试脚本。它只是列出所有 systemd 目标,并运行那么多遍以获得更可靠的结果。

结果

root 用户身份直接在系统中运行脚本

# ./cheetah_vs_scr.rb
Number of calls: 1000
Cheetah   : 8.84ms per call
SCR       : 22.90ms per call

如你所见,即使没有容器参与,Cheetah gem 也快两倍多!

那么在容器中运行并运行 /mnt chroot 中的 systemctl 命令时,情况会如何变化?

yast-container # ./cheetah_vs_scr.rb
Number of calls: 1000
Cheetah   : 7.30ms per call
SCR       : 91.78ms per call

如你所见,SCR 调用慢了 4 倍多。这与我们在服务管理器中看到的减速相对应。但更有趣的是,在容器中运行时,Cheetah 的情况实际上略微更快!如果你比较容器中的 Cheetah 和 SCR,Cheetah 快 10 多倍!哇!

因此,在这种情况下,SCR 调用是瓶颈,容器环境通常只会对速度产生轻微影响。我们实际上不知道导致这种减速的确切原因,可能是额外的 chrooting… :thinking:

但由于我们应该无论如何都切换到 Cheetah(因为它是一个干净的原生 Ruby 解决方案),因此我们对研究此问题不感兴趣,这种缓慢的功能到目前为止只在一种特定情况下引起了麻烦。

注意:它是在 openSUSE Leap 15.4 系统中测试的,它也严重依赖于硬件,你的系统可能会得到非常不同的数字。

总结

如果你只是从 YaST 模块中执行其他程序几次,这可能并不重要,你使用哪种方法。

但如果你调用外部程序很多次(大约一百次),则取决于你是以 root 用户身份还是非 root 用户身份运行。对于非 root 用户的情况,最好使用 Ruby 原生调用(system 或 `` 反引号)。对于 root 访问权限,通常在 YaST 中,最好使用 Cheetah gem 而不是 YaST SCR。

在我们的例子中,这意味着我们应该更新 YaST 服务管理器模块以使用 Cheetah,这应该大大减少启动延迟。并且也会改善直接在宿主机系统中启动的速度。