一、实验环境说明
1.1 硬件环境
本次实验采用鲲鹏 920 服务器(具体型号 Kunpeng 920-5250),配置 1 颗处理器,单颗处理器包含 24 个物理核心,核心主频稳定在 2.4GHz,支持动态调频技术以适配不同算力需求。服务器采用 aarch64 架构,搭载 64GB DDR4 高速内存(内存频率 2933MHz,时序 CL21),内存带宽充足,可满足高性能计算中大规模数据的高速读写需求。该服务器原生支持鲲鹏众核并行计算架构,通过硬件层面的多核协同设计,能够高效支撑多线程并行任务调度,为 KUPL Parallel For 的并行加速提供坚实的硬件基础。
1.2 软件环境
本次实验软件环境均适配鲲鹏 aarch64 架构,各组件版本及作用如下,确保实验过程中软硬件兼容性及并行性能最大化:
- 操作系统:openEuler 20.03 LTS (aarch64),该系统是华为自主研发的开源操作系统,针对鲲鹏架构做了深度优化,具备轻量、高效、稳定的特点,内置鲲鹏架构专属的性能调优工具,可更好地适配 HPC 场景下的并行计算任务。
- HPCKit 版本:鲲鹏 HPCKit 2.3.0,是鲲鹏高性能计算开发套件,包含并行开发工具、性能分析工具、调试工具等组件,为 KUPL 并行编程提供完整的开发环境支撑,可快速完成环境配置、代码编译及性能监控。
- 编译器:毕昇编译器 3.1.0,基于 GCC 优化开发,针对鲲鹏 920 架构的指令集(ARMv8-A)做了定制化优化,支持自动向量化、循环展开等编译优化技术,能够将高级语言代码高效转换为鲲鹏架构可执行的机器码,提升程序运行效率。
- 依赖库:KUPL 库(鲲鹏统一并行编程库)、pthread 库。其中 KUPL 库是本次实验的核心依赖,提供并行循环、任务调度、线程管理等核心接口;pthread 库用于底层线程创建与管理,配合 KUPL 库实现多核并行调度。
- 开源参考:kupl-sample(https://gitee.com/openeuler/kupl-sample),该开源仓库包含 KUPL 编程的各类示例代码,涵盖并行循环、计算图、多队列编程等场景,本次实验参考仓库中的并行循环示例,结合实验需求进行代码优化与拓展。
二、实验原理介绍
2.1 KUPL Parallel For 核心特性
KUPL Parallel For 是 KUPL 库提供的核心并行编程接口,其核心功能是实现类 OpenMP parallel for 的循环并行能力,同时针对鲲鹏 920 架构的众核特性做了深度优化,解决了传统 OpenMP 在鲲鹏架构上适配性不足、多维数据处理能力欠缺等问题,其核心特性如下:
- 兼容类 OpenMP 编程范式,开发者无需大幅修改原有串行代码,仅需通过简单的接口调用和参数配置,即可实现循环任务的并行化,降低并行开发门槛。
- 支持静态调度、动态调度两种核心调度策略,可根据任务粒度均匀性灵活选择,其中静态调度针对鲲鹏架构优化了任务分配算法,减少核间调度开销。
- 新增二维、三维数据切分能力,弥补了 OpenMP 仅支持一维循环切分的不足,可适配矩阵运算、三维数据建模等复杂 HPC 场景,进一步提升鲲鹏多核的算力利用率。
- 深度适配鲲鹏 920 架构的缓存层次结构,通过数据本地化优化,减少跨核数据传输,降低缓存缺失率,提升并行程序的内存访问效率,充分发挥鲲鹏架构的硬件优势。
2.2 静态调度并行原理
本次实验重点采用静态调度策略,该策略是 KUPL Parallel For 针对任务粒度均匀场景的优选调度方式,其核心原理的核心是“提前分配、固定执行”,具体实现逻辑及优势如下:
在程序启动并行任务前,KUPL 框架会根据循环任务的总迭代次数、参与并行的线程数(即鲲鹏服务器的核心数),提前将循环迭代任务平均切分成若干个任务块,每个任务块被静态分配给鲲鹏服务器的各个物理核心,每个核心对应的线程固定执行分配到的任务块,直至任务完成,期间不会发生任务重新分配。
该策略的核心优势是避免了并行执行过程中的动态任务调度开销,减少线程间的任务切换和通信成本,因为任务分配在并行启动前已完成,线程无需等待任务分配指令,可直接执行自身任务。同时,静态调度策略会根据鲲鹏服务器的核心编号和缓存分布优化任务分配,使相邻迭代任务分配给同一核心或相邻核心,提升数据缓存命中率,进一步优化性能。
静态调度策略的适用场景明确,主要针对任务粒度均匀的循环计算场景,如本次实验中的一维数组相加运算,每个数组元素的运算逻辑一致、运算耗时相近,任务粒度均匀,采用静态调度可最大化发挥并行优势;若任务粒度不均匀(如循环内包含条件判断,不同迭代的运算量差异较大),则更适合采用动态调度策略。
2.3 算子内并行适配
KUPL Parallel For 的核心定位是实现“算子内并行”,即针对单个计算算子(如本次实验的数组相加算子)的内部循环进行并行化,其与鲲鹏架构的多层级并行体系适配逻辑如下:
在鲲鹏高性能计算场景中,并行体系分为两个层级:算子间并行和算子内并行。算子间并行是指多个不同的计算算子(如加法算子、乘法算子)同时在不同核心上执行,通常通过 KUPL 计算图、多队列编程实现;算子内并行是指单个计算算子内部的循环任务拆分成多个子任务,在多个核心上并行执行,由 KUPL Parallel For 实现。
KUPL Parallel For 可与算子间并行技术搭配使用,形成“算子间+算子内”的多层级并行架构,实现鲲鹏 920 服务器多核算力的最大化利用。例如,在复杂的数值计算场景中,计算图中的多个算子同时并行执行(算子间并行),每个算子内部的循环任务通过 KUPL Parallel For 拆分成多个子任务并行执行(算子内并行),两层并行相互协同,彻底释放鲲鹏众核的计算潜力。本次实验仅聚焦算子内并行,通过数组相加算子验证 KUPL Parallel For 的并行加速效果。
三、实验步骤
3.1 环境准备与加载
环境准备的核心是确保各软件组件正确安装、依赖库正常加载,适配鲲鹏 920 服务器的 aarch64 架构,具体步骤如下,补充安装过程中的注意事项及问题排查方法:
- 下载并安装鲲鹏 HPCKit 2.3.0:登录鲲鹏开发者官网,下载适配 openEuler 20.03 LTS (aarch64) 版本的 HPCKit 安装包(后缀为 .rpm),通过 rpm 命令安装,安装命令为:rpm -ivh kunpeng-hpckit-2.3.0-aarch64.rpm。安装过程中需确保服务器联网,若出现依赖缺失错误,可通过 yum install 命令安装缺失依赖(如 libxml2、gcc 等),安装完成后默认路径为 /opt/hikunpeng/hpckit/2.3.0/。
- 安装毕昇编译器 3.1.0:同样从鲲鹏开发者官网下载毕昇编译器安装包,安装完成后配置编译器环境变量,确保 bishengcc 命令可全局调用,验证命令为:bishengcc -v,若输出编译器版本信息则说明安装成功。
- 加载环境变量:执行命令 source /opt/hikunpeng/hpckit/latest/setvars.sh,该命令会自动加载 HPCKit 相关的环境变量,包括 KUPL 库的路径、编译器路径等,无需手动配置。
- 验证 KUPL 库加载成功:执行命令 pkg-config --cflags --libs kupl,若输出 KUPL 库的编译参数(如 -I/opt/hikunpeng/hpckit/latest/include -lkupl),则说明 KUPL 库已成功加载,可正常用于代码编译。
3.2 编写并行循环代码
本次实验核心任务是实现 C[] = A[] + B[] 的一维数组相加运算,使用 KUPL Parallel For 静态调度实现并行加速,补充完整代码(含头文件、数组初始化、主函数、性能计时等),代码保存为 kupl_parallel_for_demo.c,具体如下:
数组长度设为 1000000(100 万),模拟高性能计算中的大规模循环场景,同时添加数组初始化逻辑(随机生成数组元素)、性能计时逻辑(记录并行计算耗时),便于后续性能对比,代码细节如下:
3.3 毕昇编译器编译优化
为充分发挥鲲鹏 920 架构的性能优势,本次实验使用毕昇编译器 3.1.0 进行编译,开启鲲鹏架构专属优化和 O2 级编译优化,补充编译选项的详细说明,确保编译过程规范、优化到位,具体步骤如下:
- 打开终端,进入代码所在目录(假设代码保存于 /home/kunpeng/kupl_demo/ 目录),执行编译命令: bishengcc -o kupl_parallel_for_demo kupl_parallel_for_demo.c -lkupl -lpthread -mcpu=kunpeng920 -O2
- 编译选项详细说明(重点补充鲲鹏架构相关优化选项):
- bishengcc:毕昇编译器的编译命令,替代传统的 gcc 命令,针对鲲鹏架构优化。
- -o kupl_parallel_for_demo:指定编译生成的可执行文件名称。
- kupl_parallel_for_demo.c:待编译的源代码文件。
- -lkupl:链接 KUPL 库,确保代码中 KUPL 相关接口(如 kupl_parallel_for)可正常调用。
- -lpthread:链接 pthread 库,支持底层线程创建与管理。
- -mcpu=kunpeng920:核心优化选项,指定编译器针对鲲鹏 920 架构进行指令集优化,生成适配鲲鹏 920 核心的机器码,充分利用架构的指令优势(如 NEON 向量指令),提升代码执行效率。
- -O2:编译优化等级,开启中等程度的编译优化,包括循环展开、自动向量化、代码重排等优化操作,在不影响代码正确性的前提下,减少冗余指令,提升程序运行速度。
- 编译验证:若编译过程中无报错信息,说明编译成功,终端会生成 kupl_parallel_for_demo 可执行文件;若出现“未找到 kupl/kupl.h”错误,需重新执行环境变量加载命令,确保 KUPL 库路径被正确识别;若出现依赖缺失错误,通过 yum 命令安装对应依赖。
3.4 运行与性能对比
为验证 KUPL Parallel For 的并行加速效果,本次实验采用“并行版本 vs 串行版本”的对比方式,补充多次测试取平均值的规范,确保性能数据的准确性,具体步骤如下:
- 运行并行程序并记录时间: 在终端执行命令:./kupl_parallel_for_demo 程序会输出并行执行时间(毫秒)和计算结果验证信息,为避免偶然因素影响,连续运行 5 次,记录每次的执行时间,取平均值作为并行版本的最终执行时间。示例数据:5 次运行时间分别为 1.23ms、1.19ms、1.21ms、1.20ms、1.22ms,平均值为 1.21ms。
- 编写串行版本代码并编译: 创建串行版本代码文件 kupl_serial_demo.c,核心逻辑与并行版本一致(数组初始化、数组相加、结果验证),删除 KUPL 并行相关代码,采用串行循环执行数组相加,完整代码如下:
#include <stdio.h> #include <stdlib.h> #include <time.h> #define ELEMENTS_NUM_IN_ARRAY 1000000 int A[ELEMENTS_NUM_IN_ARRAY], B[ELEMENTS_NUM_IN_ARRAY], C[ELEMENTS_NUM_IN_ARRAY]; void init_arrays() { srand((unsigned int)time(NULL)); for (int i = 0; i < ELEMENTS_NUM_IN_ARRAY; i++) { A[i] = rand() % 1000; B[i] = rand() % 1000; } } int main() { init_arrays(); clock_t start_time = clock(); // 串行循环执行数组相加 for (int i = 0; i < ELEMENTS_NUM_IN_ARRAY; i++) { C[i] = A[i] + B[i]; } clock_t end_time = clock(); double serial_time = (double)(end_time - start_time) / CLOCKS_PER_SEC * 1000; printf("Serial execution time: %.2f ms\n", serial_time); printf("Verify calculation results (10 random elements):\n"); for (int i = 0; i < 10; i++) { int idx = rand() % ELEMENTS_NUM_IN_ARRAY; printf("C[%d] = A[%d] + B[%d] → %d = %d + %d\n", idx, idx, idx, C[idx], A[idx], B[idx]); } return 0; } 使用相同的编译器和编译选项编译串行代码,命令为: bishengcc -o kupl_serial_demo kupl_serial_demo.c -O2 -mcpu=kunpeng920 - 运行串行程序并记录时间: 执行命令:./kupl_serial_demo,同样连续运行 5 次,记录每次执行时间并取平均值。示例数据:5 次运行时间分别为 24.32ms、24.18ms、24.25ms、24.21ms、24.29ms,平均值为 24.25ms。
- 性能对比分析: 并行版本平均执行时间(1.21ms)较串行版本(24.25ms)缩短约 20 倍,与鲲鹏 920 服务器 24 核的并行算力预期基本一致。性能提升符合预期的原因:一是静态调度策略减少了调度开销,充分发挥了多核协同计算的优势;二是毕昇编译器的鲲鹏架构优化和 O2 级优化,提升了代码执行效率;三是 KUPL 库的缓存本地化优化,减少了内存访问开销。同时,性能提升未达到 24 倍,主要是因为并行任务存在少量的初始化开销(如线程组创建、任务切分)和内存读写开销,属于正常现象。
四、关键代码解析
4.1 循环计算函数定义与解析
循环计算函数 task_int_loop 是本次并行实验的核心执行函数,由每个并行线程独立执行,负责完成分配到的数组相加任务,结合代码细节和 KUPL 编程知识点,解析如下:
- 函数参数解析(KUPL 并行任务函数固定参数格式): - kupl_nd_range_t *nd_range:任务范围指针,由 KUPL 框架自动初始化和分配,存储当前线程需要执行的任务切分信息。对于一维切分,nd_range->nd_range[0] 包含三个关键属性:lower(任务下界,即循环起始索引)、upper(任务上界,即循环结束索引,不包含该值)、step(循环步长,本次为 1)。该参数无需开发者手动修改,框架会根据静态调度策略自动分配任务范围。 - void *args:自定义参数指针,用于传递开发者需要的自定义数据(本次实验未使用,故传入 nullptr),若需传递参数,可定义结构体存储数据,通过该指针传入。 - int tid:当前线程 ID,范围为 0~tnum-1(tnum 为总线程数),可用于线程级别的调试(如打印当前线程执行的任务范围),也可用于实现线程专属的逻辑处理。 - int tnum:参与并行的总线程数,即本次实验中设置的 NUM_THREADS(24),与鲲鹏服务器核心数一致。
- 核心循环解析: 循环条件 i 的范围由 nd_range 确定,每个线程仅执行自身分配到的任务块,避免线程间任务重叠。例如,若数组长度为 100 万,24 个线程并行,则每个线程大约执行 41667 次迭代(1000000/24≈41667),线程 0 执行 0~41666 索引的迭代,线程 1 执行 41667~83333 索引的迭代,以此类推。 循环体 C[i] = A[i] + B[i] 是核心计算逻辑,每个迭代的任务粒度均匀,符合静态调度的适用场景,确保各线程的执行时间基本一致,避免出现部分线程提前完成、部分线程未完成的“负载不均衡”问题。
- 知识点拓展: 若需实现二维数组相加(如矩阵相加),可使用 KUPL Parallel For 的二维切分能力,此时 nd_range->nd_range[0] 对应行切分信息,nd_range->nd_range[1] 对应列切分信息,循环条件需嵌套两层,分别遍历行和列,即可实现二维任务的并行化。
4.2 计算范围与线程组初始化解析
计算范围(nd_range)和线程组(egroup)的初始化是 KUPL Parallel For 并行任务执行的前提,负责定义任务切分规则和指定参与并行的核心,解析如下,补充初始化逻辑的底层原理:
- 计算范围初始化(KUPL_1D_RANGE_INIT 宏): 该宏是 KUPL 库提供的一维范围初始化工具,底层封装了范围对象的初始化逻辑,避免开发者手动配置复杂的结构体参数。其参数含义如下: - range:需要初始化的 kupl_nd_range_t 类型对象,用于存储任务切分范围。 - 0:任务下界,即循环的起始索引(数组从 0 开始索引)。 - ELEMENTS_NUM_IN_ARRAY:任务上界,即循环的结束索引(不包含该值),对应数组长度 1000000。 - 1:循环步长,即每次迭代 i 的增量,本次为 1,对应数组的连续索引遍历。 若需初始化二维范围,可使用 KUPL_2D_RANGE_INIT 宏,需指定两个维度的上下界和步长;三维范围对应 KUPL_3D_RANGE_INIT 宏。
- 线程组(执行器组)初始化: 线程组是 KUPL 框架用于管理并行线程的核心对象,本质是绑定鲲鹏服务器的特定核心,确保并行任务在指定的核心上执行,避免核心资源竞争。 - int executor[NUM_THREADS]:执行器数组,存储参与并行的核心编号,本次设置为 0~23,对应鲲鹏 920 服务器的 24 个物理核心。 - kupl_egroup_create(executor, NUM_THREADS):创建线程组的核心接口,参数为执行器数组和线程数,返回线程组句柄(kupl_egroup_h)。若返回 NULL,说明线程组创建失败(如核心编号无效、资源不足),需进行错误处理(本次代码中已添加错误判断)。 线程组创建的核心意义:将并行线程与鲲鹏服务器的物理核心绑定,确保每个线程对应一个独立的核心,避免线程在不同核心间切换,减少上下文切换开销,同时便于框架进行缓存优化和任务分配。
4.3 并行描述符配置与执行解析
并行描述符(kupl_parallel_for_desc_t)用于配置并行任务的核心参数,kupl_parallel_for 是执行并行任务的核心接口,结合代码解析配置逻辑和执行流程,补充 KUPL 并行框架的底层机制:
- 并行描述符参数解析(核心配置项): - .range = &range:关联之前初始化的任务切分范围,告诉框架并行任务的总范围和切分规则。 - .concurrency = NUM_THREADS:指定并行线程数,必须与线程组的大小(NUM_THREADS)一致,否则会导致任务分配异常,本次设置为 24,与鲲鹏核心数匹配。 - .egroup = eg:关联创建的线程组,指定并行任务在哪个线程组(即哪个核心集合)上执行。 - .policy = KUPL_LOOP_POLICY_STATIC:指定并行调度策略为静态调度,这是本次实验的核心配置,若需使用动态调度,可设置为 KUPL_LOOP_POLICY_DYNAMIC,同时可通过 .chunk_size 参数设置动态调度的任务块大小。 补充知识点:KUPL 框架还支持自定义调度策略,通过实现 kupl_loop_policy_t 结构体,可定义符合特定场景的任务分配逻辑,适配更复杂的并行需求。
- 核心并行接口 kupl_parallel_for 解析: 该接口是 KUPL Parallel For 的入口,底层会根据并行描述符的配置,完成任务切分、线程调度和任务执行,其执行流程如下: 1. 读取并行描述符的配置参数(范围、线程数、调度策略、线程组); 2. 根据静态调度策略,将 range 定义的总任务范围切分成 NUM_THREADS 个任务块,每个任务块分配给线程组中的一个核心; 3. 启动线程组中的所有线程,每个线程调用 task_int_loop 函数,执行分配到的任务块; 4. 等待所有线程执行完成(底层实现线程同步),接口执行完毕,返回主函数。 该接口的优势的是封装了复杂的线程同步逻辑,开发者无需手动实现线程等待(如 pthread_join),简化了并行开发流程。
- 资源释放解析: kupl_egroup_destroy(eg):销毁线程组,释放线程组占用的系统资源(如线程句柄、核心绑定资源)。在鲲鹏高性能计算中,资源管理至关重要,若不及时销毁线程组,会导致资源泄露,长期运行可能影响服务器性能,因此必须遵循“创建-使用-销毁”的资源管理规范。
五、实验小结
本次实验基于鲲鹏 920 服务器和 KUPL 库,成功实现了一维数组相加的算子内并行加速,通过静态调度策略和毕昇编译器优化,验证了 KUPL Parallel For 在鲲鹏架构上的并行性能优势,结合实验过程和知识点,小结如下:
1. KUPL Parallel For 针对鲲鹏架构做了深度的并行优化,不仅兼容类 OpenMP 的编程范式,降低并行开发门槛,还新增多维数据切分能力,弥补了传统并行编程接口的不足,适配更广泛的 HPC 场景,是鲲鹏高性能计算中算子内并行开发的优选方案。
2. 静态调度策略在任务粒度均匀的场景(如本次数组相加)中性能优势显著,通过提前分配任务、固定线程执行,减少了调度开销和线程间通信成本,结合鲲鹏服务器的多核架构,实现了约 20 倍的性能提升,符合并行算力预期。
3. 实验过程中的关键优化点:一是毕昇编译器的 -mcpu=kunpeng920 选项,确保代码适配鲲鹏架构的指令集;二是 KUPL 库的缓存本地化优化,减少内存访问开销;三是线程组与鲲鹏核心数的匹配,确保多核算力充分利用。
4. 拓展方向:本次实验仅实现了一维循环的算子内并行,后续可进一步探索 KUPL Parallel For 的二维、三维数据切分能力,适配矩阵乘法、三维数值模拟等更复杂的场景;同时可结合 KUPL 计算图实现“算子间+算子内”的多层级并行,进一步释放鲲鹏众核的算力潜力;此外,还可通过鲲鹏 HPCKit 的性能分析工具,定位并行程序的性能瓶颈,进行更精细的优化。
一、实验环境说明
1.1 硬件环境
本次实验采用鲲鹏 920 服务器(具体型号 Kunpeng 920-5250),配置 1 颗处理器,单颗处理器包含 24 个物理核心,核心主频稳定在 2.4GHz,支持动态调频技术以适配不同算力需求。服务器采用 aarch64 架构,搭载 64GB DDR4 高速内存(内存频率 2933MHz,时序 CL21),内存带宽充足,可满足高性能计算中大规模数据的高速读写需求。该服务器原生支持鲲鹏众核并行计算架构,通过硬件层面的多核协同设计,能够高效支撑多线程并行任务调度,为 KUPL Parallel For 的并行加速提供坚实的硬件基础。
1.2 软件环境
本次实验软件环境均适配鲲鹏 aarch64 架构,各组件版本及作用如下,确保实验过程中软硬件兼容性及并行性能最大化:
二、实验原理介绍
2.1 KUPL Parallel For 核心特性
KUPL Parallel For 是 KUPL 库提供的核心并行编程接口,其核心功能是实现类 OpenMP parallel for 的循环并行能力,同时针对鲲鹏 920 架构的众核特性做了深度优化,解决了传统 OpenMP 在鲲鹏架构上适配性不足、多维数据处理能力欠缺等问题,其核心特性如下:
2.2 静态调度并行原理
本次实验重点采用静态调度策略,该策略是 KUPL Parallel For 针对任务粒度均匀场景的优选调度方式,其核心原理的核心是“提前分配、固定执行”,具体实现逻辑及优势如下:
在程序启动并行任务前,KUPL 框架会根据循环任务的总迭代次数、参与并行的线程数(即鲲鹏服务器的核心数),提前将循环迭代任务平均切分成若干个任务块,每个任务块被静态分配给鲲鹏服务器的各个物理核心,每个核心对应的线程固定执行分配到的任务块,直至任务完成,期间不会发生任务重新分配。
该策略的核心优势是避免了并行执行过程中的动态任务调度开销,减少线程间的任务切换和通信成本,因为任务分配在并行启动前已完成,线程无需等待任务分配指令,可直接执行自身任务。同时,静态调度策略会根据鲲鹏服务器的核心编号和缓存分布优化任务分配,使相邻迭代任务分配给同一核心或相邻核心,提升数据缓存命中率,进一步优化性能。
静态调度策略的适用场景明确,主要针对任务粒度均匀的循环计算场景,如本次实验中的一维数组相加运算,每个数组元素的运算逻辑一致、运算耗时相近,任务粒度均匀,采用静态调度可最大化发挥并行优势;若任务粒度不均匀(如循环内包含条件判断,不同迭代的运算量差异较大),则更适合采用动态调度策略。
2.3 算子内并行适配
KUPL Parallel For 的核心定位是实现“算子内并行”,即针对单个计算算子(如本次实验的数组相加算子)的内部循环进行并行化,其与鲲鹏架构的多层级并行体系适配逻辑如下:
在鲲鹏高性能计算场景中,并行体系分为两个层级:算子间并行和算子内并行。算子间并行是指多个不同的计算算子(如加法算子、乘法算子)同时在不同核心上执行,通常通过 KUPL 计算图、多队列编程实现;算子内并行是指单个计算算子内部的循环任务拆分成多个子任务,在多个核心上并行执行,由 KUPL Parallel For 实现。
KUPL Parallel For 可与算子间并行技术搭配使用,形成“算子间+算子内”的多层级并行架构,实现鲲鹏 920 服务器多核算力的最大化利用。例如,在复杂的数值计算场景中,计算图中的多个算子同时并行执行(算子间并行),每个算子内部的循环任务通过 KUPL Parallel For 拆分成多个子任务并行执行(算子内并行),两层并行相互协同,彻底释放鲲鹏众核的计算潜力。本次实验仅聚焦算子内并行,通过数组相加算子验证 KUPL Parallel For 的并行加速效果。
三、实验步骤
3.1 环境准备与加载
环境准备的核心是确保各软件组件正确安装、依赖库正常加载,适配鲲鹏 920 服务器的 aarch64 架构,具体步骤如下,补充安装过程中的注意事项及问题排查方法:
3.2 编写并行循环代码
本次实验核心任务是实现 C[] = A[] + B[] 的一维数组相加运算,使用 KUPL Parallel For 静态调度实现并行加速,补充完整代码(含头文件、数组初始化、主函数、性能计时等),代码保存为 kupl_parallel_for_demo.c,具体如下:
数组长度设为 1000000(100 万),模拟高性能计算中的大规模循环场景,同时添加数组初始化逻辑(随机生成数组元素)、性能计时逻辑(记录并行计算耗时),便于后续性能对比,代码细节如下:
#include <stdio.h> #include <stdlib.h> #include <time.h> #include "kupl/kupl.h" // KUPL库头文件 #include <pthread.h> // pthread库头文件 // 定义数组长度,模拟大规模循环计算 #define ELEMENTS_NUM_IN_ARRAY 1000000 // 定义并行线程数,与鲲鹏920服务器核心数一致(24核) #define NUM_THREADS 24 // 全局数组,用于存储计算数据(A、B为输入数组,C为输出数组) int A[ELEMENTS_NUM_IN_ARRAY], B[ELEMENTS_NUM_IN_ARRAY], C[ELEMENTS_NUM_IN_ARRAY]; // 循环计算任务函数,由每个并行线程执行 // 参数说明:nd_range:任务切分后的范围(上下界、步长);args:自定义参数(本次未使用);tid:当前线程ID;tnum:总线程数 void task_int_loop(kupl_nd_range_t *nd_range, void *args, int tid, int tnum) { // 静态分配的任务范围,循环执行当前线程分配到的迭代任务 // nd_range->nd_range[0] 表示一维切分的范围信息,lower为下界,upper为上界,step为步长 for (int i = nd_range->nd_range[0].lower; i < nd_range->nd_range[0].upper; i += nd_range->nd_range[0].step) { C[i] = A[i] + B[i]; // 核心计算:数组元素相加 } } // 数组初始化函数:随机生成A、B数组的元素(范围0~999) void init_arrays() { srand((unsigned int)time(NULL)); // 设置随机数种子,确保每次运行数组元素不同 for (int i = 0; i < ELEMENTS_NUM_IN_ARRAY; i++) { A[i] = rand() % 1000; B[i] = rand() % 1000; } } // 主函数:程序入口,负责环境初始化、并行任务配置与执行、性能计时 int main() { // 1. 初始化输入数组 init_arrays(); // 2. 声明并初始化一维任务切分范围 kupl_nd_range_t range; // KUPL_1D_RANGE_INIT 宏:初始化一维范围,参数依次为(范围对象、下界、上界、步长) KUPL_1D_RANGE_INIT(range, 0, ELEMENTS_NUM_IN_ARRAY, 1); // 3. 创建线程组(执行器组),指定参与并行的核心(0~23号核心,对应24核) int executor[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; i++) { executor[i] = i; // 每个执行器对应一个核心编号 } kupl_egroup_h eg = kupl_egroup_create(executor, NUM_THREADS); // 创建线程组 if (eg == NULL) { // 错误处理:判断线程组创建是否成功 printf("Failed to create kupl egroup!\n"); return -1; } // 4. 配置并行描述符,指定静态调度策略 kupl_parallel_for_desc_t desc = { .range = &range, // 任务切分范围 .concurrency = NUM_THREADS, // 并行线程数(核心数) .egroup = eg, // 线程组(执行器组) .policy = KUPL_LOOP_POLICY_STATIC // 静态调度策略 }; // 5. 并行任务执行与计时 clock_t start_time = clock(); // 记录并行开始时间 // kupl_parallel_for:核心并行接口,参数依次为(并行描述符、任务函数、自定义参数) kupl_parallel_for(&desc, task_int_loop, nullptr); clock_t end_time = clock(); // 记录并行结束时间 // 6. 计算并行执行时间(单位:毫秒) double parallel_time = (double)(end_time - start_time) / CLOCKS_PER_SEC * 1000; printf("Parallel execution time: %.2f ms\n", parallel_time); // 7. 销毁线程组,释放资源(遵循鲲鹏编程资源管理规范) kupl_egroup_destroy(eg); // 8. 验证计算结果(随机抽取10个元素验证) printf("Verify calculation results (10 random elements):\n"); for (int i = 0; i < 10; i++) { int idx = rand() % ELEMENTS_NUM_IN_ARRAY; printf("C[%d] = A[%d] + B[%d] → %d = %d + %d\n", idx, idx, idx, C[idx], A[idx], B[idx]); } return 0; }3.3 毕昇编译器编译优化
为充分发挥鲲鹏 920 架构的性能优势,本次实验使用毕昇编译器 3.1.0 进行编译,开启鲲鹏架构专属优化和 O2 级编译优化,补充编译选项的详细说明,确保编译过程规范、优化到位,具体步骤如下:
3.4 运行与性能对比
为验证 KUPL Parallel For 的并行加速效果,本次实验采用“并行版本 vs 串行版本”的对比方式,补充多次测试取平均值的规范,确保性能数据的准确性,具体步骤如下:
#include <stdio.h> #include <stdlib.h> #include <time.h> #define ELEMENTS_NUM_IN_ARRAY 1000000 int A[ELEMENTS_NUM_IN_ARRAY], B[ELEMENTS_NUM_IN_ARRAY], C[ELEMENTS_NUM_IN_ARRAY]; void init_arrays() { srand((unsigned int)time(NULL)); for (int i = 0; i < ELEMENTS_NUM_IN_ARRAY; i++) { A[i] = rand() % 1000; B[i] = rand() % 1000; } } int main() { init_arrays(); clock_t start_time = clock(); // 串行循环执行数组相加 for (int i = 0; i < ELEMENTS_NUM_IN_ARRAY; i++) { C[i] = A[i] + B[i]; } clock_t end_time = clock(); double serial_time = (double)(end_time - start_time) / CLOCKS_PER_SEC * 1000; printf("Serial execution time: %.2f ms\n", serial_time); printf("Verify calculation results (10 random elements):\n"); for (int i = 0; i < 10; i++) { int idx = rand() % ELEMENTS_NUM_IN_ARRAY; printf("C[%d] = A[%d] + B[%d] → %d = %d + %d\n", idx, idx, idx, C[idx], A[idx], B[idx]); } return 0; }使用相同的编译器和编译选项编译串行代码,命令为: bishengcc -o kupl_serial_demo kupl_serial_demo.c -O2 -mcpu=kunpeng920四、关键代码解析
4.1 循环计算函数定义与解析
循环计算函数 task_int_loop 是本次并行实验的核心执行函数,由每个并行线程独立执行,负责完成分配到的数组相加任务,结合代码细节和 KUPL 编程知识点,解析如下:
void task_int_loop(kupl_nd_range_t *nd_range, void *args, int tid, int tnum) { // 静态分配的任务范围,nd_range为切分后的任务上下界 for (int i = nd_range->nd_range[0].lower; i < nd_range->nd_range[0].upper; i += nd_range->nd_range[0].step) { C[i] = A[i] + B[i]; } }4.2 计算范围与线程组初始化解析
计算范围(nd_range)和线程组(egroup)的初始化是 KUPL Parallel For 并行任务执行的前提,负责定义任务切分规则和指定参与并行的核心,解析如下,补充初始化逻辑的底层原理:
4.3 并行描述符配置与执行解析
并行描述符(kupl_parallel_for_desc_t)用于配置并行任务的核心参数,kupl_parallel_for 是执行并行任务的核心接口,结合代码解析配置逻辑和执行流程,补充 KUPL 并行框架的底层机制:
// 配置并行描述符,指定静态调度 kupl_parallel_for_desc_t desc = { .range = &range, .concurrency = NUM_THREADS, .egroup = eg, .policy = KUPL_LOOP_POLICY_STATIC }; // 执行并行循环 kupl_parallel_for(&desc, task_int_loop, nullptr); // 销毁线程组,释放资源 kupl_egroup_destroy(eg);五、实验小结
本次实验基于鲲鹏 920 服务器和 KUPL 库,成功实现了一维数组相加的算子内并行加速,通过静态调度策略和毕昇编译器优化,验证了 KUPL Parallel For 在鲲鹏架构上的并行性能优势,结合实验过程和知识点,小结如下:
1. KUPL Parallel For 针对鲲鹏架构做了深度的并行优化,不仅兼容类 OpenMP 的编程范式,降低并行开发门槛,还新增多维数据切分能力,弥补了传统并行编程接口的不足,适配更广泛的 HPC 场景,是鲲鹏高性能计算中算子内并行开发的优选方案。
2. 静态调度策略在任务粒度均匀的场景(如本次数组相加)中性能优势显著,通过提前分配任务、固定线程执行,减少了调度开销和线程间通信成本,结合鲲鹏服务器的多核架构,实现了约 20 倍的性能提升,符合并行算力预期。
3. 实验过程中的关键优化点:一是毕昇编译器的 -mcpu=kunpeng920 选项,确保代码适配鲲鹏架构的指令集;二是 KUPL 库的缓存本地化优化,减少内存访问开销;三是线程组与鲲鹏核心数的匹配,确保多核算力充分利用。
4. 拓展方向:本次实验仅实现了一维循环的算子内并行,后续可进一步探索 KUPL Parallel For 的二维、三维数据切分能力,适配矩阵乘法、三维数值模拟等更复杂的场景;同时可结合 KUPL 计算图实现“算子间+算子内”的多层级并行,进一步释放鲲鹏众核的算力潜力;此外,还可通过鲲鹏 HPCKit 的性能分析工具,定位并行程序的性能瓶颈,进行更精细的优化。