-
想做一个系统生成器,需要一款相对成熟的AI代码生成器,求推荐
-
《megahit 源码迁移》项目大作业解析代码迁移工具是一款可以简化用户迁移步骤,将用户的现有的 886 环境上的应用迁移到赋能平台上,并获得良好的性能和功能体验的辅助工具具体的功能有过上节课的介绍我们就不多讲了。今天我们这堂课主要的功能的话聚焦在我们的源码迁移部分,这部分的话主要就是我们分析源码。然后根据扫描出来的根据分析报告,然后去修改根据分析报告的还有迁移建议去修改相关的源码。修改完成修改完之后,然后我们再去编译来自端。完整的体验下一款软件在鲲鹏代码在代码迁移工具上的并已过产。整个迁移流程的话大概分为这几块,一个是环境搭建,这块的话对硬就是有硬件要求,这块的话可以通过后面的整个代码迁移工具的就是实验的介绍环节去做更加深的了解这块主要就是就是我们可以通过远程实验室去申请我们相关的硬件,然后去在上面搭建我们的代码迁移工具等。代码迁移工具搭建好之后,我们再根据就是整个代码迁移工具的源码扫描这块的功能去操,去对我们的源码进行一个扫描分析。扫描分析之后,那么我们再根据仪式的内容去做一个就是代码的修复的也不是修复,就是去做一个代码的那个修改,让它能够适配在我们的空盆环境上运行。等失败完之后,我们再去迁移之后验证迁移之后重新编译 B 的话就是一个验证,也是一个验证的过程。最后我们再去运行它运行,再去确保我们的迁移。迁移后软件是能够在空本环境上运行起来的。前,那我们就开始我们的大作业的讲解,先了解一下我们今天这款需要迁移的软件叫是一款用作基因组优化的程序,也是这是一款开源软件,是在 github 上有源码。就是托管了源码的,我们后续的话就要不要到那个就是 github 上去把相关的代码下载下来,然后放到我们的代码迁移工具的对应的目录上去。这个是简单一个介绍,主要就是介绍一下我们今天的这个主角,就是这个代码一个需要迁移的源码的一个背景。它这这块主要涉及到就是这三块代码,这块三块代码也是我们整个代码迁移工具中就是支持的扫描的类型先看一下我们这个就是我们的硬件环境,这个的话到时候我们可以通过远程实验室去申请使用我们的鲲鹏崩盘的代码。这块的话主要就是下载地址,一个是下载地址,还有就是我们目前的就是我们最终的运行环境的操作系统的介绍。 open 奥拉。对,还有就是我们的那个运行环境是鲲鹏环境这块,也就简单的给大家过一下,先了解一下我们整个的那个迁移大概的这就是这个这款软件迁移的大概步骤第一块是先把我们的迁待迁移源码下载到下下载下下载到服务器上。然后就是我们使用一个终端工具先连接到我们的部署。好我们代码迁移工具的环境,然后将我们的代码清代码,就是这个下载的源码拷贝到对应的目录去,或者是直接在当前目录去下载操作也可以的。对,这两步相当于把我们的那个信息材料给下载下来,然后放于我们工具的对应指定的目录下。第三第四块就是把我们的代码和合并,就是说因为你需要生成 Mac file 之类的,需要生成 make file 然后我们再进行扫描。最后就是我们创建一个这个主要就是前面的步骤,就是说将整个我们的源码的环境准备好,然后到最后生成 Mac file 然后再修改我们的数组,这些都是第一步,我们的这个源码准备的,准备相关的这块的准备,主要就是将代码下载到我们代码工迁移工具的指定位置,然后去给它把那个编译那那就是那个构建文件生成,然后并修改图组,能让代码迁移工具去扫描识别调步的话就是我们登录到我们已经安装好的那个代码迁移工具的 web 页面,然后就开始进行下面的那个操作了。就是选择我们的源码迁移,目标这块的话就是选在这个目目录选择我们需要迁移的内容,然后这块是指定我们需要迁移的源码类型,就是我们刚才讲的它主要是 C 和 C 加加代码这个有平台相关性,然后就是我们之后要迁移的那个目标操作系统,还有我们准备的就是编译编译的那个编译的编译的编译的编译器编译器和编辑版本我们支持的比较多编译器,到时候大家可以打开去看一下。然后目前的话我们根据我们迁移的 open 欧拉,然后毕生这个是我们推荐的一个就是 open 欧拉环境,还有我们的构建工具,然后搞搞编辑器。然后构建工具的话就是因为我们是生成了 make file 然后我们就选择 make file 然后 make 的话就是它编译命令也就是 make arm 就是就不指定它的别命,大家可以参考这块。就是这个就是我刚才介绍的我们的一个代码迁移的那个目录,指定我们的源码目录,然后这个是指定我们的目标操作系统、指定编译器,还有指定的构建工具和编译命令等这些都填充完之后,我们开始扫描,点击。开始分析之后就是进行代码扫描,等代码扫描完成之后会打会弹出一份报告。然后我们建议进入查看,查看报告来到查看报告页面。具体的就是。报告页面的话主要会给我们展示架构相关的依赖库,还有我们的需要修改的源码信息。然后目前的话从我们分析的结果来看,它的依赖库跟架构相没有跟架构相关的依赖库。然后目前的话主要就是进行我们的源码的相关的修改,去适配我们的鲲鹏编译鲲鹏的环境。左边的话这些文件列表就是我们待修改的就是有x 86 特有的内容。然后我们需要根据我们的提示建议把它修改为就是能够支持鲲鹏运鲲鹏环境上运行的代码就是做一个兼容适配。像第一块这块的话,这个 CPU dispatach.h 里面的话有那个内联汇编相关的内联换编相关的话可以会用到我们的汇编。就是这块的解析是用到我们汇编翻译的提供的功能,它可以将就是叉 86 的汇编翻译成我们的工程上支持的汇编内容。然后针对这块修改点的话还需要导入一个头文件,这个头文件的话也是随着我们的鸿蒙代码信息工具一起发布的。然后它会在我们的工具的目录下,等我们后面正式去编译的时候会用到这个好文件,大家稍后在最后我们的那个编译环境上可以确认一下。就是这块的使用大家可以看,可以看,可以先记一下。就是说我们最到最后的编译因为是导入头文件,会使用到我们带出去的查文件,然后等我们的然后这几块的话都是我们提供的一个汇编,就是汇编翻译的汇编少描述汇编相关的内容。然后我们可以用到我们的自动修复修复,就是 clickfix 可以直接就自动把代码 1 代码适配成支持昆仑设扫描的内容,鲲鹏运行的内容这样这个修改点就是第一个三点的修改点的话就引入这个头文件,然后就 prefix 之后,它会自动自己添加这部分代码。然后那个第二跟第四个修改点的话是就是会指定编译指定命令的编译的那个替换。替换的话因为这里加注释是因为担心就是用户有一些特殊功能没有识别,需要用户去就是再次确认,然后再加加入到它的那个代码中。这块的话还需要把它的那个修复完之后,还需要把它的注释去掉这个代码才能够生效。然后等修改这个文件内的那个待修改点都修改完之后,我们点击一个保存,把我们的修改后的代码保存下来。这个是我们第一个文件的修改,第二个是我们的那个 MA make 就是那个编我们的那个构建文件,构建文件里面的它识别出来的就是不支持这两个指令 BMI 度和那个标 pcnt 指令。然后需要就是需要我们在编译选项中把这两个指令给删去。这个大家可以看一下。就是说到我们的代码迁移工具,支持这些编译命令 w6 特有的编译命令的识别,因为空本上因为架构不同,它们支持的指令不一样,所以说这些不支持的我们需要去掉,不然也还是编译不过的。等等。把这些内容去掉之后我们也保存也是保存之后就要就是修改完成之后保把我们的那个修改完之后代码保存下来等等我们的代码上的修改点都修改完成之后,我们到我们的就是刚才提到的这个头文件,因为我们导入了一个我们就是汇编翻译提供的相关的条文件,然后我们就到那个我们的就是汇编到我们的工具的目录下去,将我们的这个查文件,然后拷贝到它的代码的对应的目录目目录下,这样它能够调用。这个就是我刚才提到的需要从注意一个点的地方,就是我们提供了头文件,但这个头文件还是需要拷贝到我们的那个,在正式编译过程中还是需要拷贝到我们的的,那就是我们使用这个就是待迁移源码,迁移源码使用到这个草文件的地方,这样才能够正常编译等拷贝完成之后,我们就可以开始执行编译。编译过程中我们会发现这里报一个我们的那个一个头文件缺失,一个头文件缺失等。这个头文件缺失的话,这个应该是叉 86 英券这个头文件是鲲鹏特有的,不是叉 86 特有的那个叉 86 特有的一个头文件。所以说我们需要将这块内容注释掉,然后等待保存。保存完这个文件之后我们再继续编译等编译的话我们会发现通过。编译通过之后我们在去跑这块源码提供的相关的测试用例等验证我们的基本功能是否完备等验证完成之后,我们才能够就是说我们的整个迁这个源码的迁移完成。以上就是整个我们大概就是需要做的步骤和每一步做了什么,就给大家讲解了一下就是我们这块内容的话大概就是一个就是我们准备好我们待迁移源码,对应生成好相关的开始文文那个构建文件之后,等到后面我们再第二步就是我们用我们代码工具去做一个扫描分析,分析完之后我们再去做一个修代码的一个兼容性的那个修改修改让我们的那个鲲鹏的工资能够就是把叉 86 特有的放到鲲鹏的分支下,然后放放在那个差 86 配有的分支下,把放在空门的分支下,等整个然后等代码修改完之后,我们再去编译,然后并将我们的投文件拷贝到对应的位置去编译的。然后如果还有报错的话,我们再统计报错信息去修改对应的源码文件这块的话,我们因为后面的版本才做了那个同键优化,后续的话不需要就相当于这块同键我们也能够识别出来。当前的话目前因为我们之前使用的这个版本的话是所以这个头文件还没有识别,需要手动去修改,后面的话会让这块识别完整的等这就不需要再编译再修改的过程。就是这样。然后等这个都修改完之后,我们再去编译。编译完之后,我们再去跑我们的测试用例,测试用例跑完了之后,相当于我们的整个迁移过程就完成了。行然后我们就是最后的一个总结。总结就是说我们这个碰迁代码迁移的大作业主要就是了解一下,就是使用我们的昆明代码申请工具,然后去去分析源码,然后到我们的整个迁移端到端的就是将整个迁移过程到最后运行到他修改的到修改代码,到最后编译到最后运行测试用例,将整个代码迁移工具给大家演示完成。然后就是增强大家对整个迁移过程的了解,然后也就是更加熟悉我们的代码迁移工具。以上就是我们的这个诈骗的内容,然后后面的话我们将会进行就是带着大家再从我们实验的内容里面给大家过一遍我们的整个代码迁移的内容。行。那我们开始我们的实验相关的操作的,跟大家一起过一遍我们的整个操作过程。然后因为环境相当于来说大家都是比较稀缺的。然后我们的那个我们的鲲鹏社区的话提供了一个远程实验室的申请,大家可以通过远程实验室去申请。我们的那个就是我们的实验环境,位置是在这块我们的那个空门社区开空盆,大家可以记一下,然后到我们的开发者开发者的那个开发套件 DU tier 这里 DU tiger 这里的话我们再往下拉拉到我们的远程实验室,然后去一个。立即申请的话根据填一个邮箱,然后根据在线使用的时间,如果有时间限制,然后选择自己的那个环境这块我就不去为大家做演示了然后我们就申请完成之后他会返给我们一封邮件,邮件邮件的内容里面会会有那个会有相关的信息的介绍。这块的话到时候等大家自己去申请的时候会去知道然后申请完成了之后,我们去登上 VPN 连到我们的网络上去之后我们去使用我们的已经发服务器上已经安装好的一个运行环境。我们先登录下。这是我们一个登录,就是一个用户密码。然后我们这里因为是第一次登录他会让我们签署一个声明,然后给我们切到代码迁移工具,等到这块的话我们就可以到环境上去准备我们的一个代码。代码迁移代码的一个材料,我们需要通过那种终端工具,我这边的话是使用那个。使用这个魔法插上大家根据自己的使用法使用去一个就是去根据自己的日常使用的一个通常工具,去做一个连接。密码。二维码,连上工具账号。然后我们到我们的那个到我们的工具目录下到 put admin 下面的那个这边是这边是之前就是我们这个实验室会存在的一个环境。然后我们去根据我们之前的那个操作指导,去一个下载我们的那个代码。等我们的代码拷贝完成之后,代码拷贝完成之后,然后我们再继续下一步操作。就是初始化代码,然后这个是生成那个一个构建目录。好,最后这个 CMake 是去生成我们的那个要。目前这台环境的话 CMake 没有安装,那我们可以继续安装一下。然后我们继续执行上一步操作,等这个操作完成之后,我们再继续到就是修改那个代码权限。因为是这样的,就是我们的那个 porting 的话,因为对文件读取权限的一些限制之类的。就是我们要改成让 PO 我们的代码迁移工具能够读取到这个文件,都现在操作已经把我们的那个就是代码,就是代迁移代码的材料准备好了,这块就是我们的第一个步骤,准备好我们的代码迁移材料然后我们回到我们的界面上操作这块的话,就是我们选择我们的那个代码,就是我们的那个需要迁移的目录调整到那个 build 下面,因为这块的它的那个就是构建文件是存在这个 build 目录下的。然后选择的话这块就是我们的那个代码,就是我们它因为主要类型是 CC 加加,然后还有汇编当就是之前的照片里面讲到的内容。然后这个是目标迁操作系统,就是我们将要迁移到的环境。我们是 open 欧拉,我们目标相关系统是 open 欧拉的话,然后编写版本的话它会自动根据默认的那个部分和 Lark 相关的去带出来。然后构建工具的话是一定是 make file 那就 make 构建编译命令的话,因为也就默认的 make 就行。然后我们就执行我们的整个就是扫描动作。这个分整个分析过程,因为分为好几步,它会有一些那个时间上的,就是应应该也不会太久,大家到时候具体操作的时候就会知道,就是你扫描的那个文件内容越多,它的那个时间会相对来说越久。就是还有这块的界面,来给大家介绍一下。刚好就是这块的话就是我们的整个大概我们就是我们整个源码迁移的整个界面。就是像这些我们还支持一些其他类型的语,像破圈 go 还有那些解释型语。这边的话就是说我们有个历史报告的这个功能,就是说因为可能会迁移到比较多软件,或者说是我们迁移过程中会存在一些就是说就是继续扫描之类的,就是可以通过这块去打开我们的那个报告,就是说去查看这些扫描的。当然因为是整个因为我们整个代码修复过程是一个动态的,所以说建议就是说看最新的这样子,把一些代码混在一起,很快是这样的一个内容这个代码就是因为分析还是要一点点时间的。大家再稍微等一等,然后马上我们结果就会出现了。我们的代码已经分析完了,然后我们跳转到我们的那个查看报告页面。然后这块的话整体也给大家解读一下,这块是我们一些扫描的配置信息,就是说是我们之前填充的一些内容。然后这块的话就是我们的一些汇总信息,就是说有多少要替换了。目前分析的话发现它是没有一些架构相关的一些内容的,就是说那个这个是没有架构相关的那个依赖库的那种限制的,这块就不用关注了。然后主要的就是我们这些代码需要修改,可以通过这里查看源码建议这里跳过去也可以通过这块就是说这块解码建议这里跳过去行。然后就是到我们的那个代码修改的关键那部分,然后我们可以先放到我们待修改点,然后这里就是因为它不支持这个指令。然后我们就把这两个编译选项给去掉。能找到这两个编译选项在的位置。这个看有一个 mbm BMI 号码,一个是 mmpopcn T 去掉。然后还有一个这个。然后保存。我们已经修改完成第一个文件了,那我们再修改第二个文件。这块也是就是说我们有个就是那个标 P cnp 这个不支持,然后让我们把这个编辑选项给去掉。已修改完成,然后记得保存,这些都就是需要手动去替换的这些建议。然后这块的话就是关于我们自动替换的那个内容,这块的话就是我们需要去添加刚才说的添加一个空盆券时间的点 H 的头文件,这个头文件也在我们工具的目录下放着的,到时候等份子编译的时候你们会看到。然后这两个就是我们的那个汇编翻译的那个内容,汇编翻译也这是自动修复的。这块的话修复完之后,因为这个代码需要就是说把这些注释给去掉。也就是就直接能够替换掉。然后等修改完成之后,我们保存一下。对这块目前的话我们的所有的那个代码上的修改点也就完成了。然后我们去执行一个编译到需要到这块这个应该终端这里,我们先到那个我们的那个,先把我们的那个图文件拷贝到我们的那个对应目录下面去。拷贝完了之后,我们再到这个幕布下去进行编译。目前的话就出现一个,报错信息就是我们那个 interest 投文件这个未修改这块的话是因为之前现在的版本有点就是做的有点就是有点稍微有点缺陷,到时候我们在后面的版本进行了一个修复的。当前因为演示版本是这样,就大家还需要再手动改一个改一下。快的话就是找到那个 int 文件,把它那个注释掉。再保存一下。然后再再执行编译。编译稍微要点时间,大家稍微等一下。关于这块的话,因为头文件的话我们在后面也会也做了一个需求的,这个在新后面的新版本中大家可以使用一下。就是说我们对这块做了一个修改,可以同建这种,可以专门给它识别出来。在他编的过程中刚好还可以。再就是回顾一下,就是我们整个迁移过程主要就是准备一个代码,代码代码的话就是按照人事工程的编译。然后我们在我们的那个就是代码迁移工具里面去指定它的那个 mysbattle 所在的就是它构建文件或者的所在的路径告诉给工具,然后工具再去识别我们的代码中相关的那个就是代码中架构相关的内容,包括汇编翻译汇汇编汇编包括编译选项,包括过一些头文件,因为头文件应该也是那个车友,包括就是一些编译选项。还有那个就是就是红之类的,就是卡巴 6 相关的内容。这块的话需要就是需要去做一个空门平台上来兼容适配。然后等兼容适配完成之后,然后我们再去就是去就是再去。编译的过程中,去就是去解决一些就是说工具的话去解决大部分的那些就是架构相关的问题。然后其他可能有一些小部分这些问题需要用户稍微调整一下。但这块我们也在就是说把这块的那个漏报,相当于有些点,也尽可能里面给用户抱抱,全让他只减少这个就是反复去编去做的一个过程。对,就是这样子我们能够方便用户整个的一个迁移,就是提升他的迁移效率,让他能够在尽可能短时间内把我把他那个就是问题发现全。确实因为每次编译如果是说编到后面的话,某一个文件失败了,就会整个编译其实是挺全的。对用户来说,最耗时的就是这个编译过程中是这样的,比如说工具的话,就是为了解解决这部分问题。然后因为这块是一个例子,就是说到时候用就是真遇到真实的环境的时候,真实的一些问题的时候,我们也就是,多去使用一下工具,然后去协助解决一些问题。遇到实习项目的时候。目前看已经 60% 了,还需要一点点时间。是整个操作的话其实就是还是需要,就是结合工具作为一个比较好的辅助工具,去帮助去解决一些问题。像我们的这个汇编翻译这快的话,如果是需要,就是如果没有工具的话,需要自己再去写一遍,这个 handle 来说也是比较困难的,就是工具对这块就是常见的那个汇编的翻译都能够支持了也常用的场景。但还除了汇编这块。然后汇编这块的话,因为除内编汇编,我们还有一个全汇编的这块的话,在这个案例就不体现了。就是后面如果对工具使用或者说有实际项目都可以,就是尝试着用我们的工具去做一个扫描。仍然快结束了,因为这个过程还是稍微需要点时间的,已经到 93% 了。从编译就是整个。其实整体来说整个迁移过程就是修改的点对修改的内容可能就是还还好,但是整个编译整个测试过程会稍微久一点这样的。因为就目前了解到的那种这些就是成成项目比较大的耗时点,就是在这些问题的解决上,这些架构相关的迁移之类的。我看已经编译完成了,然后我们再去跑一下我们的那个测试用例。这个整个试用跑完了也没有报错,就证明我们整个代码已经从就是在昆仑上已经能够完整的运行和使用了。
-
本节主要介绍了代码迁移工具的功能与特性。通过本节学习,您将:1、了解什么是代码迁移工具2、了解代码迁移工具的功能3、了解到代码迁移工具在实际案例中如何运用本课程主要介绍鲲鹏代码迁移功能根据的特性与功能本节思维导图如下:一、代码迁移工具是什么?1、产生原因:处理器所支持指令集不同,开发者需要对代码进行跨平台迁移。2、代码迁移工具:代码迁移工具是一款可以简化应用迁移到鲲鹏架构服务器过程的工具。二、代码迁移工具功能1、五大功能①软件迁移评估 ②源码迁移 ③软件包重构 ④专项软件迁移 ⑤鲲鹏亲和分析2、功能特性3、应用场景① 为C/C++代码提供迁移指导② 对最常见的x86汇编指令进行识别并转换③ 支持自动化迁移开源软件和构建鲲鹏软件包4、部署方式代码迁移工具采用单机部署方式5、如何访问和使用代码迁移工具提供两种用户访问模式① CLI方式 ②Web/IDE插件方式6、源码迁移7、源码迁移业务流程① 输入 ② 分析处理 ③输出8、源码迁移报告9、汇编翻译① 支持汇编代码自动识别,一键替换迁移,一分钟完成汇编代码的迁移。② x86 汇编常用100%识别,1000+指令自动翻译,覆盖80%的常用应用场景。10、软件迁移评估① 自动扫描并分析软件包、已安装软件,提供可迁移性评估报告② 在报告中对鲲鹏平台支持的依赖库给出提示11、软件迁移评估流程① 输入 ②分析处理 ③ 输出12、软件包重构识别分析用户x86软件包构成,重构适用于鲲鹏平台软件包,并输出重构报告13、专项软件迁移① 一键下载开源代码,并编译、构建为鲲鹏软件包② 支持大数据、数据库、Web、高性能计算等场景主流软件的一键编译迁移14、内存一致性① 支持下载生成的BC文件② 支持一键式自动修复三、案例演示1、源码迁移1上传源码,选择C/C++、make参数,执行扫描操作,对扫描出源码需修改文件进行逐个修改,然后进行保存2、源码迁移2Python语言扫描和Go语言扫描3、软件迁移与软件包重构
-
代码迁移工具的功能 软件迁移评估、源码迁移、软件包重构、专项软件迁移、鲲鹏亲和分析。 功能特性 应用场景为C/C++代码提供迁移指导对常用的X86汇编指令进行识别并转换支持自动化迁移开源软件和构建鲲鹏软件包鲲鹏亲和检查 部署方式 如何使用代码迁移工具提供了两种用户访问模式CLI方式:通过命令 Web/IDE插件 源码迁移 代码迁移流程 源码迁移 可以生成详细的源码迁移报告 汇编翻译支持汇编代码自动识别,一键替换迁移,一分钟完成汇编代码的迁移X86汇编常用指令100%识别,1000+指令自动翻译,覆盖80%的应用场景 软件迁移评估识别检查X86平台软件安装包或以安装软件中使用的动态链接库,静态链接库和可执行文件,并将检查出的文件与工具内置的依赖文件列表进行比较匹配,从而为用户提供迁移建议Jar、war包扫描增强,识别鲲鹏平台已经支持的依赖,提供跟精准的依赖兼容替换建议 软件迁移评估流程 软件包重构识别分析用户X86软件包构成,重构适用于鲲鹏平台软件包,并输出重构报告 转型软件迁移一键下载开源代码,并编译,构建为鲲鹏软件包支持大数据、数据库、Web、高性能计算等场景主流软件的一键编译迁移 内存一致静态检查,检查用户软件迁移到鲲鹏平台可能存在的弱内存序问题并修复自动修复功能,更新用户使用的GCC编译器,在用户软件的编译过程中自动完成弱内存序问题的修复支持下载静态检查工具产生的BC文件,可根据实际需要进行BC文件扫描分析静态检查工具,编译器自动修复工具组合使用,可一键式修复支持自动生成目标工程的中间文件,不需要用户修改编译脚本
-
基于鲲鹏的Monkey补丁迁移 关于Monkey是基于Go 语言编写的一种补丁,它能在程序运行时插入汇编指令,使得调用目标函数时可以重定向到替换函数执行,同时能够取消插入的汇编指令,恢复对目标函数的正常调用。例如:有两函数 a, b,它们的实现分别如下:由图可知,a函数实现了一个 从 0到10的累加,b函数则直接返回一个 221在经过 monkey函数处理之后 monkey.patch(a, b)最后执行a函数,发现并没有执行从 0 到 10的累加,而是直接返回了221,也即最后执行的是b函数,起到了一个偷梁换柱的效果。Monkey设计当初是为x86架构设计的,未考虑Arm架构,且在 2015年停止了后续更新,即后续不会再推出适配Arm的版本。 项目背景某客户自研软件用到猴子补丁库 – Monkey, 软件在编译过程中报错,堆栈信息显示,编译过程中出现了不合法的地址:问题定位用devle工具进行单步调试追踪发现,在Monkey补丁的根目录下的monkey_386 .go 和monkey_amd64.go 文件中,均出现了x86的机器码:完整的机器码:问题根因:Monkey强行将x86的机器码硬编码进了程序中,从而导致在Arm架构的机器编译时无法正确识别,引发报错。 迁移思路迁移难点:从根因可知,Monkey的迁移,大概分为五个步骤:解构 go代码,将移位(byte(to >> XXXXXX))翻译成对应的机器码(16进制);翻译机器码,找到其对应的x86汇编代码,例如 0xFF,0x22 对应 jmp QWORD PTR [rdx] 将x86的汇编代码,翻译成Arm架构下所对应的汇编代码将Arm架构下的汇编代码,翻译成对应的Arm的机器码将Arm的机器码,改写成go对应的语句,并重新编译验证迁移思路: 迁移思路:Devkit工具扫描,看迁移建议。Exager: 将客户软件直接运行在Exager上直面问题,按五步法走 Devkit扫描:工具未给出任何迁移建议Exager 与 宿主机共用同一内核,pagesize为64K, 而原生 x86的go,是运行在4k上的,故go无法在 Exager上运行,除非修改pagesize为4k,但pagesize的修改有可能导致软件性能下降,客户不接受此方案。只能采用五步法为了更好地实施五步法,我们还需要了解Monkey的整体设计思路,做好全局的把控, Monkey的设计现有如下程序写法:执行变量f,发现执行的是a函数,是因为,a函数把其地址,赋值给了变量f对上面代码进行反汇编,可以发现:main.a.f加载到寄存器rdx里,然后把rdx寄存器指向的地址存入rbx里,最后调用。函数的地址值总是会加载到rdx寄存器里面,当代码调用的时候可以用来加载一些可能会用到的额外信息。回到开头,两函数,a, b 如何实现,调用a,实际上调用b?我们需要修改函数a,让它跳转到b的代码,跳过执行它自己的代码。实际上,我们需要通过这种方法来实现替换,加载函数b到寄存器rdx,然后执行时跳转到rdx上面。对应的x86汇编代码与机器码如下:其中:mov rdx是固定的,对应 48 C7 C2main.b.f 为函数地址,作为立即数,是动态变化的,用??取代,当立即数为0时,即为00,最大有8组,即64位立即数jmp [rdx] 也是固定的,对应的机器码为 FF 22因此我们可以得到如下类似函数:由此可知,动态变化的机器码,即为b函数地址,我们需要做的,就是将地址作为立即数,赋值给 rdx. X86与Arm的汇编差异了解了Monkey的设计思路后,我们再回看根目录下的 monkey_amd.64很明显,问题的关键,在于如何把to,进行拆分,并赋值给寄存器。X86的汇编很直接,寄存器能一口气存下 最大64位的立即数:即 movabs rdx, XX XX XX XX.. xx 每两个一组,每组8位,一共8组若立即数不够 64位,后面补0即可。即一个16位立即数,最后赋值会是 movabs rdx, xx xx 00 00 00 00 00 00 Arm则不通,最多一次只能存下 16位立即数,若立即数大于16位,比如64位,则必须拆分为4段。例如,现有立即数:0x8877665544332211对应机器码为 :1000100001110111011001100101010101000100001100110010001000010001从高位进行拆分,每16位拆分一段:第一段:1000100001110111011001100101010101000100001100110010001000010001即为:0x2211第二段:1000100001110111011001100101010101000100001100110010001000010001即为:0x4433第三段:1000100001110111011001100101010101000100001100110010001000010001即为:0x6655第四段:1000100001110111011001100101010101000100001100110010001000010001即为:0x8877综上,把一个0x8877665544332211 的立即数,赋值给一个寄存器(例如x10)最后的写法如下: 迁移实施综上。我们根据五步法,进行迁移尝试。第一步:解构 go代码,将移位(byte(to >> XXXXXX))翻译成对应的机器码(16进制)to是b函数地址,这里我们假设,其数值为0x8877665544332211 第二步:翻译机器码,找到其对应的x86汇编代码0x48, 0xBA, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,0x77,0x88对应汇编:movabs rdx, 0x88776655443322110xFF, 0x22对应汇编:jmp QWORD PTR [rdx] 第三步:将x86的汇编代码,翻译成Arm架构下所对应的汇编代码movabs 我们用 mov movk 对应jmp 我们用 ldr 和 br进行对应故翻译过来就是:第四步:将Arm架构下的汇编代码,翻译成对应的Arm的机器码第五步:将Arm的机器码,改写成go对应的语句,并重新编译验证这两句是固定不变的0x4b, 0x01, 0x40, 0xf90x60, 0x01, 0x1f, 0xd6问题在于,如何将变化的部分,转换成go的代码呢?从机器码可以看出,变化的部分,仅仅只是红框框起来的部分,黑色部分是不变的。我们拿第一行进行举例:mov x10, 0x2211 11010010100001000100001000101010我们将红色部分归零 11010010100000000000000000001010对应的16进制是:0xd280000a我们要做的,把0想象成一个个坑,我们要做的就是填坑,如何把0x2211 填到坑里面去从图中可知,头尾两部分,11010010100 和01010是固定不变的,需要保留,我们可以将0x2211 左移五位,把空位都留出来。即:00000000000001000100001000100000最后与 0xd280000a 相或即可,0与0相或为0, 0与1相或为1, 1与1相或也为1,这样,就做到了一个填坑的效果写成代码,即: Arm64的cache一致性要让变更的机器码发挥作用,需要对内存进行修改,我们可以看到在后续的操作中。monkey通过copyToLocation 进行了内存修改操作X86架构从硬件方面保证了cache一致性,而Arm架构则需要通过软件进行保证,对内存修改操作后,需要对cache进行刷新,保证cache一致性。因此,我们需要在 copyToLocation中,进行cache刷新。修改copyToLocation函数如下:func copyToLocation(location uintptr, data []byte) { f := rawMemoryAccess(location, len(data)) mprotectCrossPage(location, len(data), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC) paddr := (*C.char)(&f[0]) copy(f, data[:]) C.dcache_flush(paddr, 64) mprotectCrossPage(location, len(data), syscall.PROT_READ|syscall.PROT_EXEC)} 函数汇编指令过短问题从前面的论述我们得知,由于两种架构汇编指令得差异,同样的修改效果,x86架构所需汇编指令2条,Arm需要6条测试得知,当被替换函数过于简单,本身汇编指令条数小于6条时,且替换函数与被替换函数相邻时,会发生指令覆盖踩踏,导致错误。例如,有如下两个函数:no 是被替换函数,yes是替换函数它们的汇编指令如下:可以看到,no函数的指令条数是4, 小于6条当用monkey进行修改时,会发生如下现象no的汇编指令全被6条指令覆盖了,no本身只有4条,多出来的2条,把yes本身的前两条汇编指令也覆盖了,也即,yes函数的完整性遭到了破坏,因此br跳转时,会找不到yes函数的首地址,引发报错。规避措施:对于不在场景限制里的情况,无须考虑任何措施;对于处在场景限制里的情况,考虑对被替换函数增加空函数调用,如图1-11所示。
-
基于鲲鹏的Monkey补丁迁移 关于Monkey是基于Go 语言编写的一种补丁,它能在程序运行时插入汇编指令,使得调用目标函数时可以重定向到替换函数执行,同时能够取消插入的汇编指令,恢复对目标函数的正常调用。例如:有两函数 a, b,它们的实现分别如下:由图可知,a函数实现了一个 从 0到10的累加,b函数则直接返回一个 221在经过 monkey函数处理之后 monkey.patch(a, b)最后执行a函数,发现并没有执行从 0 到 10的累加,而是直接返回了221,也即最后执行的是b函数,起到了一个偷梁换柱的效果。Monkey设计当初是为x86架构设计的,未考虑Arm架构,且在 2015年停止了后续更新,即后续不会再推出适配Arm的版本。 项目背景某客户自研软件用到猴子补丁库 – Monkey, 软件在编译过程中报错,堆栈信息显示,编译过程中出现了不合法的地址:问题定位用devle工具进行单步调试追踪发现,在Monkey补丁的根目录下的monkey_386 .go 和monkey_amd64.go 文件中,均出现了x86的机器码:完整的机器码:问题根因:Monkey强行将x86的机器码硬编码进了程序中,从而导致在Arm架构的机器编译时无法正确识别,引发报错。 迁移思路迁移难点:从根因可知,Monkey的迁移,大概分为五个步骤:解构 go代码,将移位(byte(to >> XXXXXX))翻译成对应的机器码(16进制);翻译机器码,找到其对应的x86汇编代码,例如 0xFF,0x22 对应 jmp QWORD PTR [rdx] 将x86的汇编代码,翻译成Arm架构下所对应的汇编代码将Arm架构下的汇编代码,翻译成对应的Arm的机器码将Arm的机器码,改写成go对应的语句,并重新编译验证迁移思路: 迁移思路:Devkit工具扫描,看迁移建议。Exager: 将客户软件直接运行在Exager上直面问题,按五步法走 Devkit扫描:工具未给出任何迁移建议Exager 与 宿主机共用同一内核,pagesize为64K, 而原生 x86的go,是运行在4k上的,故go无法在 Exager上运行,除非修改pagesize为4k,但pagesize的修改有可能导致软件性能下降,客户不接受此方案。只能采用五步法为了更好地实施五步法,我们还需要了解Monkey的整体设计思路,做好全局的把控, Monkey的设计现有如下程序写法:执行变量f,发现执行的是a函数,是因为,a函数把其地址,赋值给了变量f对上面代码进行反汇编,可以发现:main.a.f加载到寄存器rdx里,然后把rdx寄存器指向的地址存入rbx里,最后调用。函数的地址值总是会加载到rdx寄存器里面,当代码调用的时候可以用来加载一些可能会用到的额外信息。回到开头,两函数,a, b 如何实现,调用a,实际上调用b?我们需要修改函数a,让它跳转到b的代码,跳过执行它自己的代码。实际上,我们需要通过这种方法来实现替换,加载函数b到寄存器rdx,然后执行时跳转到rdx上面。对应的x86汇编代码与机器码如下:其中:mov rdx是固定的,对应 48 C7 C2main.b.f 为函数地址,作为立即数,是动态变化的,用??取代,当立即数为0时,即为00,最大有8组,即64位立即数jmp [rdx] 也是固定的,对应的机器码为 FF 22因此我们可以得到如下类似函数:由此可知,动态变化的机器码,即为b函数地址,我们需要做的,就是将地址作为立即数,赋值给 rdx. X86与Arm的汇编差异了解了Monkey的设计思路后,我们再回看根目录下的 monkey_amd.64很明显,问题的关键,在于如何把to,进行拆分,并赋值给寄存器。X86的汇编很直接,寄存器能一口气存下 最大64位的立即数:即 movabs rdx, XX XX XX XX.. xx 每两个一组,每组8位,一共8组若立即数不够 64位,后面补0即可。即一个16位立即数,最后赋值会是 movabs rdx, xx xx 00 00 00 00 00 00 Arm则不通,最多一次只能存下 16位立即数,若立即数大于16位,比如64位,则必须拆分为4段。例如,现有立即数:0x8877665544332211对应机器码为 :1000100001110111011001100101010101000100001100110010001000010001从高位进行拆分,每16位拆分一段:第一段:1000100001110111011001100101010101000100001100110010001000010001即为:0x2211第二段:1000100001110111011001100101010101000100001100110010001000010001即为:0x4433第三段:1000100001110111011001100101010101000100001100110010001000010001即为:0x6655第四段:1000100001110111011001100101010101000100001100110010001000010001即为:0x8877综上,把一个0x8877665544332211 的立即数,赋值给一个寄存器(例如x10)最后的写法如下: 迁移实施综上。我们根据五步法,进行迁移尝试。第一步:解构 go代码,将移位(byte(to >> XXXXXX))翻译成对应的机器码(16进制)to是b函数地址,这里我们假设,其数值为0x8877665544332211 第二步:翻译机器码,找到其对应的x86汇编代码0x48, 0xBA, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,0x77,0x88对应汇编:movabs rdx, 0x88776655443322110xFF, 0x22对应汇编:jmp QWORD PTR [rdx] 第三步:将x86的汇编代码,翻译成Arm架构下所对应的汇编代码movabs 我们用 mov movk 对应jmp 我们用 ldr 和 br进行对应故翻译过来就是:第四步:将Arm架构下的汇编代码,翻译成对应的Arm的机器码第五步:将Arm的机器码,改写成go对应的语句,并重新编译验证这两句是固定不变的0x4b, 0x01, 0x40, 0xf90x60, 0x01, 0x1f, 0xd6问题在于,如何将变化的部分,转换成go的代码呢?从机器码可以看出,变化的部分,仅仅只是红框框起来的部分,黑色部分是不变的。我们拿第一行进行举例:mov x10, 0x2211 11010010100001000100001000101010我们将红色部分归零 11010010100000000000000000001010对应的16进制是:0xd280000a我们要做的,把0想象成一个个坑,我们要做的就是填坑,如何把0x2211 填到坑里面去从图中可知,头尾两部分,11010010100 和01010是固定不变的,需要保留,我们可以将0x2211 左移五位,把空位都留出来。即:00000000000001000100001000100000最后与 0xd280000a 相或即可,0与0相或为0, 0与1相或为1, 1与1相或也为1,这样,就做到了一个填坑的效果写成代码,即: Arm64的cache一致性要让变更的机器码发挥作用,需要对内存进行修改,我们可以看到在后续的操作中。monkey通过copyToLocation 进行了内存修改操作X86架构从硬件方面保证了cache一致性,而Arm架构则需要通过软件进行保证,对内存修改操作后,需要对cache进行刷新,保证cache一致性。因此,我们需要在 copyToLocation中,进行cache刷新。修改copyToLocation函数如下:func copyToLocation(location uintptr, data []byte) { f := rawMemoryAccess(location, len(data)) mprotectCrossPage(location, len(data), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC) paddr := (*C.char)(&f[0]) copy(f, data[:]) C.dcache_flush(paddr, 64) mprotectCrossPage(location, len(data), syscall.PROT_READ|syscall.PROT_EXEC)} 函数汇编指令过短问题从前面的论述我们得知,由于两种架构汇编指令得差异,同样的修改效果,x86架构所需汇编指令2条,Arm需要6条测试得知,当被替换函数过于简单,本身汇编指令条数小于6条时,且替换函数与被替换函数相邻时,会发生指令覆盖踩踏,导致错误。例如,有如下两个函数:no 是被替换函数,yes是替换函数它们的汇编指令如下:可以看到,no函数的指令条数是4, 小于6条当用monkey进行修改时,会发生如下现象no的汇编指令全被6条指令覆盖了,no本身只有4条,多出来的2条,把yes本身的前两条汇编指令也覆盖了,也即,yes函数的完整性遭到了破坏,因此br跳转时,会找不到yes函数的首地址,引发报错。规避措施:对于不在场景限制里的情况,无须考虑任何措施;对于处在场景限制里的情况,考虑对被替换函数增加空函数调用,如图1-11所示。
-
## 一、前言 这篇文章作为C语言基础知识点,介绍C语言常用的几个语句的用法、规则、使用案例。 **介绍的语句如下:** ```cpp if..else 判断语句 for循环语句 while循环语句 do..while循环语句 switch 语句 goto 语句 return 语句 break 语句 continue 语句 ``` 第二章介绍语法使用规则、使用案例,第三章列出了一些练习题,用于结合第二章介绍的语句完成知识点巩固。 ## 二、知识点与案例代码 ### 2.1 if语句语法规则、使用案例 **if语句语法:** ```cpp //形式1 if(条件表达式>) //条件为真的时候执行 { ...执行的代码.. } //形式2 if(条件表达式>) //条件为真的时候执行 { ...执行的代码.. } else //条件为假的时候执行 (else的语句块可以选择的,可以写也可以不写) { } //形式3 if(条件表达式>) //条件为真的时候执行 { ...执行的代码.. } else if(条件表达式>) //条件为假的时候执行 (else的语句块可以选择的,可以写也可以不写) { ...执行的代码.. } else if(条件表达式>) { ...执行的代码.. } ........ 条件表达式> 里可以写什么代码? 写的语句执行完必须返回具体的结果。 比如:12>34 比如: if(printf("12345")) ``` **使用案例** ```cpp #include int main(int argc,char *argv[]) { int a=100; int b=20; int c=30; if(a>b) { printf("a>b\n"); } if(a>b)printf("a>b\n"); //容易出错的地方 if(a==0) //if(a=0) { printf("a>b a>c\n"); } //容易出错的地方 if(a&&b)//if(a&0) { printf("a>b a>c\n"); } return 0; } /* 逗号、分号 逗号:间隔符号。 比如: int a,b,c,d; ! 逻辑非 && 逻辑与 -- 并且 || 逻辑或 --或者 & 按位与(3&1)、取地址(&a) * 乘号(a*b) 、指针(*a)、 *(块注释) */ #include int main(int argc,char *argv[]) { int year; printf("输入年份:"); scanf("%d",&year); //判断平年和闰年 条件:能被4整除并且不能被100整除 条件2:能被400整除 if((year%4==0 && year%100!=0) || (year%400==0)) { printf("闰年\n"); } else { printf("平年\n"); } //判断平年和闰年 条件:能被4整除并且不能被100整除 条件2:能被400整除 if(year%4==0 && year%100!=0) { printf("闰年\n"); } else if(year%400==0) { printf("闰年\n"); } else { printf("平年\n"); } return 0; } ``` ### 2.2 while、do..while语句 ```cpp //循环语句 while(条件表达式>) { ...执行的代码.. } do { ...执行的代码.. }while(条件表达式>); #include int main(int argc,char *argv[]) { int a=5; int b=5; //循环语句 a-- 、a=a-1、a-=1; while(a--) { printf("a=%d\n",a);//4\3\2\1\0 } //循环语句 do { printf("a=%d\n",b);//\5\4\3\2\1\0 }while(b--); return 0; } ``` ### 2.3 for语句 ```cpp #include int main(int argc,char *argv[]) { int a=0; //写法1 for(初始化语句>;条件表达式>;自增/自减>) { } 比如: for(a=0;a10;a++) { } //写法2 for(;条件表达式>;自增/自减>) { } 比如: for(;a10;a++) { } //写法3 for(;;) //条件永远为真 { } 等价于 while(1)//条件永远为真 { } return 0; } #include int main(int argc,char *argv[]) { //打印99乘法口诀表 int i,j; for(i=1;i=9;i++) { for(j=1;j=i;j++) { printf("%dx%d=%2d ",i,j,i*j); } printf("\n"); } return 0; } /* 1x1=1 2x1=1 2x2=4.... 3x1=1 3x2=6 3x3=9 ..... ................ */ ``` ### 2.4 break语句 作用: 跳出当前的一层循环,或者跳出switch语句。 ```cpp #include int main(int argc,char *argv[]) { int i,j,cnt=1; for(i=0;i5;i++) { for(j=0;j10;j++) { if(j==5) { break; //跳出当前一层循环 } printf("cnt=%d\n",cnt++); //25 } } return 0; } ``` ### 2.5 goto跳转语句 可以在函数内设置标签,任意跳转。 ```cpp #include int main(int argc,char *argv[]) { int i,j,cnt=1; for(i=0;i5;i++) { for(j=0;j10;j++) { if(j==5) { goto AA; //跳转语句 } printf("cnt=%d\n",cnt++); //5 } } AA: printf("程序执行完毕.\n"); return 0; } ``` ### 2.6 continue语句 跳出本次循环,继续执行下一次新的循环。 ```cpp #include int main(int argc,char *argv[]) { int i,j,cnt=1; for(i=0;i5;i++) { for(j=0;j10;j++) { if(j==5) { continue; //跳出当前一次循环 } printf("cnt=%d\n",cnt++); // } } return 0; } ``` ### 2.7 switch 分支语句(C89标准和C99标准) ```cpp #include int main(int argc,char *argv[]) { int a; scanf("%d",&a); switch(a) { case 1: printf("选择1.\n"); break; case 2: printf("选择2.\n"); break; case 3: printf("选择3.\n"); break; case 4: case 5: case 6: printf("选择4,5,6\n"); break; default: printf("选择默认值.\n"); break; } return 0; } ``` ## 三、练习题 ### 3.1 输入整数,然后使用二进制方式输出(数据传输) ```cpp 0x23; ---8次 00100011 #include int main() { unsigned int a,i; int flag=0; printf("输入整数:"); scanf("%d",&a); for(i=0;i32;i++) { //if(a&0x1) //从低位开始判断 //if(a&0x80000000) //从高位开始判断 if(a&(131)) //从高位开始判断 { flag=1; printf("%d",1); } else { if(flag) { printf("%d",0); } } a=1; //把a的左边移动 (高位溢出、低位补0) } printf("\n"); return 0; } ``` ### 3.2 打印所有水仙花数。 所谓水仙花是指一个三位数,其各位数字的立方和等于该数 ```cpp #include int main() { int i; int a, b, c; for (i = 100; i = 999; i++) { a = i / 100; b = i % 100 / 10; c = i % 10 / 1; if ((a*a*a + b*b*b + c*c*c) == i) { printf("%d ", i); } } return 0; } ``` ### 3.3 百元买百鸡 公鸡每只 5 元,母鸡每只 3 元,小鸡 3 只一元,问一百元买一百只鸡有几种买法。 ```cpp #include int main() { int m, g, x; int m_max; int g_max; int x_max; int q, cnt; //cnt=数量 q:钱 printf("公鸡每只 5 元,母鸡每只 3 元,小鸡 3 只一元\n"); printf("请输入要买鸡的数量:\n"); scanf("%d", &cnt);// 100 100 printf("请输入要买鸡的钱:\n"); scanf("%d", &q);// 100 100 /*1. 判断输入的数据是否合理*/ if (q>0 && cnt>0) { m_max = cnt / 3; // 母鸡 100 /3 =33 g_max = cnt / 5; // 公鸡 100 /5 =20 x_max = (cnt / 1) * 3; // 小鸡 100 /1*3 =300 for (g = 0; g/ 循环判断可能 { for (m = 0; m/总数量-公鸡数量-母鸡数量 =小鸡的数量 if (x + g + m == cnt && x / 3 + g * 5 + m * 3 == q) //买鸡的总数等于总鸡数和总钱数等于买鸡的钱数就输出 { printf("公鸡=%d\t", g); printf("母鸡=%d\t", m); printf("小鸡=%d\t\n", x); } } } } else printf("输入有误\n"); return 0; } ``` ### 3.4 打印正三角形 ```cpp #include /* 1 121 12321 1234321 */ int main() { int i, j; int len; scanf("%d", &len); for (i = 1; i = len; i++) //总行数 { /*1. 打印空格*/ for (j = 1; j = len - i; j++) { printf(" "); } /*2. 打印前面一半*/ for (j = 1; j = i; j++) { printf("%d", j); } /*3. 打印后面一半*/ for (j = i - 1; j >= 1; j--) { printf("%d", j); } printf("\n"); } return 0; } ``` ### 3.5 打印倒三角形 ```cpp #include int main() { int i, j; int len; scanf("%d", &len); for (i = len; i >= 1; i--) //循环的总次数 { /*1. 打印空格*/ for (j = len; j>i; j--) { printf(" "); } /*2. 打印前面部分*/ for (j = 1; j = i; j++) { printf("%d", j); } /*3. 打印后半部分*/ for (j = i - 1; j >= 1; j--) { printf("%d", j); } printf("\n"); } return 0; } ```
-
1)SIMD 指令迁移。SSE、AVX 都是 x86 平台的 SIMD 指令,SIMD 是单指令多数据流的简称,可用来加快计算速度。鲲鹏平台也有自己的 SIMD 指令,名为 Neon。x86 平台下的 SIMD 指令迁移到鲲鹏平台时,只需找到对应的函数替换实现即可。实践中一般不会逐条迁移,更推荐采用开源工程的移植途径。例如华为开源了一个工程,其中实现了大部分 SIMD 指令的替换。实践移植时,只需将开源头文件放到应用目录下引用即可无感知迁移,非常方便。2)编译选项移植。例如应用编译成 64 位时,x86 平台上使用 m64 选项,鲲鹏上则是 mabi=lp64,根据编译文档将有架构差异的编译选项对等替换即可。3)内联汇编移植。内联汇编移植时有两种方法,一种是通过汇编指令一对一替换,关键在于指令到指令的替换思想;另一种是通过 builtin 函数替换,根据指令的功能选择对应的 builtin 函数。4)编译宏移植。编译宏是用来识别架构平台并选择对应代码分支的工具。编译器自带的编译宏在 x86 下只有两个,到鲲鹏平台时直接替换即可;用户自定义的编译宏也只需根据自己的写法自定义替换。但编译宏替换后,如果没有 ARM 对应的源码分支,就需要实现对应的功能。5)builtin 函数移植。builtin 函数是编译器内部实现的函数,需要迁移的不多,主要关注 CRC32 校验值计算的几个函数。
-
# 90%代码如何移植到鲲鹏平台上 介绍一下我名字叫张汝涛,现供职于华为智能计算,设计架构与设计。主要是负责相关软件产品的架构设计以及功能开发。今天要讲的主题是关于软件迁移这一件事,是一个久远的话题。因为但凡是牵扯到切换平台、CPU架构的变化,甚至一些语言版本的升级,我们都可能会面临到一些软件迁移的问题。今天就借着一个小时的时间,我们想探讨一下软件移植过程的原理,以及软件工程的相应的过程。 ![图片1.png](https://bbs-img.huaweicloud.com/blogs/img/1582541819452325.png) 接下来要讲一下我们鲲鹏在软件移植的过程当中,如何去帮助开发者去提升效率,如何把我们鲲鹏沉淀下来的,把华为沉淀下来的软件开发以及一致的经验如何反馈到开发者这里,让开发者能够加速软件开发的进度,降低成本。接下来我们会推出我们鲲鹏的开发套件,帮助用户做软件的移植,以及做基于鲲鹏平台的性能加速,今天主要是这样三个内容,我们下来就开始。 ![图片2.png](https://bbs-img.huaweicloud.com/blogs/img/1582541836497082.png) 其实一提到软件移植,我不知道大家有多少是做了比较底层软件的,做底层软件的话,大家可能会用到一些汇编,C++加这样的底层语言。用到这种底层语言,它是会和机器的硬件架构强相关的,当你在从一个平台切换到另外一个平台的时候,这些强相关的语言势必要进行一次,跟我们所采用的编程语言以及移植的平台环境强相关。当我们用汇编代码或者是用这种编译型语言的时候,我们就会面临着一些移植的问题、一些挑战,有些问题可能通过编译器能解决,有些问题特别是一些低阶的代码或者比较低级的一些代码,底层的代码不能说低阶,我们就会可能要面临着必须要手工去查手册,然后去把它相应的去转换到新平台所使用的机器码。 ![图片3.png](https://bbs-img.huaweicloud.com/blogs/img/1582541850500579.png) 这里就列出了一个我们鲲鹏处理器和x86处理器的一个指令差异,这里列了一个简单的两个数相加,两个int型相加的这样一个简单程序。通过GCC编译完之后,我们去通过OMGD,我们就能看到指令的具体的格式形式以及相应的对应的汇编代码。这里我们能看到我们看到对应x86平台而言,因为x86是CICS指令是复杂指令集,鲲鹏是完全兼容Arm64架构的,是我们华为自研的vinic,我们的指令集也是和Arm64副精简指令集是完全兼容的。 这里插一下,讲一下背景,其实所谓的经限指定集和复杂指令集的区分是从上个世纪的70年代开始的,IBM曾经做过一个研究,就是关于说CPU如何去高效的运行,然后他们会发现可能有些常用的指令或者是程序代码,当时背景下常用的程序代码,可能常用的和不常用的有很大的差异,当时又因为IC的制程或者工艺或者器件的设计水平没有现在这么突飞猛进,所以就会想如何去把CPU从硬件设计上来讲要简单一点,从软件上来讲高效一点,所以他们就推出了提出了精简指令集这么一个概念,其最大的特点一个显著的特点就是它的指令宽度就是长度,我们说的指令长度,是相等的,也就说每一个指令这个位宽是相等的,么每个指令执行的SICO几乎也相同,就是他把很繁杂的事情做的尽可能的简单,然后用很多简单的操作去完成一件复杂的任务。 从相反的复杂指令集的角度,我们看一下x86下面的复杂指令级,它每一个指定的长度是不同的,也就是说像这里列举的mov和add这两个指令,它的机器码、指令码是不同的,长度是不同的,势必会造成我们IC器件的解码器,以及包括我们真正的到软件流水操作上处理的步骤是不一样的,也必然会导致我们每条指令的执行的周期是不同的,但是这样也有一个好处,就是我一个指令就能完成一个比较复杂的事情,尽管说我的指令可能会变得很长,但是我一条指令能完成一比较复杂的事情,对上层的程序员来讲,可能就便于理解或者是相对的会容易理解一些。 这就是精简指令集和复杂指令集的一个简单背景,这里边我们能看到,在我们反汇编下来的x86指令集和我们的鲲鹏指令集的汇编代码上,我们看到我们的操作指令是完全不同的,我们的计存器的命名也是完全不同的,在x86的平台上,我们有16个通用寄存器,我们讲的是x86 64模式下,我们有16个通用寄存器,我们浮点计存器,根据我们支持的MMX技术或者是SSE或者是ABS技术,我们x86平台,x86 44的平台,最多可以存在32个浮点寄存器。 反观我们鲲鹏平台,因为我们是和Arm64指令兼容的,所以指令集要事完全对照,所以从这个角度来讲,我们鲲鹏平台有31个通用寄存器,除了这31个通用寄存器以外,我们还有一些状态寄存器或者是一个站寄存器,那对应到浮点寄存器,我们就有32个这样一个叫做ASMB的的advances单指令多数据这样一个寄存器,我们有32个寄存器位,位宽是128。这一点是和x86 64平台是有差异的,比如说现在x8664,它如果支持ABX512的话,它的位宽是500 12比特,从这个角度上,是一个硬件器件差异是非常明显的。 然后从反汇编的角度来讲,大家不知道有没有注意到x86平台上有一个mov指令。从第一行我们能看到从寄存器、rbp一个mov的存储数据,到EDX这样一个寄存器,做一个从把变量从内存里漏斗进来。同样的一件事情在上面的鲲鹏处理器平台上寄存器的指令就变成了LDR和然后下面当然加法还是有add的,然后存储的时候对于x86来讲,又是从寄存器mov到了内存力,但是对于鲲鹏平台它是用一个str指令,所以这也反映出了一个risk指令的特点,也许是第2个特点,我们姑且这么叫,它是用一个load stall这样一个模式,也就是说在鲲鹏处理器平台上不支持内存到内存的一个直接访问,必须要经过一个寄存器作为桥接作为一个中转。 这一点是和x86指令复杂型指令集不同的另外一个地方。还有就是在x86这个平台上,它的内存访问的模式非常多,对于公共平台上就没有丰富了。这个就是以一个程序为例,我们简单列举一下,从我们CPU的角度来看,同样是一段C代码,CPU他做了不同的事情,执行了不同的指令,经过不同的周期不同的运算以后,它会输出最终计算的一个结果。当然从这个角度来讲,从这段程序两个平台是没有任何差异的,除了指令上以外,执行结果是不会有任何变化。 但这里也就侧面反应出来了,因为指令集不一样,所以对于C,C++这样偏底层的这样一个语言来讲,虽然它是个高级语言,但是我们必须要考虑一个平台差异,在我们平台切换的时候,甚至在我们平台这个软件的编制过程当中,我们要考虑一个平台兼容性,所以要养成一个良好的编程习惯。 ![图片4.png](https://bbs-img.huaweicloud.com/blogs/img/1582541881376535.png) 跨平台移植软件要面临的不少问题,因为软件移植本身就是一个工程性问题。我们这里通常第1步来讲,如果说我们决定从x86平台迁移到鲲鹏平台,我们就要去判断一下这个软件迁移值不值得,困难有多大?我们通常目前的常用的做法就是我们把x86的平台,相应的软件包拿下来,然后去看看它的依赖性关系。 他这个叫什么意思呢?就是我们看看这个软件,如果跑在x86平台上,他依赖哪些第三方组件?这些第三方组件在你这个目标平台上存不存在要做一些这样的判断。这种判断通常都是这个平台之间的反反复复的安装,去运行,然后根据系统报出来的错误去一个个来排除,所以这都是通过人工来完成的,这个比较费劲,如果有移植经验的同学就会觉得比较费劲,有些事情很繁琐琐碎,然后一个不小心就错了,然后可能还找不出来了。 当你解决完第1步编译的过程的这个问题之后,你可能会还碰到一些跑过,结果新平台上出现了function fault。功能性错误我们就很讨厌了,可能是原因比较多,有的是本身软件逻辑有问题,第2个可能是第三方组件的APA跨平台兼容性有问题,第3个可能是系统本身支持度也有问题,这个就是影响因素比较多,这样子就需要移植之后,技术人员去相应定位,定位对每个人对相应的工程人员来讲,专业技术要求会比较高,也存在着一个反复编译、反复调整、反复验证,这个过程成本会很高。 当你完成了功能验证之后,你说跑过一些基本测试以后,你觉得这个软件在新平台上可以刊用了,你可能会面临的一个性能的问题,当你用在工作环境、生产环境的情况下,因为生产环境的软件都希望用最小的硬件跑出最大的性能,然后跑出最高的一个性价比,这时候都会对软件性能上的需求,对他有要求。这个时候我们就会不得不去采取一些方法,例如用一些商业软件也好,或者一些开源的软件命令也好,去分析这个软件的瓶颈到底是哪里有问题?是系统有配置的参数有问题,还是我软件本身逻辑有问题? 所以这三步是我们在华为的软件这么多年的开发过程当中积累下来觉得比较重要的三步,对我们软件的质量、移植的质量是有决定性影响的。这三步也同时对于任何人来讲,可能都不是一个能轻松逾越的几个障碍。 ![图片5.png](https://bbs-img.huaweicloud.com/blogs/img/1582541891753565.png) 再稍微解释一下,对于我们软件移植这件事情,通常我们讲的是对于编译型软件会面临这样的一个困难,对于解释性反而比较轻松,为什么?比如像我们现在常用的一些Java的或者Python的,甚至一些GOD这样一些软件,我们的依赖是什么?依赖的是语言所提供的虚拟运行环境,甚至是一些像Java提供的Java虚拟机GUM,我们只需要选一相应平台的GUM安装,我们就能把底层的所有差异性都屏蔽掉。 这个软件只根据运行环境去跑,通常是没有问题的。对于像C,C++,GOD这种的,可能作为编译,甚至说可能会调用C,C++加这种组件的这种软件,我们就需要C,C++这种代码进行移植,我们分这么几种情况。 第1种是开源软件,对我们通常是和社区进行合作,让社区去支持空洞平台,或者是支持M64的平台,这样我们就一劳永逸的解决问题。然后对于自研软件,对于有些SB用户会开发者资源软件,他不能开放代码,我们就需要进行商业合作,去引导客户去移植到我们鲲鹏平台上。 对于商业B软件,我们最典型的,比如说像微软的一系列软件,或者是orico的软件数据库,我们不可能去获得源码,可能去推动他们和我们中国的软件界合作,可能也非易事,这个时候你只能找到要么是合作,要么就是找一个替代方案,对吧?如果实在是又不能替换用户的业务,又不能去修改,我们就可能不得已采取一个鲲鹏平台和x86进行一些混合部署,这是一个软件部署方面的策略。 还有一种就是对于我们常用的windows平台的一种系列开发,我们也知道windows虽然一年多前可能说要支持Arm64这个架构,但实际上到现在为止他也没有宣布。其实商业上的考虑或者是其他的因素可能都考虑的比较多,尤其是这样一个大体量的公司,但是对于windows平台就是说我们进行有限度的在开元生态里面进行有限度的支持,比如说像微软的C shut,其实他的call3.0已经开源了,已经在Arm平台上能够用起来了。换句话来讲,我们也可以在鲲鹏平台上基于call3.0支持C shut。对于我们鲲鹏软件移植的过程,我们把它分解为这样几个步骤流程,其中最重要的就是我们这里所列到的第2步第3步以及性能达标分析这一步,我们现在提供了相应的每一步提供一些的辅助工具去帮助客户进行用户开发者进行分析进行移植。 ![图片6.png](https://bbs-img.huaweicloud.com/blogs/img/1582541907369304.png) 其中的二进制文件依赖扫描,是我们去提供了一个工序软件进行软件安装、依赖库的扫描和软件运行依赖库的扫描。根据我们长期积累的有一个兼容性清单,这个兼容性清单覆盖了市面上大多数流行的以及常用的OS以及相应的版本,还有相应的GCC的版本,对于移植的第二阶段,像移植修改C,C++原码,我们也同样提供了一款工具去做C,C++源码的分析,这个分析主要是集中在这样几个方面,集中在汇编代码、边选项,还有宏定义,还有built in函数和编辑提供的built in函数和attribute,然后去重点检查用户的Makefile和CMakeList。如果用户软件是用make构建的或者CMake构建的,我们能帮助去发现一些,识别一些移植中需要修改的地方,同时我们会给出移植修改的建议。 当移植完成之后,我们会提供一个性能分析的工具,去帮助用户去check这个软件是不是能够达到工作这样一个标准,也就是说check它的性能指标,我们会去进行系统性的性能分析,也会去做软件级的热点定位分析。然后在此基础上我们会给用户提供一些华为所积累下来的认为比较有效的一些软件优化的方法,做一些比如说终端版壳操作,甚至一些其他的软件修改的这样建议,这个就是我们今天要介绍的三款软件,通过这三款软件我们就能比较方便的或者比较高效率的完成C,C++代码,从非鲲鹏平台向鲲鹏平台的这样一个迁移值的过程。 ![图片7.png](https://bbs-img.huaweicloud.com/blogs/img/1582541962501754.png) 在C,C++软件移植的过程当中,我们要着重考虑三个方面的问题,第1个问题是软件构建文件的差异。这里面举两个例子,一种是咱们这个方案里面,我们可能在x86平台上常看到一个叫-M64的这样一个知道编译选项的option,这个含义,实际上是说要把我这个软件生成成为64位模式的。是分成64位模式的,我们编译目标代码的ABI。实际上在鲲鹏平台上,我们可以用类似的,我们可以用-mabi=lp64去来替换,当然如果安全的情况下,加上-FPIC就生成一个flowting的address,来屏蔽一些底层的相关依赖性,这样子就能达到一个编译选项M64的一个替换。 还有一个就是对应Arm指令集、SA的这样一个替换,我们常用的可能会见到一些-march的这样一个参数,在x86的平台上提供了多达二三十种架构平台,从intend到AMD的各种各样的,Arm平台来说,就相对简单一点,我们只需要去选用我们鲲鹏平台,你CPU所支持的兼容Arm的架构。我们鲲鹏920,我们进入的是AArm8.2-a这样一个架构。如果这些版本比较新,比如说9.1以上的,我们就可以去选用-mtune=tsv110。这实际上是我们泰山微内核110这个型号这里面会在Gcc内部进行了我们提了一些措施,针对架构做的一些的public的tune优化,能够提供一个相对较好的性能。 性能增加,据说有5%~10%的性能提升。 接下来第二部分就是C,C++原码的移植,这里面也举两个例子,第1个例子是这个是基本数据类型,尽管说我们鲲鹏平台支持的是LP64,然后这个x86平台也支持LP64的这样一个规范,但是实际上大家在某些细节定义上还是有区别的,虽然字符宽度,比如说对x来讲都是8字节,但是x86他这个x是有符号类型的,但是对于我们鲲鹏平台,我们用是无符号类型的,但这块的改动我们就可以通过修改makefile里面,加一个参数,加上-makefilex,去把默认的无符号的x定义成有符号的x,这样就能保证C代码逻辑,关于x操作上不会引入歧义。 第2类问题就是我们编译器当中提供了多达数百个的这个宏定义,可以被我们C,C++软件识取,比如说我们用GC的话,我们可以在C,C++的软件里面,原文件里面直接去使用相应的宏定义,这个宏定义在编译的时候可能会我们的编译器直接做环境变量的check,然后直接设置了相应的正确的值,跟host环境相关的。我这里指编译和运行在同一款机器上,我们不讲host和target相异的情况。这个时候对于相应的软件,我们就可能需要区分一下宏定义,比如说像这里x86 64,显然一看就知道他是支持x86的,不可能在我们鲲鹏平台上运行,这时候我们就会建议用户去修改用户代码,用预编译的方式做软件范围的定义隔离,很显然对于我们鲲鹏平台,我们常用的关键字就是aarch64或者是Arm64,这样的关键字去做软件逻辑的定义,除了这些以外,包括BBC都有各自的架构定义关键字。 ![图片8.png](https://bbs-img.huaweicloud.com/blogs/img/1582541975636242.png) 第3类问题就是我们汇编代码的移植,这也是最头疼的一块,因为x86平台如果细算的话,他将有2100个不到的汇编指令,鲲鹏平台因为兼容Arm64,我们有1000出头,1100不到,这样一个汇编指令,其实这加起来3000多条指令,如果大家想把它分清楚,那是非常痛苦的。Int的相应指令集的手册有4000多页,Arm相关指令集的手册有7000多页,纯英文的文档大家读起来肯定会崩溃的,所以在这里面汇编代码的移植,这是一个难点。 汇编代码在我们的软件过程中表现有若干种形式,第1种是我们纯粹的就用Asm关键字去写汇编代码,第2种是我们用built in函数做一些替换,比如说这里举个例子,像GCC里提供了built in的CRC的32计算的一些加速指令,我们可以去寻找鲲鹏平台上的相应的指令去进行替换,比如说像x86平台上用到的预取的指令,我们也可以去找到鲲鹏平台,上的built in函数去做替换。接下来还有第3种,就是我们可能会用到的Intrisic。Intrisic实际上是在jcc里提供的像C语言可以一样去使用的汇编函数,引出这个Intrisic是在x86平台上和Arm64平台,就相差非常的大。 在x86的平台上Intrisic总数总数将近达到7000个,7000不到,然后在鲲鹏水平上相差就差的比较多,远远少于这个数,为什么?这是因为在x86平台上它支持的指令集比较多,他自己经过二三十年的演进,对吧?他有mx的指令集,有SSE的指令集,还有AVX,AVX也分了128比特的,256以及500 12比特的三种。 每一种它对应的Intrisic非常的多,所以移植的数量是非常大的。在这个里面我们可以找到一些,比如说对于一个28比特的操作进行一些对应,可以做一些替换。 针对上面提出的这些问题,比如说我们C,C++刚才提出这些问题,我们就提供了这样几个工具,我们这里提供了分析扫描工具,代码迁移工具。分析扫描工具,就是识别我们软件移植的依赖性,然后去帮助用户做兼容性的排查。然后第2个提供代码迁移的工具是做源码的构建工程工程构建文件,还有C,C++原码以及汇编代码的扫描移植指导。第三个工具就是性能优化工具,我们刚把软件移植到鲲鹏平台之后,我们需要去用这个工具去分析性能,去发现热点,我们也提供了基于鲲鹏平台的一个加速库这么一个概念,一个组件。 这里面就提供了一个软件硬件协同加速用户应用的一个方式。 ![图片9.png](https://bbs-img.huaweicloud.com/blogs/img/1582541991902921.png) 比如说我们这里优化了GDPC基础运行环境,我们优化的压缩、加密、加解密,包括一些数学计算这样一些开源的或者是三方的组建,我们优化了一些IPP信号处理的一些程序功能提升,就是用我们软硬结合的方式极大提升了性能。这里面我们大致分析的一个流程,我们会在分析扫描里面,我们把用户的软件上传到我们的工具环境下,我们工具环境就会分析用户X86平台上软件的安装包,比如说这里的RPM包还有一些JAR、Java类的程序,包括一些压缩包,我们就会去扫描识别里面软件包内部以及软件安装路径内,包括我们压缩包内部的集成的,比如说这些SO件、二进制文件,去检验它是否在鲲鹏平台上不同的操作系统上是否支持,去反馈用户一个一致性的分析报告,会逐个告诉用户SO是否兼容,不兼容的话怎么去处理?我们会提供链接是原码的值,这个是源码级的链接,或者是提供移植文档方式书的这种链接,都会在我们报告里提供出来。 ![图片10.png](https://bbs-img.huaweicloud.com/blogs/img/1582542026766302.png) 我们这个工具提供了两种工作方式,一种是我们通过命令行的方式,下面这种形式通过参数输入,一种是通过这种外部方式,我们在做了安装包的依赖性分析以及原码的扫描之后,会给用户产生一个移植分析指导的报告,这个报告是提供CVS的格式或者是HDM的格式,用户可以去下载,里面就会详细罗列出哪些依赖库,哪些二级制文件需要移植,然后哪些C,C++以及汇编代码,需要移植规模有多大? 会给用户呈现一个移植的工作量,比如以每月为单位提供一个工作量。 ![图片11.png](https://bbs-img.huaweicloud.com/blogs/img/1582542037699483.png) 计算标准,用户是可以自己输入的,比如说你的编正能力强,你一个月C,C++代码,你可以完成800行,汇编代码你可以完成600行,对吧?如果你的移植能力有限,有的编码能力有限,技术成本有限,你可以把它设置成比如说我C,C++代码一个月300行,汇编代码100行,它就会根据不同的标准,计算出你移植工作量,做工程技术上的第1步,第1部信息掌握。 ![图片12.png](https://bbs-img.huaweicloud.com/blogs/img/1582542046872504.png) 这里就列出了我们主要的功能,前面我已经基本赘述了,就是SO文件的检查,构建工程的检查、源文件的检查,评估一致性,然后进行工作量评估,两种方式,外部方式和命令行方式。 ![图片13.png](https://bbs-img.huaweicloud.com/blogs/img/1582542058218793.png) 通过这个工具,我们就可以拿到软件移植的工程量的第1手资料,然后决定是否移植。当决定极值之后,我们就可以用代码迁移工具去做进一步的分析,代码移植工具主要是分析了用户的源代码,还是一样,他着重分析的是makefile,C,C++的源码,就包括我们这里的编译器提供的宏定义,然后用户自定义宏,还有built in函数,Intrisic,还有汇编代码,我们分析完这些内容会提供一个详尽的移植指导,这里面就包含makefile怎么修改?C,C++代码怎么修改? 然汇编代码,我们怎么去修改? ![图片14.png](https://bbs-img.huaweicloud.com/blogs/img/1582542071458973.png) 这里我们只是给移植建议,我们并不去修改用户的原码,用户可以参照着相应的输出我们这里输出的一致报告,去用GTDF的方式,大家去做一个这个对比,然后去把它在工具界面以外用第三方的,比如说用其他的编辑工具把它完成修改。那么这一页我们就列出了我们代码移植工具的一个大致工作流程,同样我们也是外部方式和命令行方式两种方式,方便用户做选择。我们分析用户的源码构建工程,还有公共建工程配置文件,还有C,CC+加的源码或者是汇编源码,然后给出移植知道,那么对于源码的变化,我们会提供对比的方式显示,像这里举的例子就是左边第1点是我们要改哪些文件,就是修改文件列表,第2类就是我们要原文件是什么样子,第3类就是我们建议修改成什么样子? ![图片15.png](https://bbs-img.huaweicloud.com/blogs/img/1582542081512082.png) 这是我们软件移植工具所能提供的能力,我们C,C++,我们这里还是针对C,C++目前为止C,C++的这样编译型语言,去做了建议值,然后我们要有源码,没有源码,也就谈不上移植了。 好,这就到了我们抽奖的第2次抽奖环节,我们稍事休息。 ![图片16.png](https://bbs-img.huaweicloud.com/blogs/img/1582542092597810.png) 下面我们将画面交给老师,继续授课。 ![图片17.png](https://bbs-img.huaweicloud.com/blogs/img/1582542103833612.png) 前面已经讲了,我们如何去做软件依赖性的分析,通过华为开发套件去做软件依赖性的分析,以及做C,C++的移植,我们在完成移植之后,我们会在生产环境上去跑我们这个软件,我们可能会去做性能分析,这时候我们就会提供一个我们叫做分析的一个工具,这个工具主要是帮助用户去做软件性能定位,比如说你有些性能瓶颈或者有想继续优化,我们这里提供了一些手段,这里对于这个工具我们可以帮助用户去分析处理器相关指标,以及看到调度的一些信息,包括外设的信息,包括CPU、磁盘,甚至网卡、短期性的数据,去帮助用户分析C,C++或者是Java程序这样一个性能指标。 我们Java类不是说把GBM当成一个进程,我们是看到GBM内部的,还是有一定作用的,还是比较有用的。我们会把这些数据统统的分析起来,然后通过我们自己定的定义的数学模型进行分析,去看到用户的软件性能瓶颈,比如说是资源竞争的问题或者是调度的问题,甚至说比如说有一些bug导致了一些次循环等等的,我们提供了多种的方式来呈现这样一个结果。比如说我们常用的这种火焰图的方式,我们这里能够提供比较直观的可视化方式,帮助用户去看自己的软件里面到底有没有性质上的问题。 ![图片18.png](https://bbs-img.huaweicloud.com/blogs/img/1582542114846009.png) 这个是我们这里是罗列一下我们目前性能分析工具能够提供的性能指标,我们能够看硬件器件相关的,比如CPU、内存、磁盘、网卡、系统级的,我们也能看这种线系统调度以及的比如说进程、线程,还有彼此之间的切换,或者是资源的争抢,锁这样的一些关键变量的这样一些性能分析先行指标,我们也提供了一个基于火焰图、基于代码逻辑的深层次检查,能够提出用户代码的真正的开销,大的地方在哪里,对应的代码对应到源码。 通过这样一个手段,我们就能帮助客户比较快的去帮助开发者比较快的定位自己的软件,编译形软件的瓶颈。,当定位到软件瓶颈的时候,我们会提供一些附加的能力,比如说这里我们就提供加一个叫加速库,我们软硬结合的加速库帮助用户去优化代码。这个原因是什么?这原因主要是因为我们鲲鹏是一个sock,我们是一个片量系统。 ![图片19.png](https://bbs-img.huaweicloud.com/blogs/img/1582542128204490.png) 除了泰山内核,以及多达48甚至64的内核以外,我们还提供了一些额外的能力,额外的一些引擎,这些加速引擎就可以支持,比如说压缩LZ77的这种算法,还有加解密的,比如说非对称的,还有对称加密的,包括一些常用的变加解密的这样算法,比如说DH编码等等。 我们还支持了比如说存储用到code等一些这样一些常用的软件算法,我们把它运化成加速器,这种压缩用起来非常简单,就跟我们用一个外设一样,我们只需要从华为的网站上去获取相应的硬件驱动代码,把它安装上之后,我们就可以像一个正常的外设一样去使用它。 ![图片20.png](https://bbs-img.huaweicloud.com/blogs/img/1582542139648078.png) 当然了你要使用我们提供的一些API的话,可能还要遵循一些,比如说我们要提供给用户手册,用户可能要去修改一下自己的源码,比如说可能原来掉的一些是软件的这样一些函数,或者是三方组建的API,这时候可能去要用加速器的话,就需要根据API修改我相应的这个代码逻辑,但这个代码逻辑只是存在于API层面。 这里举个例子,比如说我们这里集成了一个叫RC的加速的引擎,是用来计算finish加密的,我们支持1024~4096,4种:1024 2048 3072 3096,4种密钥长度。我们在我们加速器引擎里面,我们是通过一个用户态的来libry去做一个隔离,对上去隔离用户的,比如说开源的第三方软件,比如说这里贴到open SSL的的API,我们去对接open SSAPI我们也可以把API暴露出来,直接给用户的APP去使用,在libry下层的就是我们IC引擎的相应的驱动,用户可以完全不用知道下面细节是如何实现的,但是我们通过只要使用我们正确调用鲲鹏RC的提供的用户libry,就可以去使用我们加速器的硬件计算能力,极大的加快了RC的计算。 其实我们也知道RC计算如果用CPU算的话,那是相当费时费力的。比如说一个像x86的一个中高端的一个call,可能它每秒钟只能执行720次左右的一个RC2048的计算。但是你要用到了鲲鹏920提供的RC计算引擎的话,计算量将是大幅度的提升,也就是说,我可以把原本用来计算RC的这些CPU完全释放出来,跑我的业务!在一个芯片内完成这样业务,就会对用户来讲就会提供另外一个选择,我不需要去买某些PCIE的插卡,我直接去用软件的方式来提升我的软件性能,达到一个比较简单的提升性能的一个方式。 这是我们举的一个例子,在这些里面,在我们移植工具里面,都会去通过我们软件移植的这样一些能力去提供给开发者直接使用。 ![图片21.png](https://bbs-img.huaweicloud.com/blogs/img/1582542154699770.png) 这个是我们几个工具组建的发布的策略,我们目前为止是停留在中间这一列上,我们完成了多OS的适配,比如说我们支持3~4、74、7.5、7.6、7.7、7.8对吧?我们也支持中标麒麟等,我们也支持了像苏C这样一些的操作系统,就是我们尽可能的去帮尽可能覆盖我们常用的这样一些操作系统的类型,我们也支持了GCC的多个版本,我们从4.8.9一直支持到目前为止至少8.3,我们后续会支持到9点几的版本,一直往上支持上去,帮助客户去尽可能的简化一些重复劳动,我们也支持MAC构建工具,也要支持CVK构建工具,未来还会支持automake这样的一个构建工具的一些检查。 支持C,C++的与代码移植,也支持汇编代码的识别,因为刚才前面说了,从汇编指令的角度来讲,从你Intrisic的数量来讲,这个量非常的大,而且也很有技术挑战的,就是汇编语言的替换,所以这块我们会逐步完善。对于加速这一块,我们是提供一些Intrisic的一些替换,比如说我们有abs或者有SSE。 我们也去优化了,像一些常用的加速的三方的组件,比如说像一些z-lib的加速或者stapi的一些加速,还有一些scan这种字符扫描的加速的,我们用鲲鹏的指令去进行优化,进行性能提升,取得了比较可观的一个性能改变都是50%,一倍,甚至更多的3,4倍的这样一个性能提升,所以加速的效果还是挺明显的。这样也能让用户的软件应用跑在空间当中跑的更快又快又好。 如何去获取我们这几个工具,我们可以通过华为的spot网站去下载,也可以通过华为空方社区去下载相应的软件,这上面提供了一些链接。 ![图片22.png](https://bbs-img.huaweicloud.com/blogs/img/1582542164928478.png) 对于我们加速库软件,这里的策略是主要采取开源的这种策略,比如说像JDPC或者一些三方组建的,包括一些压缩算法,压缩引擎的,包括这些软件组件,我们都是把相应的patch去推动到社区里面去。对于硬件加速引擎,我们也是直接可以从华为的鲲鹏社区上去下载,然后去安装使用,取用起来还是比较方便的。 鲲鹏社区以后就是华为重点建设的一个和开发者沟通的互动的地方桥梁。在这个地社区里,我们就可以下载到数百份的软件移植指导以及相应的软件调优的经验,可以在这里面和其他的开发者做互动,做技术上进一步的探讨。 然后很多新的技术资料、技术文档,包括一些白皮书,一些产品设计方案都会在社区里陆续发布,不同的开发者都能得到一些不同的信息。 ![图片23.png](https://bbs-img.huaweicloud.com/blogs/img/1582542174162472.png) 这里列出来,我们空开发者社区里面如何去得到两工具这几个工具,目前我们这些工具都已经上线了,9月30号是第1版本,9月30号以后我们是每月逐月发布的这样一个节奏,这个节奏将延续到2020年,就是说我们不是一个短期行为,我们会一直从开发者的需求角度来出发,去把这个工具做的更加应用,更加方便帮助用户完成C,C++加代码的90%的工具化移植。 ![图片24.png](https://bbs-img.huaweicloud.com/blogs/img/1582542184997265.png) 其实在鲲鹏的开发的平台里面,因为鲲鹏是空中平台,是一个新生事物,对吧?对于x86而言是一个新生事物,在这个里面我们也能感受到,随着鲲鹏计算平台的壮大,应用越来越多,需要大量的开发者去投入到平台的生态建设里面来。所以华为在这里推出了这种线上认证培训的这么一系列的技能提升的活动,包括在线课程,包括云端的实验室,包括线上认证和线下培训,希望大家能够积极参与,来共同构建华为鲲鹏的生态软件生态。 ![图片25.png](https://bbs-img.huaweicloud.com/blogs/img/1582542194233230.png) 这里提到一个华为鲲鹏认证开发工程师这么一件事情,就是HCIA认证认证其实在华为内部还在对华为来讲还是蛮有价值的,对开发者来讲也是很有价值的。因为你通过了认证之后,在一定程度上将会成为你进入华为从事软件开发的一个直通车。 ![图片26.png](https://bbs-img.huaweicloud.com/blogs/img/1582542204310188.png) 所以大家可以关注一下相关的一些培训认证的信息,去找到一个适合自己的方向,然后去在一个更大的舞台上,我们一起来构建华为鲲鹏的软件生态环境,让华为鲲鹏做得越来越好。 ![图片27.png](https://bbs-img.huaweicloud.com/blogs/img/1582542215417542.png) 现在线上提供了华为文档中册这么一个活动,希望开发者通过扫描二维码来参与到我们华为空投文档的这一个测试活动,因为华为文档、鲲鹏文档实际上是我们近几年来在鲲鹏平台上的软件移植、调优,包括软件优化,包括软件开发的一系列经验总结。 这里面文档非常多,大家可以去找自己感兴趣的去无论是学习也好,还是去交流也好,大家可以在鲲鹏的开发者社区上去留言互动,我们会有相应的开发工程师及时来和大家来响应。 ![图片28.png](https://bbs-img.huaweicloud.com/blogs/img/1582542227334819.png) 好,这就到最后一个提问互动环节了。线上的开发者有什么问题吗?我们可以交流一下。 主持人:刚才老师在讲的过程中,我们也在线下将这些问题进行了梳理,现目前老师就进行一个答疑的环节,老师我把问题给到您,您可以看一下。 老师:第1个问题是王涵,他这个问题是软件移植是基于同样的操作系统移植吗?我们的软件系统不是一个可以是相应平台,也就是说我们软件可以跑在其他软件上,但是我最终的软件移植可能是移植在一个乌帮图上,甚至说一个中标70上。这时候就是我们支持一个相应软件的移植,其实对我们来讲,重点要关注的就是你的软件运行依赖库或者是安装依赖库的一个兼容性问题,就是我们后面一个花了巨大人力去积累的一个清单,兼容性清单。从技术角度来讲,在非常大的程度上去满足这种建设性清查。 第2个问题,从x86移植到鲲鹏,会不会导致性能急剧下降?要看应用的类型。就说如果你是一个纯粹的C,C++做一些通用计算的话,我能说是不会,但是如果说你C,C++里面非常高级的,你用了一些底层的汇编,如果用到了一些x86所独有的,比如说AVX4做一些向量化计算,或者是做一些这种高并发的计算,我不得不坦率的告诉你,性能软件,因为AVX指令或者是不管无论是256比特大概是512比特,在鲲鹏920平台上现在是没有对应,但是以后会有对应。 这个以后我目前我也不能说是什么时候对吧?但是这是一个长期的演进的计划和过程,所以这个说我只能先告诉你,如果你要用到这些AVX指令,我们是有性能差异的。坦白的来讲,对吧?有一说一。 然后还有一个问题,鲲鹏与手机处理器都是基于惊险指令集,能否认为可将手机一起底层的功能移植到鲲鹏平台上?这个是我们现在可能常说的所谓的叫端边云融合,实际上做手机端开发,我们可能更多的是基于安卓的系统,然后做一些用一些Java类的语言,然后我们的应用可能是编译成字节码跑的GVM上,对吧? 从这个角度来讲,如果说你单纯从语言,我可能说差异并不大,你可能在鲲鹏平台上你可以方便的装一个安卓模拟器,对吧?通过这种方式,我们华为鲲鹏也提供这样这样一个解决方案,让你可以跑一个云手机,在官方平台上看云手机,跑得非常的高效,非常的高效,这个也能满足,所以从这个角度来讲,我们提供了一套解决方案,是没有任何没有任何问题。 第三个问题,Java代码如何移植?这个问题在前面的胶片上我们已经看了,因为Java属于这种字节码运行的,于强依赖于他的roundtime环境,在我们华为鲲鹏平台上,我们是完美支持open jdk的,所以你用open jd1.8或1.9甚至更高的版本,这个是从Java,代码语言本身来讲是没有问题的。 但是如果说你Java里面非常高级的,你又嵌套了一些C的一些API,请把C单独的源码拿出来做一些一致性的检查。 第四个问题,目前提供什么移植工具?Java刚才说的,我们在这个工具里面不去做涵盖检查,除非你Java,或者是像类似于这种Python,你用到了一些C编译成的SO文件用到一些C提供的API,我们是要检查。 最后一个问题,还有10%的代码可以如何迁移?但是这个问题问得很好,我们刚才前面其实我已经提到了,因为指令集的差异,对吧?包括C里面的编译器里面提供了一些built in对应函数的一些差异,可能有些built in如果大家去细细看的话,看C++的源码的话,你会发现有些标的函数用的是AVX的指令,甚至是SSE的一些指令,这些指令可能在鲲鹏平台上目前并不支持,所以从这种角度来讲,我们没办法给你做到这种100%的对应。 但这个就是我们所提到的一个缺陷,因为从我们华为软件移植的既往经验来讲,可能真正的汇编代码在一个大的工程里面,可能它所占的比例是比较有限的,而这部分的代码里面又用到了AVX,甚至于说和华为共同完全不兼容的,这样功能性上不兼容的指令是又更加少一些,所以这就是我们所说的,我并不能保证100%去支持这样一个软件的移植,所以我们也在不断的演进,谢谢! 主持人:希望大家多多关注我们的鲲鹏社区,今天老师主讲的内容我们已经上传到了鲲鹏社区,大家可以对资料进行一个下载,再进行一些学习都可以的,因为鲲鹏社区,它汇聚了我们鲲鹏的最新的一些知识问答,也汇聚了一批我们行业的专家,大家有什么问题的话都可以在鲲鹏社区上进行一个留言,我们的专家会进行一个及时的回复,相关的技术包、安装包以及相关的一些学习资料上面也比较齐全,如果关注鲲鹏的话,可以到上面去进行一个获取。 ![图片29.png](https://bbs-img.huaweicloud.com/blogs/img/1582542239969021.png) 摘自:https://bbs.huaweicloud.com/blogs/150572
-
编译优化编译器已经发展优化很多年了,一些关于指令重排,精简操作指令,尽量去满足cpu的流水操作,对程序分支进行预测,重新调整代码执行顺序,对简单的程序,使用循环展开等等这些优化代码的操作,大部分的编译器就可以完成。因此本文并不会着重介绍这些性能优化方法,尤其是指令重排和精简指令操作这两种。这些优化,可以通过一些优化选项来让编译器帮我们完成。1.1 Gcc的四级优化-O0:不做任何优化,这是默认的编译选项。 -O1:对程序做部分编译优化,对于大函数,优化编译占用稍微多的时间和相当大的内存。使用本项优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化。 它主要对代码的分支,常量以及表达式等进行优化。 -O2:会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。Gcc将执行几乎所有不包含时间和空间折中的优化。当设置O2选项时,编译器并不进行循环展开以及函数内联。与O1比较而言,O2优化增加了编译时间的基础上,提高了生成代码的执行效率。 -O3:在O2的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。-Os:主要是对程序的尺寸进行优化。打开了大部分O2优化中不会增加程序大小的优化选项,并对程序代码的大小做更深层的优化。(通常我们不需要这种优化)1.2 编译优化可能带来的问题调试问题:任何级别的优化都将带来代码结构的改变。例如:对分支的合并和消除,对公用子表达式的消除,对循环内load/store操作的替换和更改等,都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足。 内存操作顺序改变所带来的问题:在O2优化后,编译器会对影响内存操作的执行顺序。例如:-fschedule-insns允许数据处理时先完成其他的指令;-fforce-mem有可能导致内存与寄存器之间的数据产生类似脏数据的不一致等。对于某些依赖内存操作顺序而进行的逻辑,需要做严格的处理后才能进行优化。例如,采用volatile关键字限制变量的操作方式,或者利用barrier迫使cpu严格按照指令序执行的。精简指令操作带来的精度问题:编译优化,会精简指令操作,将某两条指令合并成一条指令等。在数学计算中,先后计算会带来精度不一致的问题。比如计算公式A*B-C*D,我们希望先计算C*D,再计算A*B,最后计算减法,于是代码中用两个乘法和一个减法指令实现此功能,然而编译器会将此三条指令优化成两个指令,即一个乘法指令和一个乘减指令。从而会先计算A*B =X,再计算X-C*D,此类问题在双精度的浮点计算时,会有较大的精度差异。1.3 常用解决方法对于上述编译优化可能引入的一些问题,一般常用的解决方法是用内联汇编的方式重新封装需要的指令。一般在一个工程中,不同代码段,被优化引入的问题是不同的,我们可能无法去修改编译选项。比如上述例子中,因为精简指令带来的精度问题,如果在编译选项中关闭精简指令,势必会影响整个工程的性能。如果在相关函数中告诉编译器禁止优化,那么其他优化方法,此函数也享受不到了。但是如果使用内联汇编的方式,此部分代码编译后,会按照内联的汇编中的指令以及指令顺序执行,这样我们就可以控制问题代码的逻辑同时其他部分继续享受编译器的优化。下面用一个简单例子来说明://下列代码为了计算A*B+E*F以及G*H-C*D float64x2_t X = vmulq_f64(A, B);//乘法指令,计算X=A*B float64x2_t Y = vmulq_f64(C, D);//乘法指令,计算Y=C*D Y = vnegq_f64(Y);//取反,即Y=-Y float64x2_t RST1 = vmlaq_f64(X, E, F);//乘加指令,计算X+E*F float64x2_t RST2= vmlaq_f64(Y, G, H);//乘加指令,计算Y+G*H代码中的实现顺序为先计算A*B=X,再计算X+E*F,先计算Y=C*D,再计算-Y+G*H。假设这是我们希望的逻辑计算顺序。开了编译优化以后,上面指令会被精简优化,取反指令会和乘加指令合并成乘减指令。优化后的公式就变成了A*B+E*F以及G*H-C*D,先计算A*B和G*H,再分别用一条乘加指令和乘减指令完成计算。此与我们希望的运算顺序不同,上述优化是因为优化了乘加指令导致的,因此,我们只用重新封装乘加指令就可以了。当程序到乘加指令的时候,走的是内联汇编版本,按照写的代码执行。如封装的内联汇编如下:FORCE_INLINE float64x2_t vmlaq_f64_ext(float64x2_t a, float64x2_t b, float64x2_t c){ float64x2_t result; __asm__ __volatile__ ( "fmla %0.2d, %2.2d, %3.2d" : "=w"(result) : "0"(a), "w"(b), "w"(c) : /* No clobbers */ ); return result;}
-
# GCC for openEuler -mcmodel选项详解 ## 导语 GCC for openEuler是基于开源GCC开发的编译器工具链(包含编译器,汇编器,链接器),在openEuler社区开源发布,并通过鲲鹏社区免费提供二进制包,支持包含ARM、x86在内的多种处理器架构。 本文将向大家详细介绍-mcmodel选项的作用以及GCC for openEuler 在-mcmodel选项上做的新功能支持[1]。 ## 背景 编译的过程中,编译器是不知道要操作的数据在哪里的,计算数据地址的工作是在链接阶段实现的。也就是说,编译器需要先把拿取数据的汇编指令定下来,在编译结束之后,链接器进行重定位计算时再填上指令的操作数是多少。那就有一个问题,编译器如何选择一个合适的指令拿取数据? **PC相对寻址** 在考虑这个问题前,我们先了解一下PC相对地址。PC(Program Count)特指PC寄存器(以下都用PC表示),是计算机处理器内部的一个专用寄存器,用来表示下一个将被执行的指令的地址。在我们的程序中,如果某一条指令访问的符号(变量,函数等)是基于当前指令的相对地址,我们就称这是**PC相对寻址**。 例如跳转指令`b, <label>`中的 `<lable>` 就是**偏移量**,并不是绝对地址。在链接阶段,链接器会根据符号出现的位置计算出正确的偏移,编码到指令中去。程序执行时到这条指令时,就会把`<lable>`所代表的偏移量与当前PC值相加,得到要跳转的指令地址。 类似的还有ADR,ADRP指令。ADR/ADRP分别是获取某个符号的地址/页面地址,并把它们存放到指定的寄存器中。它们的格式是这样的:`ADR Rd, <label>`, `ADRP Rd, <label>`, 这里的`<label>`都是代表当前指令位置的偏移量。 `<label>`受到指令位域的限制,所以偏移量都是有限制的。比如说`b, <label>` 可以基于当前PC有±128MB的寻址范围,`ADR Rd, <label>`有±1MB的寻址范围,`ADRP Rd, <label>`有±4GB的寻址范围。 链接器在链接时会把各个目标文件进行合理布局,使得跳转、函数调用、变量访问等操作都不会离当前指令特别远。通常情况下,使用前面提到的指令能满足绝大多数的情景,但是如果指令要访问的符号超出了4GB(32位)的范围,这时用默认的符号取值方式就会出错。 ## 案例 比如说在AArch64后端如果待链接符号的距离超过4GB(如图1),编译的时候又决定使用ADRP指令(32bit寻址范围,如图2)。此时如果使用这种方式去链接,则会报relocation truncated to fit这样的错(如图3)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115223rn6mznkcmbscp5ww.png) 图1:符号相对于.bss段的偏移大于4GB ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115230mhnipbboql5nnae5.png) 图2:指令集中对于ADRP指令的描述,可见其寻址范围只在±4GB ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115236bb3qbrxxx1aw9i5i.png) 图3:CESM代码中出问题的字段描述,`R_AARCH64_ ADR_PREL_PG_HI21`对应ADRP指令进行相对PC寻址 那么怎么解决这个问题?在这种情况下需要指导编译器:符号可能特别远(超过4GB),需要生成恰当的指令来获取符号地址。那么编译器就不会再用传统的ADRP这样的指令,而是采用其他的办法。 对此,GCC提供了`-mcmodel`参数,用于指导编译器应该使用哪种模型来生成指令。 下面是GCC的`-mcmodel`对AArch64架构的的官方说明: > -mcmodel=tiny\ > Generate code for the tiny code model. The program and its statically defined symbols must be within 1MB of each other. Programs can be statically or dynamically linked.\ > -mcmodel=small\ > Generate code for the small code model. The program and its statically defined symbols must be within 4GB of each other. Programs can be statically or dynamically linked. This is the default code model.\ > -mcmodel=large\ > Generate code for the large code model. This makes no assumptions about addresses and sizes of sections. Programs can be statically linked only. The -mcmodel=large option is incompatible with -mabi=ilp32, -fpic and -fPIC. `-mcmodel`选项指导编译器做出这样一种假设:代码里所有符号的位置都在某个位宽范围之内。比如`-mcmodel=small`,就是假设所有符号都在4GB范围内,32bit的位宽就可以找到符号的位置,那我们使用ADRP指令就可以了。 但是假设不成立的时候,比如上面的情况,ADRP指令不再适用,需要用位宽更大的指令。这个时候就需要增大`-mcmodel`的预设,使用`-mcmodel=large`,变更寻址方式为LDR指令。LDR指令只能绝对寻址,但是有更大的寻址范围。如果你的应用可以非地址无关编译的话,那么`-mcmodel=large`理论上可以解决所有的问题。 而例如HPC场景中的CESM应用在符号超过4GB寻址范围的时候,作为一些共享库,仍然需要按照地址无关代码(position-independent code,PIC) 的方式编译(-fPIC / -fpic地址无关功能),可以说地址无关代码是动态共享库必须的。这时LDR指令也不再适用,因为GCC对于AArch64的支持非常有限,它仅支持非地址无关代码。这是AArch64独有的一类问题。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115247hj0okyvi92ys76yk.png) 图4:aarch64 `-mcmodle=large`时的寻址方式 在x86上有可以大位宽操作的`mov`指令,它可以实现4GB以上的地址无关寻址。根据与x86后端对比可以发现,x86后端在`-mcmodel=medium`的时候之所以还可以生成地址无关代码,主要原因是其寻址方式还是相对PC寻址。与`-mcmodel=small`相比唯一的变动是相对寻址的指令由`mov`变成了`movabs`,可以进行更大范围的寻址(64bit)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/11534776zvvrndb3pxqwsa.png) 图5:x86 ABI 中对于`-mcmodle=medium`、`-mcmodle=large`的描述 反观AArch64后端,其实相对PC寻址的指令和64bit的加法指令都是有的,甚至是64位的相对PC寻址方式在ABI中都是有的(如图6),缺少的是这种重定位方式。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/1153556gwxcjxe5z9bogjo.png) 图6: AArch64 ABI 中关于64位相对PC寻址的描述 ## -mcmodel=medium, -mlarge-data-threshold=n GCC for openEuler根据上述问题的痛点,新开发了`-mcmodel=medium`, `-mlarge-data-threshold=n`两个选项。此选项使能了32bit之外的动态取址操作。在使用`-mcmodel=medium`时,对于符号size大于`aarch64_data_threshold`的符号使用**通过mov序列来获取PC值的offset,再与PC值相加**的方式实现64bit的相对PC寻址,在地址无关选项打开时,可以实现64bit相对PC寻址,获取GOT表入口,并且通过mov序列+LDR方式获取符号。 说明:`aarch64_data_threshold`的默认值为2^16 = 65536,用户可以使用`-mlarge-data-threshold=n`选项指定大符号的阈值为n。 ### 举例 如图7所示,假设`foovar`的符号距离寻址指令的距离大于4GB,`-mcmodel=small`会使用ADRP+ADD指令进行符号拿取,而`foovar`在链接时计算距离的方式是如**使用方法**中的.bss+size方式,在链接时会报`relocation truncated to fit`错误。在此使用图8中的mov序列+PC寻址方式可将寻址范围扩大至64位,解决由于地址溢出导致的报错。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115415ma3rw5yimprdy7xc.png) 图7:smallcode model寻址方式 在这种模式下,通过`adrp`和`add`指令获取`foovar`的地址。`adrp`是PC相对寻址,它会把`foovar`的页地址偏移量与当前PC值相加,并存储到`x0`寄存器,下一条指令`add`把页内地址(低12位)再加到`x0`寄存器上,这样就得到了`foovar`的地址。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/1154194wi8hxhxv25zq8qi.png) 图8:mov序列+PC寻址方式 可以看出这里使用了`movz`, `movk`, `adr`, `sub`, `add`这样一系列的指令最终得到了`foovar`的地址。`movz`和3条`movk`指令的作用是把`foovar`的64位的偏移量分4次,每次转存16位,依次存放到了x0寄存器,`adr x8, .`的作用是获取当前的PC值,`sub`是对PC值做一些修正,然后`add`是把64位偏移量与修正后的PC值相加。这样就得到具有64位PC相对地址的`foovar`地址了。 ### 使用方法 用例: libdemo.cpp ```cpp libdemo.cpp #include <iostream> char arr[10][1*1024*1024*1024]; void set_and_print(){ arr[8][0]='A'; std::cout << arr[8][0] << std::endl; } ``` 上述代码定义了一个二维数组`arr`,第一维有10个元素,每一个元素又是一个总大小为1GB的字符数组。在访问`arr[8][0]`时需要偏移8GB,已经超过了`ADR`, `ADRP`这样的取值范围。 main.cpp ```cpp extern char arr[10][1*1024*1024*1024]; void set_and_print(); int main(){ set_and_print(); return 0; } ``` 主程序会使用共享库中的`set_and_print`函数。 现在我们来编译上面的代码。如下图所示,如果不指定`-mcmodel`参数就会报`relocation truncated to fit`错误;当程序指定`-mcmodel=large`时,又与-fpic冲突,无法生成动态共享库。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115429zarqzuytqgldk7nu.png) 图9:不指定`-mcmodel`,或者指定`-mcmodel=large`进行编译,编译失败 当我们使用GCC for openEuler开发的`-mcmodel=medium`和`-mlarge-data-threshold=1`后,动态共享库被成功创建了,主程序也能正常调用它,并且得到正确的结果。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115437xqpvju6swe765vqm.png) 图10:指定-mcmodel=medium -mlarge-data-threshold=1 进行编译,编译成功 我们对共享库进行反编译查看汇编代码就会看到,取址指令已经是movz, movk这样的序列了。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115459f5g4w3kuytygifs3.png) 编译命令: ```bash c++ libdemo.cpp -fpic -shared -o libdemo.so -mcmodel=medium -mlarge-data-threshold=1 c++ main.cpp libdemo.so -o main ``` 运行主程序: ```bash ./main ``` ## 总结 该选项通过软件模拟的方式,使用多条指令去模拟`movabs`指令,使得在HPC领域一些需要大范围地址无关寻址的应用能够平滑地从其他平台迁移到鲲鹏平台中来。 所以在GCC for openEuler使用过程中,若出现`relocation truncated to fit`错误,可以尝试添加编译选项`-mcmodel=medium -mlarge-data-threshold=1`解决。 时间问题暂时写到此处,后续会继续更新一些GCC for openEuler或者毕昇编译器相关优化选项的介绍,感兴趣的朋友敬请博客留言,也可以点击文末**阅读原文**进入GCC for openEuler网页下载使用GCC for openEuler。 ## 参考 [1] https://bbs.huaweicloud.com/blogs/272527 [2] https://www.hikunpeng.com/developer/devkit/compiler/gcc [3] https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html ## 后记 欢迎加入Compiler SIG交流群与大家共同交流学习编译技术相关内容,扫码添加小助手微信邀请你进入Compiler SIG交流群。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115534xw07sqvtqm9iloxa.png) ------ 原文转载自[毕昇编译-GCC for openEuler -mcmodel选项详解](https://mp.weixin.qq.com/s/9OXRMg6xCTSdlhx_44BCvA) ![毕昇编译-二维码.jpg](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/1156583kgzvnhtfxuzozuy.jpg) 访问GCC for openEuler网页:https://www.hikunpeng.com/developer/devkit/compiler/gcc
-
计算机编程语言主要包括汇编语言、机器语言以及高级语言,具体内容如下:汇编语言该语言主要是以缩写英文作为标符进行编写的,运用汇编语言进行编写的一般都是较为简练的小程序,其在执行方面较为便利,但汇编语言在程序方面较为冗长,所以具有较高的出错率。机器语言这种语言主要是利用二进制编码进行指令的发送,能够被计算机快速地识别,其灵活性相对较高,且执行速度较为可观,机器语言与汇编语言之间的相似性较高,但由于具有局限性,所以在使用上存在一定的约束性。高级语言所谓的高级语言,其实是由多种编程语言结合之后的总称,其可以对多条指令进行整合,将其变为单条指令完成输送,其在操作细节指令以及中间过程等方面都得到了适当的简化,所以,整个程序更为简便,具有较强的操作性,而这种编码方式的简化,使得计算机编程对于相关工作人员的专业水平要求不断放宽。
-
【功能模块】鲲鹏代码迁移工具【操作步骤&问题现象】1、写了一份x86的代码,发现代码迁移工具可以将x86的代码转化成对等的鲲鹏汇编,分析报告中给出了部分代码的修改建议。但是工具上描述的一些迁移编辑以及一键代码替换功能请问该怎么开启,还有想问一下我们的迁移具体该怎么迁移?【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
课程套件会基于师生的建议和反馈,结合昇腾全栈软硬件版本、教学周期每半年刷新1次(每学期开课前)。软硬件版本会前向兼容,若出现不兼容请参考版本变更说明。版本刷新后会通过微信等线上途径知会老师,建议授课前获取官网最新版本。 考虑昇腾全栈软件版本迭代、ModelArts上可用版本,确定实验中各组件的版本。依赖ModelArts的实验跟随ModelArts版本进行刷新,其他实验采用最新版本。 | 组件 | 固件驱动 | CANN | MindSpore | MindX | MindStudio | | ------------------ | -------- | ----------- | --------- | ----- | ---------- | | 2021年H1 | 1.0.9 | 3.2.0 | 1.1 | N/A | 2.0.0 | | 2021年H2-ModelArts | 1.0.10 | 3.3.0/5.0.1 | 1.2 | N/A | 3.0.1 | | 2021年H2-手动安装 | 1.0.10 | 5.0.2 | 1.3 | 2.0.2 | 3.0.2 | ## 社区版和商用版 固件驱动和CANN区分社区版和商用版,社区版版本号以`x.x.x.alpha00x`命名,商用版版本号以`x.x.x`命名。社区版约2周发布一个alpha版本,更新较快,可以提前获取最新的功能特性。商用版约3月发布一次,经过严格测试,较为稳定。同一版本(x.x.x)的社区版和商用版,可相互替换使用。Atlas 200 DK可用Atlas 200 AI加速模块的商用版固件驱动和CANN。 ## 版本文档 - MindX SDK:通过[MindX SDK主页](https://www.hiascend.com/software/mindx-sdk)获取,或直接前往[2.0.2](https://support.huaweicloud.com/mindxsdk202/)。 - [MindSpore](https://mindspore.cn/):教程、文档、API。 - MindStudio:通过[MindStudio主页](https://www.hiascend.com/software/mindstudio)获取,或直接前往[3.0.2](https://support.huaweicloud.com/mindstudio302/index.html) - CANN:[社区版](https://www.hiascend.com/?tag=community-developer)、[商用版](https://www.hiascend.com/?tag=commercial-developer),在右上角选择版本。 - Atlas 200 DK:通过[AI开发者套件主页](https://www.hiascend.com/hardware/developer-kit)获取,或直接前往[1.0.10.alpha](https://support.huaweicloud.com/productdesc-Atlas200DK1010/atlas200_DK_pdes_19_0002.html) 不同版本CANN、Atlas 200 DK文档获取方式见:[昇腾社区>开发者>开发者文档](https://www.hiascend.com/?tag=community-developer),如下: ![文档.PNG](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202108/10/20200311rg0f80sgszbcjl.png) ## 查看版本信息 - MindX SDK:查看SDK安装目录下的`version.info`文件可确认版本信息,安装目录为用户指定,或安装包同一目录。如`cat /usr/local/Ascend/mindx_sdk/mxManufactue_2.0.1.b021/linux-aarch64/mxManufacture/version.info`。 - MindSpore:MindSpore针对Ascend、GPU、CPU平台的pip包名分别为mindspore_ascend、mindspore_gpu、mindspore。通过如`pip show mindspore_ascend`命令可确认MindSpore版本信息。 - MindStudio:查看MindStudio安装目录下的`product-info.json`文件可确认版本信息,或在MindStudio欢迎界面/Help>About界面查看版本信息。 - CANN:查看CANN安装目录下的`version.info`文件可确认版本信息,安装位置通常在`/usr/local/Ascend/`或当前用户目录下,如`cat /usr/local/Ascend/ascend-toolkit/latest/arm64-linux/ascend_toolkit_install.info`。 - 固件驱动:查看固件驱动安装目录下的`version.info`文件可确认版本信息,安装位置通常在`/usr/local/Ascend/`目录下,如`cat /usr/local/Ascend/version.info`或`cat /usr/local/Ascend/driver/version.info`。 ## 接口/功能变更 ### 当前版本 - [MindSpore](https://gitee.com/mindspore/mindspore/blob/master/RELEASE.md):含个版本接口变更说明,见各版本章节的**API Change**小节。 - [MindX SDK 2.0.2](https://support.huaweicloud.com/mindxsdk202/):见mxManufacture/mxVision用户指南的**使用约束、What's New**章节。 - [MindStudio 3.0.2](https://support.huaweicloud.com/mindstudio302/index.html):见版本说明的**版本配套关系、新增特性、问题修复说明**章节。 - CANN 3.3.0(商用版):即5.0.1版本,[C++ ACL废弃接口/返回码列表](https://support.huaweicloud.com/devg-cannApplicationDev330/atlasapi_07_0005.html),[Python ACL废弃接口/返回码列表](https://support.huaweicloud.com/usermanual-cannApplicationDev330/atlaspyapi_07_0002.html) - CANN 5.0.2.alpha003(社区版):[C++ ACL废弃接口/返回码列表](https://support.huaweicloud.com/aclcppdevg-cann502alpha3infer/atlasapi_07_0005.html),[Python ACL废弃接口/返回码列表](https://support.huaweicloud.com/aclpythondevg-cann502alpha3infer/atlaspyapi_07_0002.html) ### 历史版本 - [MindX SDK 2.0.1](https://support.huaweicloud.com/mindxsdk201/):见版本说明**What's New、问题修复说明**章节。 - [MindStudio 3.0.1](https://support.huaweicloud.com/mindstudio301/index.html):见版本说明**What's New、问题修复说明**章节。 ## 版本号变更 为了更好的管理和呈现昇腾软件平台的演进,对部分软件版本号体系进行了重构。历史变更如下,供了解: | 软件类型 | 变更前 | 变更后 | | ---------- | ------------- | -------------- | | CANN社区版 | 20.1.alpha001 | 3.1.0.alpha001 | | CANN社区版 | 20.2.alpha001 | 3.2.0.alpha001 | | CANN社区版 | 3.3.0alpha001 | 5.0.1.alpha001 | | CANN商用版 | 20.0 | 3.0.0 | | CANN商用版 | 20.1 | 3.1.0 | | CANN商用版 | 20.2 | 3.2.0 | | CANN商用版 | 3.3.0 | 5.0.1 | ## 问题支持 1. 在昇腾论坛对应板块上搜索/发帖,专家值守,有问必答。:https://bbs.huaweicloud.com/forum/forum-726-1.html 2. 通过接口人获取帮助。
-
HPC( High Performance Computing,高性能计算)领域主要是解决计算密集型、海量数据处理等业务的计算需求,如科学研究、气象预报、计算模拟等。如何提高计算能力、极致化应用性能成为当前 HPC 领域各大平台最关键的课题之一,编译器在其中发挥着至关重要的作用。毕昇编译器作为一款基于鲲鹏平台的高性能编译器,在编译算法、加速指令集、 Autotuner 等方面对应用场景进行了深度的优化,为开发者提供高效的性能加持。本期由毕昇编译器工程师卜乐为你介绍鲲鹏的性能优化利器——毕昇编译器如何释放鲲鹏的强劲算力。了解毕昇编译器毕昇编译器是基于 LLVM,针对鲲鹏平台进行了深度优化的高性能编译器。除支持 LLVM 通用功能之外,对以下三个方面进行了增强,使得鲲鹏平台的强劲算力能够最大限度地得到释放。高性能编译算法:编译深度优化,内存优化增强,自动矢量化等,大幅提升指令和数据呑吐量。加速指令集:结合 NEON/SVE 等内嵌指令技术,深度优化指令编译和运行时库,发挥鲲鹏架构极致算力。AI 迭代调优:内置 AI 自学习模型,自动优化编译配置,迭代提升程序性能,完成最优编译。毕昇编译器特性架构图当前毕昇编译器已广泛应用于多种 HPC 典型场景,如气象、安防、流体力学等,性能优势已初步体现。其中,SPEC CPU 2017 benchmark 跑分平均优于 GCC 20%以上,HPC 典型气象应用 WRF 优于 GCC 10%。毕昇编译器与开源编译器SPEC CPU 2017 跑分对比毕昇编译器典型优化场景及其优化原理1 结构体内存布局优化—大幅提升缓存命中率,突破访存瓶颈SPEC CPU 2017 benchmark 中的 mcf 子项是对内存要求极高的应用,它是一款叫做MCF的大规模交通规划软件的核心代码。其瓶颈代码如下图左边所示。结构体优化原理示意图可见在 struct 中,data1 的使用率极高,而 data2 是不使用的。然而由于源代码中,数据的排布是以结构体数组的形式排布。按照一般编译器的编译方式,拿数据时每次都会将整个结构体放到 cache 里面,导致大量不参与计算的 data2 也被加载到了 cache 中,造成高速内存空间的浪费和性能的损耗。毕昇编译器会通过用户标记的结构体声明,或者通过自动检查循环中适合优化的内存场景,确认优化点。然后通过将结构体数组变为数组结构体的方式(如上图右),将有效数据紧凑排布,从而提高 cache 命中率和应用性能。经测试,此优化可以对 mcf 子项带来50%的性能提升。2 自动矢量化—计算效率提升的秘诀鲲鹏平台支持 Armv8 NEON 矢量化指令集。当前支持32个128位的矢量寄存器,指令可以同时操作4*32或2*64的数据。毕昇编译器依托这种硬件优势做了大量优化,包括 SLP(superword-level parallelism) 矢量化和循环自动矢量化。例如在 SPEC CPU 2017 benchmark 中处理视频流格式转换的x264子项中,毕昇编译器会自动识别并使用 uabd 和 udot 这类高效向量指令完成计算来替换标量指令,增大单时钟周期的数据处理量, 从而大幅提升计算效率。对于 x264 子项,这项优化可有效提升其30%的计算效率。 矢量化优化示例 3 Autotuner—基于机器学习快速获取最优编译配置如何获取性能最优编译选项是编译器使用中常见的问题,往往需要长时间的手动选项调优。为了减少这其中的工作量,使得用户能快速找到最优的优化选项,毕昇编译器自研了基于 ML 的自动搜索技术(ML-based Search) 的 Autotuner 工具。Autotuner 的调优流程由两个阶段组成:初始编译阶段(initial compilation)和调优阶段(tuning process),如下图所示:Autotuner 使用流程简单来说,在初始编译阶段,编译器会通过用户指定的调优方向,对可调优的代码区间进行标记。在随后的调优阶段,Autotuner 会根据搜索算法对不同的优化区间生成不同的编译配置。然后使用此配置编译运行,并根据运行性能的反馈来迭代优化配置参数。最后经过给定迭代次数后找出最优配置供用户使用。在实践过程中,通过 Autotuner 对 Coremark Benchmark 进行调优可以获取5%以上的收益。 以上介绍的三个优化特性分别是毕昇编译器在中前端算法优化、后端指令优化、迭代调优中最具代表性、在各自领域对性能提升表现最佳的三个特性。除以上介绍的三个优化特性之外,毕昇编译器在软件预取、循环优化、分支预测、指针压缩等编译优化技术均有探索且取得了显著的收益,详情可点击官网获取毕昇编译器详细信息,快来试试吧! 原文转载自 华为计算-【鲲鹏DevKit黑科技揭秘】┃ 毕昇编译器,让你的代码快到飞起!
推荐直播
-
全面解析华为云EI-API服务:理论基础与实践应用指南
2024/11/29 周五 18:20-20:20
Alex 华为云学堂技术讲师
本期直播给大家带来的是理论与实践结合的华为云EI-API的服务介绍。从“主要功能,应用场景,实践案例,调用流程”四个维度来深入解析“语音交互API,文字识别API,自然语言处理API,图像识别API及图像搜索API”五大场景下API服务,同时结合实验,来加深开发者对API服务理解。
回顾中 -
企业员工、应届毕业生、在读研究生共探项目实践
2024/12/02 周一 19:00-21:00
姚圣伟 在职软件工程师 昇腾社区优秀开发者 华为云云享专家 HCDG天津地区发起人
大神带你一键了解和掌握LeakyReLU自定义算子在ONNX网络中应用和优化技巧,在线分享如何入门,以及在工作中如何结合实际项目进行学习
即将直播 -
昇腾云服务ModelArts深度解析:理论基础与实践应用指南
2024/12/03 周二 14:30-16:30
Alex 华为云学堂技术讲师
如何快速创建和部署模型,管理全周期AI工作流呢?本期直播聚焦华为昇腾云服务ModelArts一站式AI开发平台功能介绍,同时结合基于ModelArts 的实践性实验,帮助开发者从理论到实验更好地理解和使用ModelArts。
去报名
热门标签