实际上,像Java、Python、Perl这种跨平台的开发语言,它的迁移的技术难度相对来说是比较低的,主要是因为Java有一个JBM,Python中存在一个解释器,它们屏蔽了大部分的平台架构上的差异。与之相反,C和C++的难度相对来说要高一些,原因是如内敛函数还有x86加速的一些指令,无法支持像微软这种闭源的开发架构。
典型的如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库来进行替换,最后进行重新打包。
再来看Python源码的迁移,其实与Java很类似,也是需要从编译环境和 SO 库两大方面入手修改。环境上推荐使用 A32,Python3 你也可以通过样本安装,也可以通过源码安装;SO 库有多种类型,但对于各种方式的 SO 库,最后都是对应为一个 SO,定义为 SO 库,需要的步骤也都是一致的,即装配环境、重新编译、重新替换。
不同的地方只是前面安装的是Python的运行环境。Python源码同样通过解释器生成一个字节码,这时候可能我们会依赖外部的一些Python import模块进来,这些模块里面可能有一些SO库,Python解释器的解释执行后就是部署运行了。
相对于C++来说,Java/Python的迁移点并不是太多,总结下来,主要需要注意两点:
编译器包括解释器的安装、迁移,这里有两种方式,方式一是通过YAM源的方式进行安装,方式二是找到源码进行编译重新安装。
x86依赖SO库的情况下,需要找到SO库相应的源码,进行重新编译后实现替换。
写在最后