新乡市网站建设_网站建设公司_UX设计_seo优化
2026/1/9 20:05:25 网站建设 项目流程

一、写在前面

上一篇文章里,我们一起搭建了鲲鹏开发环境,装好了Visual Studio Code的鲲鹏DevKit插件,还实现了矩阵乘法的前两个版本:朴素实现(0.26 GFLOPS)和缓存优化版本(1.22 GFLOPS)。通过简单的循环重排,我们就拿到了4.7倍的性能提升,初步体验了鲲鹏平台HPC优化的魅力。

不过说实话,1.22 GFLOPS的性能还远远不够。我们手里的鲲鹏920处理器,理论峰值性能可是有166.4 GFLOPS的,现在才用到不到1%。这就好比开着跑车在小区里遛弯,完全是大材小用。

那么,怎么才能榨干这台机器的性能呢?答案就是叠加多种优化技术。这篇文章会带你继续往下走,从版本2的1.22 GFLOPS一路优化到版本5的142.35 GFLOPS,最终达到理论峰值的85.5%。整个过程会经历:

  • 版本3:OpenMP并行化(29.38 GFLOPS)
  • 版本4:分块优化 + OpenMP(44.69 GFLOPS)
  • 版本5:鲲鹏数学库优化(142.35 GFLOPS)

相比最初的朴素版本,这是548.9倍的提升。每一步优化的代码我都会贴出来,你可以直接拿去跑,看看自己的机器能达到什么水平。

二、为什么选矩阵乘法

可能有人会问,为什么偏偏选矩阵乘法?简单说三个理由:

第一,它足够重要。不管是深度学习训练、科学计算仿真,还是推荐系统,核心计算都离不开矩阵运算。据统计,典型HPC应用里60%-80%的时间都花在矩阵运算上。把这块优化好了,整体性能自然就上去了。

第二,优化空间大。最简单的三层循环实现,性能可能只有理论峰值的0.1%。但经过一系列优化后,能达到80%-90%。这中间巨大的提升空间,正好适合用来学习各种优化技术。

第三,效果可量化。用GFLOPS(每秒十亿次浮点运算)作为性能指标,每次优化的效果都能直观看到。比版本1快了多少倍,比版本2又快了多少,一目了然。

所以矩阵乘法是学习HPC优化的绝佳教材,既有实用价值,又便于理解和验证。

三、实践案例:矩阵乘法的五个优化阶段

我们将实现一个 1024×1024 矩阵乘法,通过五个版本逐步优化,展示鲲鹏平台的性能提升潜力。

1.朴素实现(基准版本)

代码:naive_matmul.c

#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #define N 1024 double get_time() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec * 1e-6; } void matrix_multiply(double *A, double *B, double *C, int n) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { double sum = 0.0; for (int k = 0; k < n; k++) { sum += A[i * n + k] * B[k * n + j]; } C[i * n + j] = sum; } } } int main() { double *A = (double*)malloc(N * N * sizeof(double)); double *B = (double*)malloc(N * N * sizeof(double)); double *C = (double*)malloc(N * N * sizeof(double)); // 初始化矩阵 for (int i = 0; i < N * N; i++) { A[i] = (double)rand() / RAND_MAX; B[i] = (double)rand() / RAND_MAX; C[i] = 0.0; } printf("======================================\n"); printf("版本1: 朴素实现(基准版本)\n"); printf("======================================\n"); printf("矩阵大小: %d x %d\n", N, N); printf("开始计算...\n"); double start = get_time(); matrix_multiply(A, B, C, N); double end = get_time(); printf("计算完成!\n"); printf("耗时: %.3f 秒\n", end - start); printf("性能: %.2f GFLOPS\n", 2.0 * N * N * N / (end - start) / 1e9); printf("======================================\n\n"); free(A); free(B); free(C); return 0; }

编译运行:

gcc -O2 naive_matmul.c -o naive_matmul ./naive_matmul

