面试题十二:CPU架构适配需要注意哪些问题?

CPU架构适配需要注意哪些问题?

一、面试官视角:这道题想考察什么?

  • 是否有Native开发经验

  • 是否关注过CPU架构适配

  • 是否有过含Native代码的SDK开发的经历(包括如何为他人提供so库)

  • 是否针对CPU架构适配做过包体积优化

    涉及包含哪些CPU架构的so库,如果包含得多了话,那么包体积肯定很大,那针对这种情况有没有过类似的优化。

二、题目剖析

1、Native开发才会关注CPU架构

2、不同CPU架构之间的兼容性如何

首先看看CPU架构的指令兼容性,在Android开发过程中,大概能遇到的CPU架构如下所示:

【mips64、mips】、【x86_64、x86】、【arm64-v8a、armeabi-v7a、armeabi】,最常见的CPU架构是【arm64-v8a、armeabi-v7a、armeabi】,最不常见的就是【mips64、mips】,而且这两个CPU架构已经废弃了。

接下来我们看看它们的兼容性如何?

1、mips其实是可以放在mips64机器上跑的,所以mips64兼容mips。

2、x86其实是可以在x86_64机器上跑的,所以x86_64兼容x86。

3、armeabi-v7a其实是可以在arm64-v8a机器上跑的,所以arm64_v8a兼容armeabi-v7a。

4、armeabi其实是可以在x86_64、x86、arm64-v8a、armeabi-v7a机器上跑的,所以x86_64、x86、arm64-v8a、armeabi-v7a兼容armeabi。

综合所述,发现armeabi的兼容性最好!!!

最后举个例子:

项目中的libs目录下有以下so库:armeabi(libmath.so、libui.so)、armeabi-v7a(libmath.so、libui.so)、arm64-v8a(libmath.so、libui.so),那么在arm64-v8a的机器上Native库如何加载呢?

它会优先选择当前CPU对应的CPU架构目录下的so库,也就是会加载arm64-v8a(libmath.so、libui.so)目录下的so库,假设如果arm64-v8a架构的so库里面没有libui.so了,那么会发生什么问题呢?它还是会去arm64-v8a架构的so库里面找,但发现so库里没有libui.so,那么加载就会失败,那刚刚也说了armeabi和armeabi-v7a本身就兼容arm64-v8a,那为什么不去加载它们呢?这是因为CPU加载so库的时候就这么规定的,所以要提供so库就一定要提供一全套,要么就一个都不要提供,这样CPU就会去找它所兼容的so库。

兼容模式运行的一些问题:

1、兼容模式运行的Native库无法获得最优性能(所以x86的电脑上运行arm的虚拟机会很慢!)

2、兼容模式容易出现一些难以排查的内存问题

3、系统优先加载对应架构目录下的so库

3、so库太多如何优化Apk体积

  • 为App提供不同的CPU架构的Native库

    实际开发过程中,我们不可能提供所有CPU架构的so库,那就要考虑兼容,兼容性最好的当然就是armeabi了,那是不是我们就只需要提供这一套了呢?如果在性能不敏感且无运行时异常的情况下可以这么干。但目前市场上大部分的机器的CPU架构都已经是armeabi-v7a和arm64-v8a了,所以这个时候提供一套兼容性最好的so库就是armeabi-v7a了,所以我们要结合目标用户群体提供合适的架构。

    还有一个方案就是,我们只提供一个CPU架构目录,比如armeabi-v7a,把所需要的so库都扔到这一个目录里面,这样一来里面也可以提供arm64-v8a的so库等,这样的话,我们就可以动态根据当前的CPU架构去选择加载一些特定的so库,微信就是这么干的。

    那到底选择那种方案去提供so库呢?

    最好的办法就是通过线上监控问题,然后针对性提供Native库。

  • 动态加载Native库

    把非启动加载的库放到云端(CSD)上, 然后通过开关让用户选择是否需要加载,需要的话就从云端下发,不需要的话就拉到。

  • 优化so体积

    • 默认隐藏所有符号,只公开必要的(-fvisibility=hidden)
    • 禁用C++ Exception&RTTI(-fno-exception -fno-rtti)
    • 不要使用iostream,应优先使用Android Log
    • 使用gc-sections去除无用代码(类似于Java的混淆)
      • LOCAL_CFLAGS += -ffunction-sections -fdata-sections
      • LOCAL_LDFLAGS += -Wl,–gc-sections
  • 构建时分包(官方提供)

    根据CPU架构进行分包,每一个包里面只包含一种CPU架构,然后借助应用市场按CPU架构分发安装包。

    1
    2
    3
    4
    5
    6
    7
    8
    splits {
    abi {
    enable true
    reset()
    include "armeabi-v7a""arm64-v8a""x86""x86_64"
    universalApk true
    }
    }

4、SDK开发者应当提供哪些so库

  • 尽量不在Native层开发,降低问题跟踪维护成本
  • 尽量优化Native库的体积,降低开发者的使用成本
  • 必须提供完整的CPU架构依赖

三、题目结论

  • 注意CPU架构指令兼容关系,以及兼容模式下的问题
  • 注意合理提供Native库,在性能与体积上取舍
  • 通过代码编写和编译的手段缩减Native库的体积
  • 通过动态下发的方式减小安装包的体积
  • SDK开发相比App开发者应当多关注的一些问题