「BAT」为什么必须将代码从 x86 迁移到 ARM?( 三 )


典型的如 C/C++/Go 语言 , 都属于编译型语言 。 编译型语言开发的程序在从 x86 处理器迁移到鲲鹏处理器时 , 必须经过重新编译才能运行 。 如下图所示 , 源码需要由编译器、汇编器翻译成机器指令 , 再通过链接器链接库函数才能生成机器语言程序 。
接下来就是 C/C++ 代码的编译构建了 , 这个过程一共分为六步:

  • 获取源码:通过 github 或第三方开源社区获取
  • 准备编译环境:安装编译器 gcc 等
  • 使用源码中的 CMakeLists.txt 或 configure 脚本生成 makefile
  • 执行 makefile 编译可执行程序
  • 替换依赖库:重编译或替换依赖 x86 平台的 so 库
  • 将可执行程序安装部署到生产或测试系统
既然是最为复杂的 , C/C++ 语言的迁移问题也是涉猎最广泛的 , 主要涉及到七类问题:
编译脚本和编译选项的移植:以 x86 下 -m64 代码为例 , 其主要功能是将应用程序编译为 64 位 , 对应到鲲鹏上是用 -mabi=lp64 的编译选项 , 此类编译选项需要在脚本中修改;此外 ,x86 平台上默认的 char 类型是一种有符号的类型 , 对应到鲲鹏上则是无符号类型 , 在移植过程中需要显示定义并将 char 类型定义为有符号 , 方法一是在源代码里加上 signed char , 方法二是直接用 fsigned-char 来修改 。
编译宏的移植:编译宏的作用就是让编译器知道编译哪些分支代码能够在不同架构下达到最优性能 。 如何对编译宏下面的代码实现移植?86 代码上有些编译器自带自定义宏 , 比如 smd 属性相关的宏在 x86 上是 SSE 开头的宏 , 对应到鲲鹏平台上就需要自定义它的编译宏和所相对应的分支 。
Builtin 函数移植:Builtin 函数是编译器自带的函数 , 其在实际迁移项目中相当常见 , 主要是 crc32 校验值的计算 。 需要移植的普通 builtin 函数实际并不多 , 大部分需移植的 builtin 函数集中在 SSE intrinsic 函数内 。
内联汇编函数的移植:第一种是指令替换 , x86 上对应的是 bswap 指令 , 鲲鹏对应的是 rev 指令 , 其它有些操作和寄存器都是基于内联汇编的语法规则进行替换的 。 第二种是 Builtin 函数的替换 , 以 x86 的指令 popcnt 为例 , 比如 popcount 是对二进制数里面的 1 进行计数 , 对应到鯤鹏平台上所替换的是 popcountll 。
SSE intrinsic 函数移植(SIMD 技术):Intel 的 SIMD 扩展指令统称 SSE , 主要分为三类 , MMX 是 64 位寄存器 , SSE 到 SSE4 是 28 位的 , 三是 AVX256 和 AVX512 。 鯤鹏基于 SIMD 的技术发展比较成熟 , 现在有些基于开源量的 NEON 库主要是在图象处理和视频处理层面 。
SSE intrinsic 函数移植(MMX/SSE):针对 MMX 指令 , x86 上用的是 -m64 的向量做加法运算 , 对应到鲲鹏上是 int32×2 然后再做加法运算 , 类似于常用的 C 函数规则;针对 SSE 指令 , 从内存中加载 4 个单精度浮点数据到寄存器 , x86 是 load , 对应到鲲鹏用的是 vld1q 。
SSE intrinsic 函数移植(AVX):以 AVX 指令使用了 256 位寄存器运算为例 , 向量 A 和向量 B 中分别存储了 8 个单 精度浮点型 (32 位) , 对应到鲲鹏处理器上 , 使用 128 位寄存器实现 SIMD(Single Instruction Multi Data) 进行计算 。
尽管解释性语言难度降低 , 但 Java、Python 代码迁移过程中同样有一些问题需要注意 。
如上图所示 , Java 想要进行迁移的话 , 首先需要安装运行环境 JDK , Java 源码通过 Java 编译器之后就会生成一个 Java 的字节码 , 这时候可能 jar 包会有一些 SO 库的依赖 , 那就需要链接进行打包 。
这整个的过程 , 存在迁移修改点的地方有二 , 一是 JDK 的安装 , 迁移过来推荐使用华为的 JDK 或者 OpenJDK;二是对 SO 库这个二进制库的依赖 , 如果你是在 x86 下编译的 , 毫无疑问 , 这在鲲鹏下面是没办法进行运行的 , 所以你需要拿到相应的源码或者找到 aarch64 的 SO 库来进行替换 , 最后进行重新打包 。


推荐阅读