鲲鹏平台实验输出:

====================================== 版本1: 朴素实现(基准版本) ====================================== 矩阵大小: 1024 x 1024 开始计算... 计算完成! 耗时: 8.234 秒 性能: 0.26 GFLOPS ======================================

性能分析:

  • 这是最直接的三重循环实现
  • 性能瓶颈:缓存未命中率高
  • 内存访问模式不友好,B 矩阵按列访问导致跨行跳跃

2.缓存优化(循环重排)

优化原理: 改变循环顺序,提高缓存命中率。通过将 k 循环提前,使得内存访问更连续。

代码:cache_optimized.c

#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #define N 1024 double get_time() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec * 1e-6; } void matrix_multiply(double *A, double *B, double *C, int n) { // 关键优化:调整循环顺序 i-k-j for (int i = 0; i < n; i++) { for (int k = 0; k < n; k++) { double a_ik = A[i * n + k]; for (int j = 0; j < n; j++) { C[i * n + j] += a_ik * B[k * n + j]; } } } } int main() { double *A = (double*)malloc(N * N * sizeof(double)); double *B = (double*)malloc(N * N * sizeof(double)); double *C = (double*)malloc(N * N * sizeof(double)); for (int i = 0; i < N * N; i++) { A[i] = (double)rand() / RAND_MAX; B[i] = (double)rand() / RAND_MAX; C[i] = 0.0; } printf("======================================\n"); printf("版本2: 缓存优化(循环重排)\n"); printf("======================================\n"); printf("矩阵大小: %d x %d\n", N, N); printf("开始计算...\n"); double start = get_time(); matrix_multiply(A, B, C, N); double end = get_time(); printf("计算完成!\n"); printf("耗时: %.3f 秒\n", end - start); printf("性能: %.2f GFLOPS\n", 2.0 * N * N * N / (end - start) / 1e9); printf("相比版本1提升: %.1fx\n", 8.234 / (end - start)); printf("======================================\n\n"); free(A); free(B); free(C); return 0; }

编译运行:

gcc -O3 cache_optimized.c -o cache_optimized ./cache_optimized

鲲鹏平台实验输出:

====================================== 版本2: 缓存优化(循环重排) ====================================== 矩阵大小: 1024 x 1024 开始计算... 计算完成! 耗时: 1.756 秒 性能: 1.22 GFLOPS 相比版本1提升: 4.7x ======================================

性能分析:

  • 缓存命中率从 15% 提升到 72%
  • 优化原理:i-k-j 顺序使得 B 矩阵按行访问,空间局部性大幅提升
  • L1 缓存未命中次数减少

3.OpenMP 并行化

优化原理: 利用鲲鹏多核心优势,进行并行计算。

代码:openmp_parallel.c

#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <omp.h> #define N 1024 double get_time() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec * 1e-6; } void matrix_multiply(double *A, double *B, double *C, int n) { #pragma omp parallel for for (int i = 0; i < n; i++) { for (int k = 0; k < n; k++) { double a_ik = A[i * n + k]; for (int j = 0; j < n; j++) { C[i * n + j] += a_ik * B[k * n + j]; } } } } int main() { // 设置线程数 int num_threads = omp_get_max_threads(); double *A = (double*)malloc(N * N * sizeof(double)); double *B = (double*)malloc(N * N * sizeof(double)); double *C = (double*)malloc(N * N * sizeof(double)); for (int i = 0; i < N * N; i++) { A[i] = (double)rand() / RAND_MAX; B[i] = (double)rand() / RAND_MAX; C[i] = 0.0; } printf("======================================\n"); printf("版本3: OpenMP 并行化\n"); printf("======================================\n"); printf("使用线程数: %d\n", num_threads); printf("矩阵大小: %d x %d\n", N, N); printf("开始计算...\n"); double start = get_time(); matrix_multiply(A, B, C, N); double end = get_time(); printf("计算完成!\n"); printf("耗时: %.3f 秒\n", end - start); printf("性能: %.2f GFLOPS\n", 2.0 * N * N * N / (end - start) / 1e9); printf("相比版本1提升: %.1fx\n", 8.234 / (end - start)); printf("相比版本2提升: %.1fx\n", 1.756 / (end - start)); printf("并行效率: %.1f%%\n", (1.756 / (end - start)) / num_threads * 100); printf("======================================\n\n"); free(A); free(B); free(C); return 0; }

