NUMA 机制的应用
在 ARM 服务器上,以默认的方式启动许多个 emulator 实例,像下面这样:
将出现许多 emulator 进程 CPU 占用率都非常高,各个 emulator 间疯狂争抢 CPU 资源的问题。这种启动 emulator 的方式是不可能在 64 核的 ARM 服务器上同时运行多于 64 个 emulator 的实例的。
ARM 服务器共有 64 个 CPU 核,及 4 个 NUMA 节点,每个 NUMA 节点都与一些 CPU 核绑定,通过命令行工具 lscpu
可以查看相关的信息。
从上面输出的信息中,可以看到,第 0 号 NUMA 节点与 0-15 号 CPU 绑定,第 1 号 NUMA 节点与 16-31 号 CPU 绑定,依次类推。通过如下命令启动模拟器,以优化整个系统的性能:
这个命令将 emulator 的执行绑定在特定的 NUMA 节点和 CPU 核上。需要注意 指定的 CPU 核需要与它绑定的 NUMA 节点一致;指定的 NUMA 节点和 CPU 核均需要在可选择的有效 NUMA 节点和 CPU 范围内,否则进程将启动失败。
经过上面的优化,整个系统的性能得到比较大的优化,不再出现许多 emulator 进程 CPU 占用率都非常高,各个 emulator 间疯狂争抢 CPU 资源的问题。
adb 导致的性能问题
经过上面的优化,虽然性能得到大幅优化,但当我们通过 top
和 htop
命令查看进程状态时,还是发现了一些问题。top 命令执行结果如下:
从 top 命令的输出结果可以看到,多个 emulator 的进程 CPU 占用率仍然较高。
htop
命令执行的结果如下:
emualtor 进程使得 CPU 持续处于异常繁忙的状态同样得到验证。
然而,我们通过如下命令杀掉 ADB server:
再次查看 top
和 htop
命令的输出。top
命令的输出如下:
从上面的输出中可以看到,除了进程号为 42980 的进程外,其它的 emulator 进程的 CPU 占用率都恢复到比较低的状态。
htop
命令的输出如下:
从上面的输出可以看到,除占用第 15 号 CPU 的进程 CPU 占用率总是非常高,接近 100% 之外,整个世界都平静了。不难猜测,这里占用第 15 号 CPU 的进程正是上面看到的进程号为 42980 的进程。
多次查看第 42980 号进程打开的端口:
这个 emulator 进程始终监听在第 5716 和 5717 号 TCP 端口上——这两个端口分别是用于终端和 ADB 连接的端口。emulator 与 tcp:localhost:5037 上的服务保持着一条长连接,并与相同地址上的服务有一个频繁地建立和销毁的连接。
对于模拟器而言,在 ADB 中,它的串号通常是 “emulator-“ 后跟终端端口号。我们启动 ADB server,并查看该 emulator 的状态:
CPU 占用率始终非常高的 emulator,其状态为 OFFLINE 的。这是由 ADB 导致的 emulator CPU 占用率高的一个问题,即处于 OFFLINE 状态的 emulator 进程 CPU 占用率非常高。
处于 OFFLINE 状态的 emulator,还可以观察到这样一种现象:
即由 ADB server 发起的与其建立的连接失败,连接始终处于 CLOSE_WAIT
状态。
除了这种处于 OFFLINE 故障状态的 emulator 进程外,在 ADB server 运行时,还有一些 emulator 进程 CPU 占用率也非常高。从中任意挑选一个,如进程号为 45559 的进程,多次查看其打开的端口状态:
这个进程同样监听在两个端口上,只是这两个端口分别是 5790 和 5791。emulator 与 tcp:localhost:5037 上的服务保持着一条长连接,并与相同地址上的服务有一个频繁建立和销毁的连接。另外一个进程与 emulator 在 tcp:localhost:5791 上的服务也保持着一条长连接。
查看在 tcp:localhost:5037 上提供服务的进程,以及与 emulator 建立连接的进程是谁:
可见 emulator 发起的连接的服务端 tcp:localhost:5037 位于 ADB server 进程内,发起与 emulator 内的服务建立连接的同样是 adb 进程。
由此推测,ADB server 启动之后,出现性能问题的可能原因:
- emulator 频繁地与 ADB server 建立销毁连接造成了比较大的开销。
- emulator 在由 emulator 发起建立的与 ADB server 之间的连接上活动频繁消耗巨大。
- emulator 在由 ADB server 发起建立的与 ADB server 之间的连接上活动频繁消耗巨大。
总结一下,要解决或检查的问题:
- 消除处于 OFFLINE 状态的 emulator,查找 ADB server 与 emulator 建立连接失败的原因。
- 检查 emulator 进程中,与 ADB server 建立的各条连接的状态。
对于 ADB 导致的性能问题,我们从 emulator 与 ADB server 建立连接的过程开始分析。
emulator 发起的与 ADB server 之间的稳定的长连接的创建位置如下:
emulator 进程与 ADB server 建立 TCP 连接的过程(位于 qemu/android/android-emu/android/wear-agent/WearAgent.cpp
)如下:
在 qemu/vl.c
的 main_impl()
函数中有如下这样一行:
这个函数创建 android::wear::WearAgent
对象,并在该对象的创建过程中,启动新的线程来建立与 ADB server 之间的连接,即与 ADB server 之间的长连接。
我们注意到,上面建立与 ADB server 之间的连接的过程中,是通过 android::base::socketTcp4LoopbackClient()
函数完成的,不难猜测在 emulator 中,建立与某个服务的连接的过程都是通过该函数完成的,包括前面观察到的与 ADB server 之间那个频繁建立与销毁的连接。
通过 GDB 来调试 emulator。我们观察到,在 emulator 的整个生命周期中,除了 qemu/android/android-emu/android/wear-agent/WearAgent.cpp
文件中,建立的与 ADB server 的长连接之外,还有如下几处,也与 ADB server 建立了连接。
连接一:
这里是在 emulator 进程完成对终端端口和 ADB 端口的监听之后,关于其状态通知 ADB server 而建立的临时连接,通知完成后连接销毁。
连接二:
这里是 emulator 运行的 Android 系统初始化到一定阶段之后,触发的与 ADB server 之间建立的连接,用于通知其状态。这个连接在通知完成之后销毁。
除了上面 3 个与 ADB server 建立连接的情况,在多个 emulator 同时运行时,如下这种情况发起的与 ADB server 建立连接的情况则要频繁得多:
比较关键的是栈的 #8 号栈帧,其相关代码位于 qemu/android/android-emu/android/wear-agent/WearAgent.cpp
中,像这样:
从中可以看到,在 WearAgentImpl::onRead()
中收到了新消息,从而触发了 emulator 与 ADB server 建立连接。这些消息实际上像下面这样:
通过观察,不难发现,当系统中有设备/模拟器实例状态发生改变,比如新创建了 emulator 实例,或者 emulator 实例从 OFFLINE 状态切换到 ONLINE 状态,每个 emulator 进程都会收到所有设备/模拟器实例的最新状态。emulator 收到的这些状态信息就像执行 adb devices
命令时看到的那样。不难推测这些消息是由 ADB server 发送的。
当系统中运行大量的 emulator 实例时,大量 emulator 实例频繁地相互争抢 CPU 资源,正是由于 ADB server 频繁地向与其连接的所有 emulator 实例广播所有 emulator 实例的状态所导致。
这种机制本来主要是为 Android 可穿戴设备设计,用于与 Android 手机配对的,但在系统中运行大量 emulator 实例时,却导致了性能问题。
ADB 导致的性能问题的解决
既然找到了问题之所在,那就要寻找解决办法。解决方法可以有两个:一是让 ADB server 不要频繁地唤醒 emulator 进程并向与其连接的所有 emulator 实例广播所有 emulator 实例的状态;二是让 emulator 进程忽略收到的这种消息。
综合来看,还是修改 ADB server 代价较低。
分析 ADB 的代码,不难发现,在 ADB server 中,是通过 system/core/adb/transport.cpp
文件中的 device_tracker_send()
函数向所有 emulator 实例发送我们前面观察到的消息的。
device_tracker_send()
函数在相同文件中的 device_tracker_ready()
和 update_transports()
函数中被调用,这两个函数定义如下:
这两个函数分别在 emulator 启动之初,和其它的 emulator 状态改变时,向所有 emulator 实例发送消息。
鉴于对 Android wear 设备的支持没有太多意义,因而可以禁掉这里向所有 emulator 实例广播所有 emulator 实例状态的动作。
经过上面这番对于 ADB server 的改造,adb 支持的命令,如 adb shell
等,依然可以正常执行,但系统中 emulator 进程的 CPU 消耗则可以降到比较低的状态。通过 top 命令可以看到:
打赏
Done。