Hugo ʕ•ᴥ•ʔ Bear Blog

记录一次性能调优的经历: app和native版本差10%

请原谅我不知道标题该起什么名字合适:D 最近在负责某benchmark在x86平台上的cpu性能优化, 遇到了这样一个问题: 该benchmark有app版本和native版本, app版本其实就是简单地通过jni调用到了native层, 明明native层的代码都是一样的, 但是它们的性能差了10%以上.

由于该benchmark比较复杂, 所以我第一个实验就是写了一个简单的app, 模拟它的行为, 同样调到了它的native层, 然后发现能够复现问题. 下一步重写了它的native层, 依旧发现可以复现问题.

至此, 这个问题已经和benchmark无关了, 变成了这样一个通用的问题: 为什么在x86平台上面, 同样的code在app里面被调用到的时候, 相较native层, 会出现10%以上的性能损失?

于是, 我就开始了一通调查, 一个比较重要的发现是: 这个问题和selinux有关, 只有selinux=enforcing的时候, 会有这个现象. selinux=permissive的时候, 就不能复现问题.

顺着这个线索, 我开始跟踪zygote的启动过程, 发现了下面这一段代码. Zygote在init的时候, 会去check selinux的状态. 如果是permissive的话, 什么都不做. 如果是enforcing的话, 会给所有后续从zygote fork的进程, 加上seccomp这个security feature.

632  static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
633    if (!gIsSecurityEnforced) {
634      ALOGI("seccomp disabled by setenforce 0");
635      return;
636    }
637  
638    // Apply system or app filter based on uid.
639    if (uid >= AID_APP_START) {
640      if (is_child_zygote) {
641        set_app_zygote_seccomp_filter();
642      } else {
643        set_app_seccomp_filter();
644      }
645    } else {
646      set_system_seccomp_filter();
647    }
648  }

然后seccomp加上的时候, 默认会disable掉这两个优化: Speculation_Store_Bypass和SpeculationIndirectBranch. 状态如下: (这是benchmark app的dump)

# /proc/[pid]/status
Seccomp:	2
Seccomp_filters:	1
Speculation_Store_Bypass:	thread force mitigated
SpeculationIndirectBranch:	conditional force disabled

不加seccomp, 它是这样的: (这是native shell的dump, native shell起来的进程由于与zygote无关, 所以不会被添加seccomp的配置)

# /proc/[pid]/status
Seccomp:	0
Seccomp_filters:	0
Speculation_Store_Bypass:	thread vulnerable
SpeculationIndirectBranch:	conditional enabled

对应kernel code是下面这段. PS: kernel 6.1下面, AUTO时候是不会对seccomp添加mitigation的.

// kernel 5.15 arch/x86/kernel/cpu/bugs.c 
1747  static enum ssb_mitigation __init __ssb_select_mitigation(void)
1748  {
1749  	enum ssb_mitigation mode = SPEC_STORE_BYPASS_NONE;
1750  	enum ssb_mitigation_cmd cmd;
1751  
1752  	if (!boot_cpu_has(X86_FEATURE_SSBD))
1753  		return mode;
1754  
1755  	cmd = ssb_parse_cmdline();
1756  	if (!boot_cpu_has_bug(X86_BUG_SPEC_STORE_BYPASS) &&
1757  	    (cmd == SPEC_STORE_BYPASS_CMD_NONE ||
1758  	     cmd == SPEC_STORE_BYPASS_CMD_AUTO))
1759  		return mode;
1760  
1761  	switch (cmd) {
1762  	case SPEC_STORE_BYPASS_CMD_AUTO:
1763  	case SPEC_STORE_BYPASS_CMD_SECCOMP:
1764  		/*
1765  		 * Choose prctl+seccomp as the default mode if seccomp is
1766  		 * enabled.
1767  		 */
1768  		if (IS_ENABLED(CONFIG_SECCOMP))
1769  			mode = SPEC_STORE_BYPASS_SECCOMP;

至此, 问题就比较清楚了, 总结一下就是: zygote配置seccomp的时候, kernel出于安全考虑, 关掉了两个处理器level的优化, 导致app层的code执行得相对较慢. 如果想强制改变这个行为, 只需要加上这两个kernel参数就好了. “ spec_store_bypass_disable=off spectre_v2_user=off ”