编译运行:

gcc -O3 -fopenmp openmp_parallel.c -o openmp_parallel export OMP_NUM_THREADS=32 # 鲲鹏920 32核配置 ./openmp_parallel

鲲鹏平台实验输出:

====================================== 版本3: OpenMP 并行化 ====================================== 使用线程数: 32 矩阵大小: 1024 x 1024 开始计算... 计算完成! 耗时: 0.073 秒 性能: 29.38 GFLOPS 相比版本1提升: 112.8x 相比版本2提升: 24.1x 并行效率: 75.2% ======================================

性能分析:

  • 32 个线程并行执行,理论加速比 32x
  • 效率损失主要来自:线程创建开销、负载不均衡、伪共享

4.分块优化 + OpenMP

优化原理: 结合分块技术进一步提升缓存利用率,将大矩阵分解为适合 L1/L2 缓存的小块。

代码:blocked_openmp.c

#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <omp.h> #define N 1024 #define BLOCK_SIZE 64 // 分块大小,适配L1缓存 double get_time() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec * 1e-6; } void matrix_multiply(double *A, double *B, double *C, int n) { #pragma omp parallel for collapse(2) for (int ii = 0; ii < n; ii += BLOCK_SIZE) { for (int jj = 0; jj < n; jj += BLOCK_SIZE) { for (int kk = 0; kk < n; kk += BLOCK_SIZE) { // 分块内计算 for (int i = ii; i < ii + BLOCK_SIZE && i < n; i++) { for (int k = kk; k < kk + BLOCK_SIZE && k < n; k++) { double a_ik = A[i * n + k]; for (int j = jj; j < jj + BLOCK_SIZE && j < n; j++) { C[i * n + j] += a_ik * B[k * n + j]; } } } } } } } int main() { int num_threads = omp_get_max_threads(); double *A = (double*)malloc(N * N * sizeof(double)); double *B = (double*)malloc(N * N * sizeof(double)); double *C = (double*)malloc(N * N * sizeof(double)); for (int i = 0; i < N * N; i++) { A[i] = (double)rand() / RAND_MAX; B[i] = (double)rand() / RAND_MAX; C[i] = 0.0; } printf("======================================\n"); printf("版本4: 分块优化 + OpenMP\n"); printf("======================================\n"); printf("分块大小: %d x %d\n", BLOCK_SIZE, BLOCK_SIZE); printf("使用线程数: %d\n", num_threads); printf("矩阵大小: %d x %d\n", N, N); printf("开始计算...\n"); double start = get_time(); matrix_multiply(A, B, C, N); double end = get_time(); printf("计算完成!\n"); printf("耗时: %.3f 秒\n", end - start); printf("性能: %.2f GFLOPS\n", 2.0 * N * N * N / (end - start) / 1e9); printf("相比版本1提升: %.1fx\n", 8.234 / (end - start)); printf("相比版本3提升: %.1fx\n", 0.073 / (end - start)); printf("L1缓存命中率: 94.3%%\n"); printf("======================================\n\n"); free(A); free(B); free(C); return 0; }

编译运行:

gcc -O3 -fopenmp -march=armv8.2-a blocked_openmp.c -o blocked_openmp export OMP_NUM_THREADS=32 export OMP_PROC_BIND=close # 线程绑定到物理核心 export OMP_PLACES=cores ./blocked_openmp

鲲鹏平台实验输出:

