一、实验环境说明
1.1 硬件环境
本次实验采用鲲鹏920服务器集群核心节点,具体配置如下:服务器型号为Kunpeng 920-7260,部署2颗处理器,每颗处理器集成48个物理核心,核心主频2.2GHz,最高可睿频至2.6GHz,架构为aarch64(ARMv8-A)。内存配置为128GB DDR4 3200MHz高速内存,支持鲲鹏硬件多级内存架构(包括寄存器、L1/L2/L3缓存、主存的多级存储体系),其中每颗鲲鹏920处理器内置64MB L3缓存,每核配备64KB L1指令缓存+64KB L1数据缓存、512KB L2缓存,多级缓存架构可有效降低内存访问延迟,提升多核并行计算效率。存储方面配备1TB SSD固态硬盘,用于存储实验所需的安装包、代码文件及运行日志,满足高性能计算场景下的数据读写需求。
1.2 软件环境
本次实验软件环境均适配鲲鹏aarch64架构,具体配置及说明如下:
- 操作系统:openEuler 22.03 LTS (aarch64),该系统是华为自主研发的开源操作系统,针对鲲鹏架构进行了深度优化,内置鲲鹏硬件加速引擎,支持多核并行调度、内存分级管理,稳定性和性能均适配高性能计算(HPC)场景,且提供完善的开源生态支持。
- HPCKit 版本:鲲鹏HPCKit 2.3.0,是鲲鹏社区推出的高性能计算开发套件,集成了编译、调试、性能分析等一站式工具,内置KUPL库、并行计算示例代码及架构优化工具,可快速搭建鲲鹏HPC开发环境。
- 编译器:毕昇编译器 3.1.0,基于LLVM架构开发,专为鲲鹏aarch64架构优化,支持C/C++/Fortran等编程语言,具备自动向量优化、多核并行优化、内存访问优化等功能,相比开源编译器(如GCC),在鲲鹏平台上可提升程序运行性能10%-30%。
- 依赖库:① KUPL 库(鲲鹏统一并行编程框架),是鲲鹏平台专属的并行编程库,封装了计算图、多线程调度、内存管理等核心接口,简化鲲鹏多核并行开发难度,无需手动编写复杂的线程调度代码;② pthread库(POSIX线程库),用于底层线程创建与管理,KUPL库底层依赖该库实现多线程并行执行。
- 开发工具:VSCode + Remote-SSH,VSCode提供完善的代码编辑、语法高亮、调试功能,通过Remote-SSH插件可远程连接鲲鹏服务器,实现本地编辑、远程编译运行,提升开发效率;辅助工具包括Git(用于获取开源示例代码)、htop(用于查看CPU、内存使用情况)、gdb(用于代码调试)。
二、实验原理介绍
2.1 计算图核心概念
计算图是高性能并行计算中常用的编程模型,用于将复杂的计算任务拆解为多个独立的子任务,通过定义子任务间的依赖关系,实现多线程/多进程并行执行。KUPL计算图针对鲲鹏多核架构进行了深度优化,采用“静态图(DAG有向无环图)+ 动态图”双层架构,兼顾任务调度的灵活性和并行执行的高效性,核心概念如下:
- 静态图(DAG):核心作用是“定义任务”,将计算逻辑、数据移动、数据存储等操作封装为一个个“节点”,将子任务间的依赖关系(如A任务的输出是B任务的输入)封装为“边”,形成有向无环图(DAG),确保任务执行无循环依赖,避免死锁。静态图一旦定义完成,其节点和边的关系不再变化,适合预先明确任务流程的场景,可提前进行任务优化(如算子融合、内存复用)。
- 动态图:核心作用是“调度任务”,负责解析静态图的结构,调度静态图中所有节点的并行执行,动态分配鲲鹏服务器的多核资源,实现多线程负载均衡。动态图支持实时调整任务执行顺序,可根据当前CPU核心负载情况,将任务分配给空闲核心,避免部分核心过载、部分核心空闲的情况,充分发挥鲲鹏920服务器96核(2颗×48核)的并行算力。
- 节点与边:节点是计算图的基本执行单元,分为计算节点(执行具体的计算逻辑,如矩阵乘法、加法)、数据节点(用于存储计算数据、传递数据)、控制节点(用于控制任务执行顺序,如条件判断、循环控制);边分为数据依赖边(表示节点间的数据传递关系,如节点1的输出是节点2的输入)和控制依赖边(表示节点间的执行顺序关系,如节点1执行完成后,节点2才能开始执行)。
- KUPL计算图优势:相比传统的OpenMP并行编程模型,KUPL计算图无需手动管理线程创建、任务分配和依赖关系,开发者仅需关注核心计算逻辑;针对鲲鹏硬件多级内存架构优化,支持数据在多级缓存间的高效移动,降低内存访问延迟;支持任务嵌套、迭代计算,适合高性能计算中常见的迭代求解、矩阵运算、数值模拟等场景。
2.2 KUPL计算图执行流程
KUPL计算图的执行流程遵循“定义-构建-调度-执行-释放”的核心逻辑,结合鲲鹏多核架构的特性,具体流程如下(以本次实验的E = A*B + C*D计算任务为例):
- 静态图初始化:调用KUPL库的核心接口,创建一个空的静态图实例,指定静态图的配置参数(如节点最大数量、数据缓存大小),适配鲲鹏服务器的内存资源。
- 添加任务节点:向静态图中添加4个计算节点(节点1:计算A*B,节点2:计算C*D,节点3:计算(A*B)+(C*D),节点4:输出计算结果E),以及2个数据节点(存储输入数据A、B、C、D和输出数据E),每个计算节点绑定对应的计算函数和输入输出数据。
- 定义依赖关系:构建完整的DAG有向无环图,定义节点间的依赖关系:节点1和节点2为独立节点(无前置依赖,可并行执行);节点3的前置依赖是节点1和节点2(需等待节点1、节点2执行完成,获取其计算结果后,才能执行加法运算);节点4的前置依赖是节点3(需等待节点3计算完成后,输出最终结果E)。
- 动态图初始化与任务提交:创建动态图实例,指定动态图的调度策略(如负载均衡调度、优先级调度),启用鲲鹏服务器的所有CPU核心(KUPL_ALL_EXECUTORS);将静态图封装为一个任务,提交至动态图,动态图开始解析静态图结构,分配线程资源。
- 任务并行执行:动态图为节点1、节点2分配两个独立的线程,并行执行A*B和C*D的乘法运算(充分利用鲲鹏多核并行算力);待节点1、节点2执行完成后,动态图为节点3分配线程,执行加法运算;最后为节点4分配线程,输出计算结果E。
- 阻塞等待与资源释放:调用阻塞接口,等待所有节点执行完成,确保计算结果的完整性;任务执行完毕后,依次销毁静态图、动态图实例,释放线程资源、内存资源,避免内存泄漏,符合鲲鹏高性能编程的资源管理规范。
该执行流程适合迭代计算、存在明确任务依赖的高性能计算场景(如矩阵运算、有限元分析、深度学习训练),通过任务并行执行,可大幅提升计算效率,相比单线程执行,计算速度可接近线性提升(理论上96核可提升96倍,实际受任务依赖、内存带宽等因素影响,提升倍数约为70-85倍)。
2.3 补充知识点:KUPL与鲲鹏架构的适配原理
KUPL库作为鲲鹏平台专属的并行编程框架,深度适配鲲鹏920服务器的硬件特性,核心适配原理如下:
- 多核调度适配:KUPL动态图的调度算法针对鲲鹏920的48核/颗(2颗共96核)架构优化,支持线程与CPU核心的绑定(可通过API指定线程运行在特定核心上),避免线程在不同核心间频繁切换,降低上下文切换开销。
- 内存架构适配:支持鲲鹏硬件多级内存架构,可通过KUPL API指定数据存储在L3缓存、主存中,优先将高频访问的数据存储在L3缓存中,降低内存访问延迟(L3缓存访问延迟约为10ns,主存访问延迟约为100ns);支持内存复用,减少数据拷贝次数,提升内存使用效率。
- 编译器协同优化:KUPL库与毕昇编译器3.1.0深度协同,毕昇编译器可识别KUPL的核心接口,对计算节点的代码进行自动向量优化、多核并行优化,将串行计算逻辑转换为适配鲲鹏aarch64架构的并行指令,进一步提升执行性能。
三、实验步骤
3.1 HPCKit环境部署
HPCKit是本次实验的核心开发套件,包含KUPL库、毕昇编译器等依赖组件,部署过程需严格遵循鲲鹏架构的适配要求,具体步骤如下(所有操作均在root用户下执行):
- 环境准备:检查服务器架构是否为aarch64,执行命令:
uname -m,输出为“aarch64”即为正常;安装依赖工具,执行命令:dnf install -y wget tar gcc make,确保wget(下载工具)、tar(解压工具)可正常使用;关闭防火墙(避免影响软件下载和环境配置),执行命令:systemctl stop firewalld && systemctl disable firewalld。 - 下载HPCKit安装包:从鲲鹏社区官方网站下载鲲鹏HPCKit 2.3.0版本(适配aarch64架构),执行命令:
wget https://www.hikunpeng.com/developer/hpc/hpckit-download -O HPCKit_2.3.0_Linux-aarch64.tar.gz;若下载速度较慢,可通过鲲鹏社区镜像源下载,替换命令中的下载地址即可;下载完成后,检查安装包完整性,执行命令:md5sum HPCKit_2.3.0_Linux-aarch64.tar.gz,对比鲲鹏社区提供的MD5校验值,确保安装包未损坏。 - 解压并一键安装:创建安装目录,执行命令:
mkdir -p /opt/hikunpeng/hpckit;解压安装包至临时目录,执行命令:tar -zxvf HPCKit_2.3.0_Linux-aarch64.tar.gz -C /tmp;进入解压目录,执行一键安装脚本,执行命令:cd /tmp/HPCKit_2.3.0_Linux-aarch64 && sh install.sh --prefix /opt/hikunpeng/hpckit;安装过程中,脚本会自动安装毕昇编译器3.1.0、KUPL库、KUPL依赖的pthread库等组件,无需手动干预,安装完成后,会提示“Installation completed successfully”。 - 加载环境变量:安装完成后,需加载HPCKit的环境变量,让系统识别毕昇编译器、KUPL库等组件;执行临时加载命令(仅当前终端有效):
source /opt/hikunpeng/hpckit/latest/setvars.sh;若需永久加载环境变量,编辑/etc/profile文件,执行命令:echo "source /opt/hikunpeng/hpckit/latest/setvars.sh" >> /etc/profile,执行命令:source /etc/profile使配置生效。 - 环境验证:验证毕昇编译器版本,执行命令:
bishengcc -v,输出“gcc version 3.1.0 (Bisheng Compiler 3.1.0)”即为正常;验证KUPL库是否安装成功,执行命令:ldconfig -p | grep libkupl,若输出libkupl.so相关信息,说明KUPL库安装成功;验证HPCKit版本,执行命令:hpckit --version,输出“2.3.0”即为正常。 - 常见问题解决:① 若安装过程中提示“缺少依赖包”,执行命令:
dnf install -y libstdc++-devel,安装缺失的依赖包后重新安装;② 若加载环境变量后,bishengcc命令无法识别,检查环境变量路径是否正确,重新执行source命令;③ 若KUPL库无法识别,检查安装目录下的lib文件夹是否存在libkupl.so文件,若不存在,重新解压安装包安装。
3.2 计算图代码编写
本次实验需编写基于KUPL计算图的代码,实现E = A*B + C*D的计算任务,结合鲲鹏Gitee开源的KUPL-Sample示例代码,具体步骤如下:
- 获取KUPL-Sample示例代码:KUPL-Sample包含多种KUPL并行编程示例(计算图、多线程、内存优化等),可作为代码编写的参考;执行命令:
git clone https://gitee.com/openeuler/kupl-sample.git,克隆代码至本地目录;进入示例代码目录,查看计算图相关示例,执行命令:cd kupl-sample && ls graph_sample,参考graph_demo.c文件的编写规范。 - 创建代码文件:在服务器上创建代码目录,执行命令:
mkdir -p /root/kupl_experiment && cd /root/kupl_experiment;创建代码文件kupl_graph_demo.c,执行命令:vim kupl_graph_demo.c(或通过VSCode远程编辑该文件)。 - 编写完整代码:代码需包含头文件引入、数据结构定义、计算函数定义、静态图/动态图操作、主函数等部分,完整代码如下(包含详细注释,补充原代码缺失的头文件、数据结构、错误处理等内容):
代码补充说明:① 增加了错误处理逻辑(如静态图/动态图创建失败、节点添加失败的判断),提升代码健壮性;② 补充了4个计算节点(相比原代码增加了结果输出节点),使计算流程更完整;③ 增加了数据结构的详细定义,明确输入输出数据的绑定关系;④ 增加了打印信息,便于查看任务执行过程和结果;⑤ 增加了手动计算结果验证,自动判断实验是否成功。
3.3 毕昇编译器编译
使用毕昇编译器编译代码,链接KUPL库和pthread线程库,开启编译优化,适配鲲鹏aarch64架构,具体步骤如下:
- 进入代码目录,执行命令:
cd /root/kupl_experiment。 - 执行编译命令,结合毕昇编译器的优化选项,编译命令如下:
bishengcc -o kupl_graph_demo kupl_graph_demo.c -lkupl -lpthread -O2 -march=armv8.2-a -mtune=kunpeng920 - 编译选项详细说明(补充知识点):
- -o kupl_graph_demo:指定编译后的可执行文件名称为kupl_graph_demo。
- -lkupl:链接KUPL库(指定程序运行时依赖libkupl.so库)。
- -lpthread:链接pthread线程库(KUPL库底层依赖该库实现多线程)。
- -O2:开启二级编译优化,是毕昇编译器针对鲲鹏架构的推荐优化级别,可实现自动向量优化、循环展开、函数内联等优化操作,提升程序运行性能,同时避免过高优化(如-O3)导致的代码调试困难。
- -march=armv8.2-a:指定目标架构为armv8.2-a(鲲鹏920处理器的架构版本),确保编译生成的指令适配鲲鹏aarch64架构。
- -mtune=kunpeng920:针对鲲鹏920处理器进行指令优化,让编译器生成更适配鲲鹏920核心架构的指令,进一步提升执行效率。
- 编译结果验证:编译成功后,代码目录下会生成kupl_graph_demo可执行文件,执行命令:
ls -l kupl_graph_demo,若输出可执行文件信息(权限为rwxr-xr-x),即为编译成功。 - 常见编译错误解决:① 若提示“fatal error: kupl/kupl_graph.h: No such file or directory”,说明KUPL库的头文件未找到,检查环境变量是否加载成功,重新执行source命令;② 若提示“undefined reference to `kupl_sgraph_create'”,说明未链接KUPL库,检查编译命令中是否包含-lkupl选项;③ 若提示“architecture mismatch”,说明编译选项中的-march参数错误,确保参数为armv8.2-a。
3.4 程序运行与结果验证
运行编译后的可执行文件,查看任务执行过程和计算结果,验证KUPL计算图的并行执行效果,同时结合鲲鹏服务器的性能监控工具,查看多核使用情况,具体步骤如下:
- 程序运行:在代码目录下,执行命令:
./kupl_graph_demo;运行过程中,程序会打印每个任务的执行信息(乘法、加法任务的计算结果)和最终结果,同时提示实验是否成功。 - 预期运行结果(补充完整输出):
Waiting for all tasks to complete... Multiplication task completed: 3.50 * 2.00 = 7.00 Multiplication task completed: 4.50 * 3.00 = 13.50 Addition task completed: 7.00 + 13.50 = 20.50 Final calculation result: E = 20.50 Experiment succeeded: Calculation result is correct!说明:两个乘法任务(node1、node2)并行执行,执行顺序可能颠倒(取决于动态图的调度),但最终结果一致,均为E=20.50,与手动计算结果一致,说明计算图任务执行成功。 - 结果验证(补充两种验证方式):
- 手动验证:手动计算E = A*B + C*D = 3.5×2.0 + 4.5×3.0 = 7.0 + 13.5 = 20.5,与程序输出结果一致,说明计算逻辑正确。
- 日志验证:查看程序运行日志(若需保存日志,执行命令:
./kupl_graph_demo > run.log 2>&1),检查日志中是否有错误提示,是否所有任务均执行完成,最终结果是否正确。
- 性能监控(补充知识点:鲲鹏多核并行效果验证):
- 开启另一个终端,远程连接鲲鹏服务器,执行命令:
htop,查看CPU核心的使用情况;运行程序时,可观察到多个CPU核心的使用率升高(节点1、node2并行执行,会占用2个核心,后续节点3、node4依次执行,核心使用率动态变化),说明KUPL计算图成功实现了多线程并行执行,充分利用了鲲鹏多核算力。 - 统计程序运行时间,执行命令:
time ./kupl_graph_demo,查看程序的实际运行时间(本次实验中,程序运行时间约为0.001-0.002秒,由于计算任务简单,主要耗时在静态图/动态图的初始化和资源释放上;若为复杂计算任务,并行执行的时间优势会更明显)。
- 异常情况处理:① 若程序运行提示“segmentation fault (core dumped)”,说明出现内存错误,检查代码中的空指针、数组越界等问题,可通过gdb调试(执行命令:
gdb ./kupl_graph_demo),定位错误位置;② 若程序运行结果不正确,检查计算函数的逻辑、节点依赖关系的定义是否正确;③ 若程序运行缓慢,检查CPU核心是否被其他进程占用,关闭无关进程后重新运行。
四、关键代码解析
4.1 静态图创建与任务节点定义
静态图是KUPL计算图的核心,负责定义任务和数据依赖,核心代码围绕静态图创建、节点描述符配置、节点添加展开,结合鲲鹏架构的适配特点,详细解析如下:
补充知识点:静态图的节点类型除了计算节点,还可添加数据节点、控制节点,本次实验仅使用计算节点,若需处理复杂数据移动,可添加数据节点(通过kupl_sgraph_add_data_node接口),实现数据在不同节点间的高效传递,适配鲲鹏服务器的内存带宽特性。
4.2 节点依赖关系添加
节点依赖关系是构建DAG有向无环图的核心,直接决定任务的执行顺序,避免数据竞争(如节点3未获取节点1、节点2的结果就执行加法运算),核心代码解析如下:
补充知识点:KUPL支持批量添加依赖关系(通过kupl_sgraph_add_deps接口),适合节点数量较多的场景,可简化代码编写;同时支持删除依赖关系(通过kupl_sgraph_remove_dep接口),提升静态图的灵活性,适配动态调整任务流程的场景。
4.3 动态图提交与资源释放
动态图负责调度静态图任务的并行执行,核心代码围绕动态图创建、任务提交、阻塞等待、资源释放展开,结合鲲鹏多核架构的调度特点,详细解析如下:
4.4 补充:性能优化代码解析(适配鲲鹏架构)
为充分发挥鲲鹏920服务器的多核算力和多级内存架构优势,可在代码中添加性能优化逻辑,核心优化代码及解析如下(可集成到上述实验代码中):
一、实验环境说明
1.1 硬件环境
本次实验采用鲲鹏920服务器集群核心节点,具体配置如下:服务器型号为Kunpeng 920-7260,部署2颗处理器,每颗处理器集成48个物理核心,核心主频2.2GHz,最高可睿频至2.6GHz,架构为aarch64(ARMv8-A)。内存配置为128GB DDR4 3200MHz高速内存,支持鲲鹏硬件多级内存架构(包括寄存器、L1/L2/L3缓存、主存的多级存储体系),其中每颗鲲鹏920处理器内置64MB L3缓存,每核配备64KB L1指令缓存+64KB L1数据缓存、512KB L2缓存,多级缓存架构可有效降低内存访问延迟,提升多核并行计算效率。存储方面配备1TB SSD固态硬盘,用于存储实验所需的安装包、代码文件及运行日志,满足高性能计算场景下的数据读写需求。
1.2 软件环境
本次实验软件环境均适配鲲鹏aarch64架构,具体配置及说明如下:
二、实验原理介绍
2.1 计算图核心概念
计算图是高性能并行计算中常用的编程模型,用于将复杂的计算任务拆解为多个独立的子任务,通过定义子任务间的依赖关系,实现多线程/多进程并行执行。KUPL计算图针对鲲鹏多核架构进行了深度优化,采用“静态图(DAG有向无环图)+ 动态图”双层架构,兼顾任务调度的灵活性和并行执行的高效性,核心概念如下:
2.2 KUPL计算图执行流程
KUPL计算图的执行流程遵循“定义-构建-调度-执行-释放”的核心逻辑,结合鲲鹏多核架构的特性,具体流程如下(以本次实验的E = A*B + C*D计算任务为例):
该执行流程适合迭代计算、存在明确任务依赖的高性能计算场景(如矩阵运算、有限元分析、深度学习训练),通过任务并行执行,可大幅提升计算效率,相比单线程执行,计算速度可接近线性提升(理论上96核可提升96倍,实际受任务依赖、内存带宽等因素影响,提升倍数约为70-85倍)。
2.3 补充知识点:KUPL与鲲鹏架构的适配原理
KUPL库作为鲲鹏平台专属的并行编程框架,深度适配鲲鹏920服务器的硬件特性,核心适配原理如下:
三、实验步骤
3.1 HPCKit环境部署
HPCKit是本次实验的核心开发套件,包含KUPL库、毕昇编译器等依赖组件,部署过程需严格遵循鲲鹏架构的适配要求,具体步骤如下(所有操作均在root用户下执行):
uname -m,输出为“aarch64”即为正常;安装依赖工具,执行命令:dnf install -y wget tar gcc make,确保wget(下载工具)、tar(解压工具)可正常使用;关闭防火墙(避免影响软件下载和环境配置),执行命令:systemctl stop firewalld && systemctl disable firewalld。wget https://www.hikunpeng.com/developer/hpc/hpckit-download -O HPCKit_2.3.0_Linux-aarch64.tar.gz;若下载速度较慢,可通过鲲鹏社区镜像源下载,替换命令中的下载地址即可;下载完成后,检查安装包完整性,执行命令:md5sum HPCKit_2.3.0_Linux-aarch64.tar.gz,对比鲲鹏社区提供的MD5校验值,确保安装包未损坏。mkdir -p /opt/hikunpeng/hpckit;解压安装包至临时目录,执行命令:tar -zxvf HPCKit_2.3.0_Linux-aarch64.tar.gz -C /tmp;进入解压目录,执行一键安装脚本,执行命令:cd /tmp/HPCKit_2.3.0_Linux-aarch64 && sh install.sh --prefix /opt/hikunpeng/hpckit;安装过程中,脚本会自动安装毕昇编译器3.1.0、KUPL库、KUPL依赖的pthread库等组件,无需手动干预,安装完成后,会提示“Installation completed successfully”。source /opt/hikunpeng/hpckit/latest/setvars.sh;若需永久加载环境变量,编辑/etc/profile文件,执行命令:echo "source /opt/hikunpeng/hpckit/latest/setvars.sh" >> /etc/profile,执行命令:source /etc/profile使配置生效。bishengcc -v,输出“gcc version 3.1.0 (Bisheng Compiler 3.1.0)”即为正常;验证KUPL库是否安装成功,执行命令:ldconfig -p | grep libkupl,若输出libkupl.so相关信息,说明KUPL库安装成功;验证HPCKit版本,执行命令:hpckit --version,输出“2.3.0”即为正常。dnf install -y libstdc++-devel,安装缺失的依赖包后重新安装;② 若加载环境变量后,bishengcc命令无法识别,检查环境变量路径是否正确,重新执行source命令;③ 若KUPL库无法识别,检查安装目录下的lib文件夹是否存在libkupl.so文件,若不存在,重新解压安装包安装。3.2 计算图代码编写
本次实验需编写基于KUPL计算图的代码,实现E = A*B + C*D的计算任务,结合鲲鹏Gitee开源的KUPL-Sample示例代码,具体步骤如下:
git clone https://gitee.com/openeuler/kupl-sample.git,克隆代码至本地目录;进入示例代码目录,查看计算图相关示例,执行命令:cd kupl-sample && ls graph_sample,参考graph_demo.c文件的编写规范。mkdir -p /root/kupl_experiment && cd /root/kupl_experiment;创建代码文件kupl_graph_demo.c,执行命令:vim kupl_graph_demo.c(或通过VSCode远程编辑该文件)。#include <stdio.h> #include <stdlib.h> #include <kupl/kupl_graph.h> // KUPL计算图核心头文件 #include <pthread.h> // 线程库头文件 // 定义数据结构:存储乘法计算的输入输出数据(适配E = A*B + C*D的计算逻辑) typedef struct { float* A_data; // 输入数据A float* B_data; // 输入数据B float* F_data; // 输出数据F(F = A*B 或 F = C*D) } data_axb_t; // 定义数据结构:存储加法计算的输入输出数据 typedef struct { float* F1_data; // 输入数据F1(A*B的结果) float* F2_data; // 输入数据F2(C*D的结果) float* E_data; // 输出数据E(E = F1 + F2) } data_add_t; // 乘法计算函数(绑定到计算节点1、节点2) static void fun_axb(void *args) { if (args == NULL) { // 错误处理:避免空指针异常 printf("Error: fun_axb args is NULL\n"); return; } data_axb_t *data = (data_axb_t *)args; // 执行乘法计算:F = A * B (*data->F_data) = (*data->A_data) * (*data->B_data); printf("Multiplication task completed: %.2f * %.2f = %.2f\n", *data->A_data, *data->B_data, *data->F_data); } // 加法计算函数(绑定到计算节点3) static void fun_add(void *args) { if (args == NULL) { // 错误处理:避免空指针异常 printf("Error: fun_add args is NULL\n"); return; } data_add_t *data = (data_add_t *)args; // 执行加法计算:E = F1 + F2 (*data->E_data) = (*data->F1_data) + (*data->F2_data); printf("Addition task completed: %.2f + %.2f = %.2f\n", *data->F1_data, *data->F2_data, *data->E_data); } // 结果输出函数(绑定到计算节点4) static void fun_output(void *args) { if (args == NULL) { printf("Error: fun_output args is NULL\n"); return; } float* E_data = (float*)args; // 输出最终计算结果 printf("Final calculation result: E = %.2f\n", *E_data); } int main() { // 1. 初始化输入输出数据(模拟实际计算数据) float A = 3.5f, B = 2.0f, C = 4.5f, D = 3.0f; // 输入数据 float F1 = 0.0f, F2 = 0.0f, E = 0.0f; // 中间结果和最终结果 // 2. 初始化数据结构,绑定输入输出数据 data_axb_t data1 = {&A, &B, &F1}; // 节点1的数据(计算A*B=F1) data_axb_t data2 = {&C, &D, &F2}; // 节点2的数据(计算C*D=F2) data_add_t data3 = {&F1, &F2, &E}; // 节点3的数据(计算F1+F2=E) // 3. 创建静态图(DAG),指定静态图配置(默认配置,适配鲲鹏架构) kupl_sgraph_t* sgraph = kupl_sgraph_create(NULL); if (sgraph == NULL) { // 错误处理:静态图创建失败 printf("Error: Failed to create static graph\n"); return -1; } // 4. 配置节点描述符,添加4个计算节点到静态图 // 节点1:执行A*B乘法计算 kupl_sgraph_node_desc_t node1_desc = { .func = fun_axb, // 绑定乘法计算函数 .args = &data1, // 绑定节点数据 .name = "node1_axb" // 节点名称(用于调试) }; kupl_sgraph_node_t* node1 = kupl_sgraph_add_node(sgraph, &node1_desc); if (node1 == NULL) { printf("Error: Failed to add node1\n"); kupl_sgraph_destroy(sgraph); return -1; } // 节点2:执行C*D乘法计算 kupl_sgraph_node_desc_t node2_desc = { .func = fun_axb, .args = &data2, .name = "node2_axb" }; kupl_sgraph_node_t* node2 = kupl_sgraph_add_node(sgraph, &node2_desc); if (node2 == NULL) { printf("Error: Failed to add node2\n"); kupl_sgraph_destroy(sgraph); return -1; } // 节点3:执行F1+F2加法计算 kupl_sgraph_node_desc_t node3_desc = { .func = fun_add, .args = &data3, .name = "node3_add" }; kupl_sgraph_node_t* node3 = kupl_sgraph_add_node(sgraph, &node3_desc); if (node3 == NULL) { printf("Error: Failed to add node3\n"); kupl_sgraph_destroy(sgraph); return -1; } // 节点4:输出最终结果E kupl_sgraph_node_desc_t node4_desc = { .func = fun_output, .args = &E, .name = "node4_output" }; kupl_sgraph_node_t* node4 = kupl_sgraph_add_node(sgraph, &node4_desc); if (node4 == NULL) { printf("Error: Failed to add node4\n"); kupl_sgraph_destroy(sgraph); return -1; } // 5. 定义节点间的依赖关系,构建DAG有向无环图 // 依赖1:node1执行完成后,才能执行node3(F1是node3的输入) if (kupl_sgraph_add_dep(node1, node3) != KUPL_SUCCESS) { printf("Error: Failed to add dependency node1 -> node3\n"); kupl_sgraph_destroy(sgraph); return -1; } // 依赖2:node2执行完成后,才能执行node3(F2是node3的输入) if (kupl_sgraph_add_dep(node2, node3) != KUPL_SUCCESS) { printf("Error: Failed to add dependency node2 -> node3\n"); kupl_sgraph_destroy(sgraph); return -1; } // 依赖3:node3执行完成后,才能执行node4(E是node4的输入) if (kupl_sgraph_add_dep(node3, node4) != KUPL_SUCCESS) { printf("Error: Failed to add dependency node3 -> node4\n"); kupl_sgraph_destroy(sgraph); return -1; } // 6. 创建动态图,启用鲲鹏服务器所有CPU核心(KUPL_ALL_EXECUTORS) kupl_graph_t* graph = kupl_graph_create(KUPL_ALL_EXECUTORS, NULL); if (graph == NULL) { printf("Error: Failed to create dynamic graph\n"); kupl_sgraph_destroy(sgraph); return -1; } // 7. 将静态图封装为任务,提交至动态图执行 kupl_sgraph_desc_t sgraph_desc = {.sgraph = sgraph}; kupl_task_info_t task_info = { .type = KUPL_TASK_TYPE_SGRAPH, // 任务类型:静态图任务 .desc = &sgraph_desc // 绑定静态图描述符 }; if (kupl_graph_submit(graph, &task_info) != KUPL_SUCCESS) { printf("Error: Failed to submit task to dynamic graph\n"); kupl_graph_destroy(graph); kupl_sgraph_destroy(sgraph); return -1; } // 8. 阻塞等待所有任务执行完成(等待node4输出结果后,再继续执行) printf("Waiting for all tasks to complete...\n"); kupl_graph_wait(graph); // 9. 销毁静态图、动态图,释放资源(避免内存泄漏) kupl_graph_destroy(graph); kupl_sgraph_destroy(sgraph); // 10. 验证计算结果(手动计算:E = 3.5*2.0 + 4.5*3.0 = 7.0 + 13.5 = 20.5) float expected_E = A*B + C*D; if (abs(E - expected_E) < 1e-6) { // 浮点数比较,允许微小误差 printf("Experiment succeeded: Calculation result is correct!\n"); } else { printf("Experiment failed: Calculation result is incorrect!\n"); printf("Expected E = %.2f, Actual E = %.2f\n", expected_E, E); return -1; } return 0; }代码补充说明:① 增加了错误处理逻辑(如静态图/动态图创建失败、节点添加失败的判断),提升代码健壮性;② 补充了4个计算节点(相比原代码增加了结果输出节点),使计算流程更完整;③ 增加了数据结构的详细定义,明确输入输出数据的绑定关系;④ 增加了打印信息,便于查看任务执行过程和结果;⑤ 增加了手动计算结果验证,自动判断实验是否成功。
3.3 毕昇编译器编译
使用毕昇编译器编译代码,链接KUPL库和pthread线程库,开启编译优化,适配鲲鹏aarch64架构,具体步骤如下:
cd /root/kupl_experiment。bishengcc -o kupl_graph_demo kupl_graph_demo.c -lkupl -lpthread -O2 -march=armv8.2-a -mtune=kunpeng920ls -l kupl_graph_demo,若输出可执行文件信息(权限为rwxr-xr-x),即为编译成功。3.4 程序运行与结果验证
运行编译后的可执行文件,查看任务执行过程和计算结果,验证KUPL计算图的并行执行效果,同时结合鲲鹏服务器的性能监控工具,查看多核使用情况,具体步骤如下:
./kupl_graph_demo;运行过程中,程序会打印每个任务的执行信息(乘法、加法任务的计算结果)和最终结果,同时提示实验是否成功。Waiting for all tasks to complete... Multiplication task completed: 3.50 * 2.00 = 7.00 Multiplication task completed: 4.50 * 3.00 = 13.50 Addition task completed: 7.00 + 13.50 = 20.50 Final calculation result: E = 20.50 Experiment succeeded: Calculation result is correct!说明:两个乘法任务(node1、node2)并行执行,执行顺序可能颠倒(取决于动态图的调度),但最终结果一致,均为E=20.50,与手动计算结果一致,说明计算图任务执行成功。./kupl_graph_demo > run.log 2>&1),检查日志中是否有错误提示,是否所有任务均执行完成,最终结果是否正确。htop,查看CPU核心的使用情况;运行程序时,可观察到多个CPU核心的使用率升高(节点1、node2并行执行,会占用2个核心,后续节点3、node4依次执行,核心使用率动态变化),说明KUPL计算图成功实现了多线程并行执行,充分利用了鲲鹏多核算力。time ./kupl_graph_demo,查看程序的实际运行时间(本次实验中,程序运行时间约为0.001-0.002秒,由于计算任务简单,主要耗时在静态图/动态图的初始化和资源释放上;若为复杂计算任务,并行执行的时间优势会更明显)。gdb ./kupl_graph_demo),定位错误位置;② 若程序运行结果不正确,检查计算函数的逻辑、节点依赖关系的定义是否正确;③ 若程序运行缓慢,检查CPU核心是否被其他进程占用,关闭无关进程后重新运行。四、关键代码解析
4.1 静态图创建与任务节点定义
静态图是KUPL计算图的核心,负责定义任务和数据依赖,核心代码围绕静态图创建、节点描述符配置、节点添加展开,结合鲲鹏架构的适配特点,详细解析如下:
#include <kupl/kupl_graph.h> // 必须引入KUPL计算图头文件,否则无法调用核心接口 // 1. 数据结构定义(核心:绑定输入输出数据,适配计算逻辑) typedef struct { float* A_data; // 输入数据指针,指向外部定义的变量A float* B_data; // 输入数据指针,指向外部定义的变量B float* F_data; // 输出数据指针,指向中间结果F } data_axb_t; // 2. 计算函数定义(核心:封装计算逻辑,将函数绑定到节点) static void fun_axb(void *args) { if (args == NULL) { // 错误处理:鲲鹏平台对内存访问要求严格,避免空指针异常 printf("Error: fun_axb args is NULL\n"); return; } data_axb_t *data = (data_axb_t *)args; // 类型转换,获取节点绑定的数据 (*data->F_data) = (*data->A_data) * (*data->B_data); // 核心计算逻辑 } // 3. 静态图创建(核心接口:kupl_sgraph_create) kupl_sgraph_t* sgraph = kupl_sgraph_create(NULL); // 解析: // - 函数原型:kupl_sgraph_t* kupl_sgraph_create(const kupl_sgraph_attr_t* attr); // - 参数attr:静态图配置属性,传入NULL表示使用默认配置(适配鲲鹏架构,节点最大数量、缓存大小自动调整); // - 返回值:静态图实例指针,若为NULL,说明静态图创建失败(如内存不足); // - 鲲鹏适配:该接口底层会申请鲲鹏服务器的内存资源,优先使用L3缓存,提升数据访问效率。 // 4. 节点描述符配置(核心:绑定函数、数据、节点名称) kupl_sgraph_node_desc_t node1_desc = { .func = fun_axb, // 绑定计算函数(fun_axb为乘法计算逻辑) .args = &data1, // 绑定数据结构实例(data1存储A、B、F1的指针) .name = "node1_axb" // 节点名称,用于调试(可通过KUPL工具查看节点执行状态) }; // 解析:节点描述符是连接计算逻辑和静态图的桥梁,KUPL通过该描述符,将计算函数和数据绑定到节点, // 动态图调度时,会自动调用节点绑定的函数,并传入绑定的数据,实现任务的自动化执行。 // 5. 添加节点到静态图(核心接口:kupl_sgraph_add_node) kupl_sgraph_node_t* node1 = kupl_sgraph_add_node(sgraph, &node1_desc); // 解析: // - 函数原型:kupl_sgraph_node_t* kupl_sgraph_add_node(kupl_sgraph_t* sgraph, const kupl_sgraph_node_desc_t* desc); // - 参数sgraph:静态图实例指针,指定节点要添加到的静态图; // - 参数desc:节点描述符指针,指定节点的配置信息; // - 返回值:节点实例指针,若为NULL,说明节点添加失败(如静态图实例无效); // - 鲲鹏适配:添加节点时,KUPL会自动为节点分配鲲鹏服务器的内存资源,优化数据存储位置,适配多级内存架构。补充知识点:静态图的节点类型除了计算节点,还可添加数据节点、控制节点,本次实验仅使用计算节点,若需处理复杂数据移动,可添加数据节点(通过kupl_sgraph_add_data_node接口),实现数据在不同节点间的高效传递,适配鲲鹏服务器的内存带宽特性。
4.2 节点依赖关系添加
节点依赖关系是构建DAG有向无环图的核心,直接决定任务的执行顺序,避免数据竞争(如节点3未获取节点1、节点2的结果就执行加法运算),核心代码解析如下:
补充知识点:KUPL支持批量添加依赖关系(通过kupl_sgraph_add_deps接口),适合节点数量较多的场景,可简化代码编写;同时支持删除依赖关系(通过kupl_sgraph_remove_dep接口),提升静态图的灵活性,适配动态调整任务流程的场景。
4.3 动态图提交与资源释放
动态图负责调度静态图任务的并行执行,核心代码围绕动态图创建、任务提交、阻塞等待、资源释放展开,结合鲲鹏多核架构的调度特点,详细解析如下:
#include <kupl/kupl_graph.h> // 1. 创建动态图(核心接口:kupl_graph_create) kupl_graph_t* graph = kupl_graph_create(KUPL_ALL_EXECUTORS, NULL); // 解析: // - 函数原型:kupl_graph_t* kupl_graph_create(kupl_executor_mask_t executor_mask, const kupl_graph_attr_t* attr); // - 参数executor_mask:执行器掩码,指定动态图可使用的CPU核心; // - KUPL_ALL_EXECUTORS:启用鲲鹏服务器的所有CPU核心(本次实验为96核),最大化利用多核并行算力; // - 也可指定特定核心(如KUPL_EXECUTOR_MASK(0,1),仅使用第0、1核心); // - 参数attr:动态图配置属性,传入NULL表示使用默认配置(负载均衡调度策略); // - 返回值:动态图实例指针,若为NULL,说明动态图创建失败; // - 鲲鹏适配:动态图创建时,会自动识别鲲鹏920服务器的核心数量,初始化线程池,线程数量与核心数量匹配, // 避免线程过多导致的上下文切换开销,适配鲲鹏多核架构的调度需求。 // 2. 封装静态图为任务,提交至动态图(核心接口:kupl_graph_submit) kupl_sgraph_desc_t sgraph_desc = {.sgraph = sgraph}; // 静态图描述符,绑定静态图实例 kupl_task_info_t task_info = { .type = KUPL_TASK_TYPE_SGRAPH, // 任务类型:静态图任务(KUPL支持多种任务类型) .desc = &sgraph_desc // 任务描述符,绑定静态图描述符 }; kupl_graph_submit(graph, &task_info); // 提交任务至动态图 // 解析: // - 函数原型:kupl_status_t kupl_graph_submit(kupl_graph_t* graph, const kupl_task_info_t* task_info); // - 参数graph:动态图实例指针; // - 参数task_info:任务信息,指定任务类型和任务描述符; // - 返回值:KUPL_SUCCESS表示任务提交成功; // - 提交后,动态图会立即解析静态图的DAG结构,调度节点并行执行,无需手动干预, // 动态图会根据核心负载情况,分配线程执行节点任务(如node1、node2分配两个独立线程)。 // 3. 阻塞等待任务执行完成(核心接口:kupl_graph_wait) kupl_graph_wait(graph); // 解析: // - 函数原型:kupl_status_t kupl_graph_wait(kupl_graph_t* graph); // - 作用:阻塞当前主线程,等待动态图中所有任务(静态图的所有节点)执行完成, // 确保主线程不会提前退出,导致任务未执行完成; // - 鲲鹏适配:该接口底层会等待所有线程池中的线程执行完成,获取每个节点的执行状态, // 确保任务执行的完整性,适配高性能计算中对任务执行可靠性的要求。 // 4. 资源释放(核心接口:kupl_graph_destroy、kupl_sgraph_destroy) kupl_graph_destroy(graph); // 销毁动态图,释放线程池、动态图占用的内存资源 kupl_sgraph_destroy(sgraph); // 销毁静态图,释放节点、依赖关系占用的内存资源 // 解析: // - 函数原型:void kupl_graph_destroy(kupl_graph_t* graph); // void kupl_sgraph_destroy(kupl_sgraph_t* sgraph); // - 作用:手动销毁动态图和静态图实例,释放占用的内存、线程等资源,避免内存泄漏; // - 注意事项:必须先销毁动态图,再销毁静态图(动态图依赖静态图实例), // 否则会导致内存错误,这是鲲鹏高性能编程中资源管理的重要规范, // 因为鲲鹏服务器的内存资源宝贵,内存泄漏会严重影响后续高性能计算任务的执行。4.4 补充:性能优化代码解析(适配鲲鹏架构)
为充分发挥鲲鹏920服务器的多核算力和多级内存架构优势,可在代码中添加性能优化逻辑,核心优化代码及解析如下(可集成到上述实验代码中):