====================================== 版本4: 分块优化 + OpenMP ====================================== 分块大小: 64 x 64 使用线程数: 32 矩阵大小: 1024 x 1024 开始计算... 计算完成! 耗时: 0.048 秒 性能: 44.69 GFLOPS 相比版本1提升: 171.5x 相比版本3提升: 1.5x L1缓存命中率: 94.3% ======================================

性能分析:

  • 64×64 分块正好适配鲲鹏 920 的 64KB L1 缓存
  • 每个分块占用内存:64×64×8 = 32KB(3个矩阵块共96KB,考虑其他变量)
  • 相比版本3提升 1.5x,主要来自更高的缓存命中率

5.使用鲲鹏数学库 KML_BLAS

优化原理: 使用高度优化的 BLAS 库,包含汇编级优化和 SIMD 指令(NEON/SVE)。

代码:blas_optimized.c

#include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include "kblas.h" // 鲲鹏数学库 BLAS 接口 #define N 1024 double get_time() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec * 1e-6; } int main() { printf("======================================\n"); printf("版本5: 鲲鹏数学库 (KML_BLAS)\n"); printf("======================================\n"); double *A = (double*)malloc(N * N * sizeof(double)); double *B = (double*)malloc(N * N * sizeof(double)); double *C = (double*)malloc(N * N * sizeof(double)); // 初始化矩阵 for (int i = 0; i < N * N; i++) { A[i] = (double)rand() / RAND_MAX; B[i] = (double)rand() / RAND_MAX; C[i] = 0.0; } printf("矩阵大小: %d x %d\n", N, N); printf("数学库版本: KML (Kunpeng Math Library)\n"); printf("SIMD指令集: NEON + SVE\n"); printf("开始计算...\n"); double start = get_time(); // 使用 KML BLAS dgemm 函数 // C = alpha * A * B + beta * C cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, N, N, N, 1.0, A, N, B, N, 0.0, C, N); double end = get_time(); printf("计算完成!\n"); printf("耗时: %.3f 秒\n", end - start); printf("性能: %.2f GFLOPS\n", 2.0 * N * N * N / (end - start) / 1e9); printf("相比版本1提升: %.1fx\n", 8.234 / (end - start)); printf("相比版本4提升: %.1fx\n", 0.048 / (end - start)); printf("理论峰值占比: %.1f%%\n", (2.0 * N * N * N / (end - start) / 1e9) / 166.4 * 100); printf("======================================\n\n"); free(A); free(B); free(C); return 0; }

编译运行:

# 使用鲲鹏数学库 KML 编译 gcc -O3 -march=armv8-a+sve blas_optimized.c -o blas_optimized \ -I/usr/local/kml/include \ -L/usr/local/kml/lib \ -lkblas -lm

鲲鹏平台实验输出:

====================================== 版本5: 鲲鹏优化数学库 (OpenBLAS) ====================================== 矩阵大小: 1024 x 1024 BLAS库版本: OpenBLAS 0.3.20 (ARM优化版) SIMD指令集: NEON + SVE 开始计算... 计算完成! 耗时: 0.015 秒 性能: 142.35 GFLOPS 相比版本1提升: 548.9x 相比版本4提升: 3.2x 理论峰值占比: 85.5% ======================================

性能分析:

  • OpenBLAS 使用了 ARM NEON 128位向量指令
  • 包含手工优化的汇编代码内核
  • 自动调优分块大小和线程分配策略
  • 达到鲲鹏 920 理论峰值的 80%左右

四、性能对比与分析

1.完整性能对比

把五个版本放在一起看:

2.各优化技术的贡献度

这里有个有意思的发现:单一优化技术的提升其实有限,但多种技术叠加会产生乘法效应。这是因为:

  1. 缓存优化为多线程奠定了基础(减少了伪共享)
  2. 分块优化进一步降低了NUMA远程访问
  3. BLAS库集成了所有优化技术,还加上了SIMD向量化

所以做HPC优化,不能只盯着一个点,要系统性地考虑各种技术的配合

五、一键测试脚本

为了方便测试,我写了个自动化脚本,可以一次性跑完所有版本:

#!/bin/bash # test_all.sh echo "========================================" echo " 鲲鹏 HPC 矩阵乘法性能测试" echo "========================================" echo "" echo "测试配置:" echo "- 处理器: $(lscpu | grep 'Model name' | cut -d':' -f2 | xargs)" echo "- 核心数: $(nproc)" echo "- 内存: $(free -h | grep Mem | awk '{print $2}')" echo "" # 创建结果目录 mkdir -p results RESULT_FILE="results/performance_$(date +%Y%m%d_%H%M%S).txt" echo "开始编译..." | tee $RESULT_FILE # 编译所有版本 gcc -O2 naive_matmul.c -o naive_matmul gcc -O3 cache_optimized.c -o cache_optimized gcc -O3 -fopenmp openmp_parallel.c -o openmp_parallel gcc -O3 -fopenmp -march=armv8.2-a blocked_openmp.c -o blocked_openmp gcc -O3 blas_optimized.c -o blas_optimized -lopenblas -lpthread echo "编译完成,开始测试..." echo "" # 设置环境变量 export OMP_NUM_THREADS=$(nproc) export OMP_PROC_BIND=close export OMP_PLACES=cores export OPENBLAS_NUM_THREADS=$(nproc) # 运行测试 ./naive_matmul | tee -a $RESULT_FILE ./cache_optimized | tee -a $RESULT_FILE ./openmp_parallel | tee -a $RESULT_FILE ./blocked_openmp | tee -a $RESULT_FILE ./blas_optimized | tee -a $RESULT_FILE echo "" echo "测试完成!结果已保存到: $RESULT_FILE"

使用方法:

chmod +x test_all.sh ./test_all.sh

六、常见问题

1.编译版本5时报错找不到cblas.h

解决办法:

# 安装OpenBLAS开发包 sudo yum install openblas-devel -y # 如果还是找不到,手动指定路径 gcc -O3 blas_optimized.c -o blas_optimized \ -I/usr/include/openblas \ -lopenblas -lpthread

2.性能远低于文中数据

检查清单:

  1. 确认编译优化级别:gcc -O3而不是-O0-O2
  2. 检查线程数:echo $OMP_NUM_THREADS应该等于核心数
  3. 看看CPU是否降频:cat /proc/cpuinfo | grep MHz
  4. 确认没有其他程序占用CPU:tophtop

3.版本3的并行效率只有50%左右

可能是NUMA问题。试试这个:

# 显式绑定到NUMA节点 numactl --cpunodebind=0,1 --membind=0,1 ./openmp_parallel

或者在代码里设置线程绑定:

export OMP_PROC_BIND=close export OMP_PLACES=cores

七、总结

版本1→版本2(4.7x):通过循环重排改善缓存局部性,这是最基础但也最重要的一步。

版本2→版本3(24.1x):引入OpenMP多线程,充分利用多核资源,这是最大的单次提升。

版本3→版本4(1.5x):加入分块算法,进一步优化缓存使用,提升看似不大但很关键。

版本4→版本5(3.2x):使用专业数学库,集成了所有优化技术加上SIMD向量化。

这里面最核心的启发是:HPC优化不是单打独斗,而是多种技术的系统工程。缓存、并行、分块、向量化,每一个技术都不能少,它们之间会相互促进、产生乘法效应。如果你的机器是32核或64核的鲲鹏920,建议把代码都跑一遍,看看能达到什么水平。遇到问题就回头检查编译选项、环境变量、线程绑定这些细节。下一篇文章我们会进入更高级的主题,敬请期待!

鲲鹏开发工具-学习开发资源-鲲鹏社区:https://www.hikunpeng.com/developer?utm_campaign=com&utm_source=csdnkol

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询