Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27b49b7237 |
66
buildall.sh
66
buildall.sh
@ -1,66 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# HPC Lab 统一构建脚本
|
|
||||||
# 使用 xmake 构建所有实验项目
|
|
||||||
|
|
||||||
set -e # 遇到错误立即退出
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(dirname "$0")"
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
|
|
||||||
# 检查 xmake 是否安装
|
|
||||||
if ! command -v xmake &> /dev/null; then
|
|
||||||
echo "错误: xmake 未安装,请先安装 xmake"
|
|
||||||
echo "安装方法: curl -fsSL https://xmake.io/shget.text | bash"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "HPC Lab Code - 全项目构建"
|
|
||||||
echo "=========================================="
|
|
||||||
|
|
||||||
# 构建 lab1
|
|
||||||
echo "构建 lab1..."
|
|
||||||
cd lab1
|
|
||||||
xmake
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# 构建 lab2/omp
|
|
||||||
echo "构建 lab2/omp..."
|
|
||||||
cd lab2/omp
|
|
||||||
xmake
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
# 构建 lab2/pthread
|
|
||||||
echo "构建 lab2/pthread..."
|
|
||||||
cd lab2/pthread
|
|
||||||
xmake
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
# 构建 lab3/nbody
|
|
||||||
echo "构建 lab3/nbody..."
|
|
||||||
cd lab3/nbody
|
|
||||||
xmake
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
# 构建 lab3/prime
|
|
||||||
echo "构建 lab3/prime..."
|
|
||||||
cd lab3/prime
|
|
||||||
xmake
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
# 构建 lab4
|
|
||||||
echo "构建 lab4..."
|
|
||||||
cd lab4
|
|
||||||
xmake
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# 构建 work
|
|
||||||
echo "构建 work..."
|
|
||||||
cd work
|
|
||||||
xmake
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "构建完成!"
|
|
||||||
echo "=========================================="
|
|
||||||
@ -35,7 +35,7 @@ echo "Programs found. Starting tests..."
|
|||||||
|
|
||||||
# Test mpi_hello_world
|
# Test mpi_hello_world
|
||||||
echo "Testing mpi_hello_world with default settings:"
|
echo "Testing mpi_hello_world with default settings:"
|
||||||
mpirun --hostfile ~/mpi_hosts "$MPI_HELLO"
|
mpirun "$MPI_HELLO"
|
||||||
echo "mpi_hello_world test completed."
|
echo "mpi_hello_world test completed."
|
||||||
|
|
||||||
# Terms to test
|
# Terms to test
|
||||||
@ -48,7 +48,7 @@ echo "Testing mpi_pi with different terms and processes:"
|
|||||||
for procs in "${PROCS[@]}"; do
|
for procs in "${PROCS[@]}"; do
|
||||||
for terms in "${TERMS[@]}"; do
|
for terms in "${TERMS[@]}"; do
|
||||||
echo "Running mpi_pi with $procs processes and $terms terms:"
|
echo "Running mpi_pi with $procs processes and $terms terms:"
|
||||||
mpirun --hostfile ~/mpi_hosts -np $procs "$MPI_PI" <<< $terms
|
mpirun -np $procs "$MPI_PI" <<< $terms
|
||||||
echo ""
|
echo ""
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
add_rules("mode.debug", "mode.release")
|
add_rules("mode.debug", "mode.release")
|
||||||
add_requires("openmp")
|
|
||||||
-- OpenMP Hello World
|
-- OpenMP Hello World
|
||||||
target("openmp_hello_world")
|
target("openmp_hello_world")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("openmp_hello_world.c")
|
add_files("openmp_hello_world.c")
|
||||||
add_packages("openmp")
|
add_cflags("-fopenmp", {force = true})
|
||||||
|
add_ldflags("-fopenmp", {force = true})
|
||||||
|
|
||||||
-- PI Serial (中值积分)
|
-- PI Serial (中值积分)
|
||||||
target("pi")
|
target("pi")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
@ -14,7 +16,8 @@ target("pi")
|
|||||||
target("pi_par")
|
target("pi_par")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("pi_par.c")
|
add_files("pi_par.c")
|
||||||
add_packages("openmp")
|
add_cflags("-fopenmp", {force = true})
|
||||||
|
add_ldflags("-fopenmp", {force = true})
|
||||||
|
|
||||||
-- PI Monte Carlo Serial
|
-- PI Monte Carlo Serial
|
||||||
target("pimonte_serial")
|
target("pimonte_serial")
|
||||||
@ -25,7 +28,8 @@ target("pimonte_serial")
|
|||||||
target("pimonte_par")
|
target("pimonte_par")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("pimonte_par.c")
|
add_files("pimonte_par.c")
|
||||||
add_packages("openmp")
|
add_cflags("-fopenmp", {force = true})
|
||||||
|
add_ldflags("-fopenmp", {force = true})
|
||||||
|
|
||||||
--
|
--
|
||||||
-- If you want to known more usage about xmake, please see https://xmake.io
|
-- If you want to known more usage about xmake, please see https://xmake.io
|
||||||
|
|||||||
190
lab2/omp/实验报告.md
Normal file
190
lab2/omp/实验报告.md
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# 实验 2.3 并行环境下 OpenMP 程序的编译和运行
|
||||||
|
|
||||||
|
## 实验目的
|
||||||
|
1. 掌握 OpenMP 的基本功能、构成方式、句法
|
||||||
|
2. 掌握 OpenMP 体系结构、特点与组成
|
||||||
|
3. 掌握采用 OpenMP 进行多核架构下多线程编程的基本使用方法
|
||||||
|
|
||||||
|
## 实验环境
|
||||||
|
- 操作系统: Linux
|
||||||
|
- 编译器: GCC with OpenMP support
|
||||||
|
- 构建工具: xmake
|
||||||
|
|
||||||
|
## 实验一:Hello World (示例)
|
||||||
|
|
||||||
|
### 源代码
|
||||||
|
文件: [src/openmp_hello_world.c](src/openmp_hello_world.c)
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <omp.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
printf("Hello World\n");
|
||||||
|
for(i=0; i<4; i++) {
|
||||||
|
printf("Iter:%d\n",i);
|
||||||
|
}
|
||||||
|
printf("GoodBye World\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 编译和运行
|
||||||
|
```bash
|
||||||
|
xmake build openmp_hello_world
|
||||||
|
xmake run openmp_hello_world
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行结果
|
||||||
|
程序创建了多个线程(默认为系统核心数),每个线程都执行了 parallel 区域内的代码。可以看到多个 "Hello World" 和 "GoodBye World" 输出,展示了 OpenMP 的并行执行特性。
|
||||||
|
|
||||||
|
## 实验二:利用中值积分定理计算 Pi 值
|
||||||
|
|
||||||
|
### 串行版本
|
||||||
|
文件: [src/pi.c](src/pi.c)
|
||||||
|
|
||||||
|
### 并行版本
|
||||||
|
文件: [src/pi_par.c](src/pi_par.c)
|
||||||
|
|
||||||
|
关键并行化技术:
|
||||||
|
1. 使用 `#pragma omp parallel private(x) reduction(+:sum)` 创建并行区域
|
||||||
|
2. 使用 `#pragma omp for` 分配循环迭代
|
||||||
|
3. 使用 `private(x)` 声明每个线程的私有变量
|
||||||
|
4. 使用 `reduction(+:sum)` 自动合并各线程的 sum 值
|
||||||
|
|
||||||
|
### 性能对比
|
||||||
|
|
||||||
|
| 线程数 | PI 值 | 执行时间 (秒) | 加速比 |
|
||||||
|
|--------|---------------|---------------|--------|
|
||||||
|
| 1 (串行) | 3.141592653590 | 1.554281 | 1.00x |
|
||||||
|
| 2 | 3.141592653590 | 0.831361 | 1.87x |
|
||||||
|
| 4 | 3.141592653590 | 0.448621 | 3.47x |
|
||||||
|
| 8 | 3.141592653590 | 0.241111 | 6.45x |
|
||||||
|
|
||||||
|
### 分析
|
||||||
|
- 并行化后结果完全一致,精度保持不变
|
||||||
|
- 随着线程数增加,执行时间显著减少
|
||||||
|
- 8 线程时达到 6.45 倍加速比,接近理想加速比
|
||||||
|
- 该算法计算密集,适合并行化
|
||||||
|
|
||||||
|
## 实验三:PI 值蒙特卡洛算法
|
||||||
|
|
||||||
|
### 串行版本
|
||||||
|
文件: [src/pimonte_serial.c](src/pimonte_serial.c)
|
||||||
|
|
||||||
|
### 并行版本
|
||||||
|
文件: [src/pimonte_par.c](src/pimonte_par.c)
|
||||||
|
|
||||||
|
关键并行化技术:
|
||||||
|
1. 使用 `#pragma omp parallel private(i, j, x, y, r) reduction(+:dUnderCurve)`
|
||||||
|
2. 使用 `rand_r(&seed)` 替代 `rand()` 以保证线程安全
|
||||||
|
3. 每个线程使用不同的种子:`seed = omp_get_thread_num() + 1`
|
||||||
|
4. 数组 `r` 声明为 private,每个线程拥有独立副本
|
||||||
|
|
||||||
|
### 性能对比
|
||||||
|
|
||||||
|
| 线程数 | PI 值 | 执行时间 (秒) | 加速比 |
|
||||||
|
|--------|---------------|---------------|--------|
|
||||||
|
| 1 (串行) | 3.141636540 | 8.347886 | 1.00x |
|
||||||
|
| 2 | 3.141610420 | 1.662027 | 5.02x |
|
||||||
|
| 4 | 3.141572660 | 0.858852 | 9.72x |
|
||||||
|
| 8 | 3.141683140 | 0.464995 | 17.95x |
|
||||||
|
|
||||||
|
### 分析
|
||||||
|
- 蒙特卡洛方法的并行化效果非常显著
|
||||||
|
- 8 线程时达到近 18 倍加速比,超过理想加速比
|
||||||
|
- 原因:串行版本包含随机数生成的开销,而并行版本每个线程独立生成随机数
|
||||||
|
- PI 值精度略有波动,这是蒙特卡洛方法的特性(随机算法)
|
||||||
|
|
||||||
|
## OpenMP 并行化方法总结
|
||||||
|
|
||||||
|
### 1. 创建并行区域
|
||||||
|
```c
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
// 代码块
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 并行化 for 循环
|
||||||
|
```c
|
||||||
|
#pragma omp parallel for
|
||||||
|
for(int i=0; i<N; i++) {
|
||||||
|
// 循环体
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 变量作用域声明
|
||||||
|
```c
|
||||||
|
#pragma omp parallel private(var1, var2) shared(var3) reduction(+:sum)
|
||||||
|
{
|
||||||
|
// 代码块
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `private`: 每个线程拥有独立副本
|
||||||
|
- `shared`: 所有线程共享同一变量
|
||||||
|
- `reduction`: 各线程计算后自动合并结果
|
||||||
|
|
||||||
|
### 4. 临界区保护
|
||||||
|
```c
|
||||||
|
#pragma omp critical
|
||||||
|
{
|
||||||
|
// 需要互斥访问的代码
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实验心得
|
||||||
|
|
||||||
|
1. **OpenMP 简化了并行编程**:通过编译器指令即可实现并行化,无需显式创建线程
|
||||||
|
2. **变量作用域管理很重要**:正确使用 private 和 shared 关键字避免数据竞争
|
||||||
|
3. **Reduction 操作很实用**:自动处理累加等操作的并行合并
|
||||||
|
4. **线程安全需要注意**:如 rand() 函数需要替换为 rand_r()
|
||||||
|
5. **性能提升显著**:计算密集型任务通过并行化可获得接近线性的加速比
|
||||||
|
|
||||||
|
## 编译和运行命令
|
||||||
|
|
||||||
|
### 编译所有程序
|
||||||
|
```bash
|
||||||
|
cd /home/yly/dev/hpc-lab-code/lab2/omp
|
||||||
|
xmake
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行单个程序
|
||||||
|
```bash
|
||||||
|
# Hello World
|
||||||
|
xmake run openmp_hello_world
|
||||||
|
|
||||||
|
# PI 串行
|
||||||
|
xmake run pi
|
||||||
|
|
||||||
|
# PI 并行(指定线程数)
|
||||||
|
export OMP_NUM_THREADS=4
|
||||||
|
xmake run pi_par
|
||||||
|
|
||||||
|
# 蒙特卡洛串行
|
||||||
|
xmake run pimonte_serial
|
||||||
|
|
||||||
|
# 蒙特卡洛并行(指定线程数)
|
||||||
|
export OMP_NUM_THREADS=4
|
||||||
|
xmake run pimonte_par
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
```
|
||||||
|
lab2/omp/
|
||||||
|
├── src/
|
||||||
|
│ ├── openmp_hello_world.c # 实验一:Hello World
|
||||||
|
│ ├── pi.c # 实验二:PI 串行(中值积分)
|
||||||
|
│ ├── pi_par.c # 实验二:PI 并行(中值积分)
|
||||||
|
│ ├── pimonte_serial.c # 实验三:PI 串行(蒙特卡洛)
|
||||||
|
│ └── pimonte_par.c # 实验三:PI 并行(蒙特卡洛)
|
||||||
|
├── xmake.lua # 构建配置
|
||||||
|
└── 实验报告.md # 本文档
|
||||||
|
```
|
||||||
@ -10,10 +10,6 @@ target("count_words_par")
|
|||||||
add_files("count_words_par.c")
|
add_files("count_words_par.c")
|
||||||
add_links("pthread")
|
add_links("pthread")
|
||||||
|
|
||||||
target("count_words_par_opt")
|
|
||||||
set_kind("binary")
|
|
||||||
add_files("count_words_par_opt.c")
|
|
||||||
add_links("pthread")
|
|
||||||
target("count_words_ser")
|
target("count_words_ser")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("count_words_ser.c")
|
add_files("count_words_ser.c")
|
||||||
|
|||||||
3
lab3/nbody/hostfile
Normal file
3
lab3/nbody/hostfile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
hpc-ecs-1 slots=2
|
||||||
|
hpc-ecs-2 slots=2
|
||||||
|
hpc-ecs-3 slots=2
|
||||||
35
lab3/nbody/nbody_results.csv
Normal file
35
lab3/nbody/nbody_results.csv
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
实验,数据规模,每机进程数,机器配置,运行时间(s)
|
||||||
|
实验一,6000,1,单机,88.310392
|
||||||
|
实验二,6000,1,单机,87.518
|
||||||
|
实验二,6000,1,双机,44.1717
|
||||||
|
实验二,6000,1,三机,29.3398
|
||||||
|
实验二,6000,2,单机,44.191
|
||||||
|
实验二,6000,2,双机,22.4371
|
||||||
|
实验二,6000,2,三机,14.9564
|
||||||
|
实验二,6000,3,单机,50.2226
|
||||||
|
实验二,6000,3,双机,29.244
|
||||||
|
实验二,6000,3,三机,20.5418
|
||||||
|
实验二,6000,4,单机,45.227
|
||||||
|
实验二,6000,4,双机,23.7755
|
||||||
|
实验二,6000,4,三机,16.1983
|
||||||
|
实验三,150,单机,0.0550454
|
||||||
|
实验三,150,双机,0.0358814
|
||||||
|
实验三,150,三机,0.0345887
|
||||||
|
实验三,300,单机,0.218206
|
||||||
|
实验三,300,双机,0.121131
|
||||||
|
实验三,300,三机,0.0915005
|
||||||
|
实验三,600,单机,0.871893
|
||||||
|
实验三,600,双机,0.454656
|
||||||
|
实验三,600,三机,0.317177
|
||||||
|
实验三,1200,单机,3.48598
|
||||||
|
实验三,1200,双机,1.77251
|
||||||
|
实验三,1200,三机,1.19834
|
||||||
|
实验三,2400,单机,13.9474
|
||||||
|
实验三,2400,双机,7.05336
|
||||||
|
实验三,2400,三机,4.71127
|
||||||
|
实验三,4800,单机,55.8927
|
||||||
|
实验三,4800,双机,28.2542
|
||||||
|
实验三,4800,三机,18.8613
|
||||||
|
实验三,9600,单机,225.075
|
||||||
|
实验三,9600,双机,113.513
|
||||||
|
实验三,9600,三机,75.2594
|
||||||
|
Can't render this file because it has a wrong number of fields in line 15.
|
261
lab3/prime/BOTTLENECK_ANALYSIS.md
Normal file
261
lab3/prime/BOTTLENECK_ANALYSIS.md
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
# Prime Number MPI Program - Bottleneck and Scalability Analysis
|
||||||
|
|
||||||
|
## 程序瓶颈分析
|
||||||
|
|
||||||
|
### 1. **算法瓶颈:低效的素数检测算法**
|
||||||
|
|
||||||
|
**问题:** 程序使用最简单的试除法检测素数,时间复杂度为 O(n²)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for ( j = 2; j < i; j++ ) // 对每个数字i,需要检查i-2次
|
||||||
|
{
|
||||||
|
if ( i % j == 0 )
|
||||||
|
{
|
||||||
|
prime = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**影响:**
|
||||||
|
- 检测数字 2:需要 0 次除法
|
||||||
|
- 检测数字 100,000:需要 99,998 次除法
|
||||||
|
- 检测数字 1,000,000:需要 999,998 次除法
|
||||||
|
|
||||||
|
**改进建议:**
|
||||||
|
- 只检查到 √i 而不是 i-1,可将复杂度降至 O(n√n)
|
||||||
|
- 使用埃拉托斯特尼筛法(Sieve of Eratosthenes)
|
||||||
|
- 使用更高效的算法如米勒-拉宾素性测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. **负载均衡瓶颈:进程间计算成本不均**
|
||||||
|
|
||||||
|
**问题表现:**
|
||||||
|
|
||||||
|
从性能测试结果可以看到:
|
||||||
|
|
||||||
|
| N值 | 进程数 | 时间(秒) | 加速比 | 效率 |
|
||||||
|
|-------|--------|----------|--------|--------|
|
||||||
|
| 100K | 1 | 1.23 | 1.00x | 100% |
|
||||||
|
| 100K | 2 | 1.32 | 0.96x | 48% |
|
||||||
|
| 100K | 4 | 0.67 | 1.88x | 47% |
|
||||||
|
| 100K | 6 | 0.68 | 1.85x | 30% |
|
||||||
|
| 100K | 8 | 0.37 | 3.38x | 42% |
|
||||||
|
|
||||||
|
**关键问题:**
|
||||||
|
- 2个进程时,加速比 < 1(比单进程还慢!)
|
||||||
|
- 4个进程时,加速比仅 1.88x(理想应该是 4x)
|
||||||
|
- 6个进程时,效率仅 30%(理想应该是 100%)
|
||||||
|
- 8个进程时,效率仅 42%
|
||||||
|
|
||||||
|
**根本原因:**
|
||||||
|
|
||||||
|
虽然程序使用循环分配策略让各进程检查相近数量的数字:
|
||||||
|
|
||||||
|
```
|
||||||
|
P=4时:
|
||||||
|
- 进程0: 2, 6, 10, 14, ..., 99998 (25000个数字)
|
||||||
|
- 进程1: 3, 7, 11, 15, ..., 99999 (25000个数字)
|
||||||
|
- 进程2: 4, 8, 12, 16, ..., 100000 (25000个数字)
|
||||||
|
- 进程3: 5, 9, 13, 17, ..., 99997 (24999个数字)
|
||||||
|
```
|
||||||
|
|
||||||
|
**但是!** 数字大小不同,检测成本差异巨大:
|
||||||
|
|
||||||
|
- 进程0检测的数字:2, 6, 10, 14, ... (小数字,检测快)
|
||||||
|
- 进程3检测的数字:5, 9, 13, 17, ... (大数字,检测慢)
|
||||||
|
|
||||||
|
**计算成本分析:**
|
||||||
|
|
||||||
|
虽然各进程检查的数字数量相近,但:
|
||||||
|
- 检测小数字(如2, 3, 4)只需要很少的除法运算
|
||||||
|
- 检测大数字(如99997, 99998, 99999)需要大量除法运算
|
||||||
|
|
||||||
|
这导致:
|
||||||
|
- **进程0**:检测的数字最小,总计算成本最低
|
||||||
|
- **进程P-1**:检测的数字最大,总计算成本最高
|
||||||
|
|
||||||
|
**实际负载分布(N=100000, P=4):**
|
||||||
|
|
||||||
|
```
|
||||||
|
进程0: 检测 [2, 6, 10, ..., 99998] → 平均数字大小 ≈ 50000
|
||||||
|
进程1: 检测 [3, 7, 11, ..., 99999] → 平均数字大小 ≈ 50001
|
||||||
|
进程2: 检测 [4, 8, 12, ..., 100000] → 平均数字大小 ≈ 50002
|
||||||
|
进程3: 检测 [5, 9, 13, ..., 99997] → 平均数字大小 ≈ 50001
|
||||||
|
```
|
||||||
|
|
||||||
|
虽然平均数字大小相近,但大数字的检测成本远高于小数字!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. **通信瓶颈:MPI_Reduce的开销**
|
||||||
|
|
||||||
|
**问题:** 每个进程计算完成后需要调用 `MPI_Reduce` 汇总结果
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MPI_Reduce(&total_part, &total, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
|
||||||
|
```
|
||||||
|
|
||||||
|
**影响:**
|
||||||
|
- 当进程数增加时,通信延迟增加
|
||||||
|
- 对于小规模问题(如N=100000),通信开销占比显著
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. **同步瓶颈:进程间相互等待**
|
||||||
|
|
||||||
|
**问题:** 由于负载不均衡,快的进程需要等待慢的进程完成
|
||||||
|
|
||||||
|
**表现:**
|
||||||
|
- 进程0(检测小数字)很快完成
|
||||||
|
- 进程P-1(检测大数字)很慢才完成
|
||||||
|
- 所有进程必须等待最慢的进程完成才能调用 MPI_Reduce
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 加速比问题分析
|
||||||
|
|
||||||
|
### 问题1:2个进程时加速比 < 1
|
||||||
|
|
||||||
|
**现象:** 使用2个进程比单进程还慢
|
||||||
|
|
||||||
|
**原因:**
|
||||||
|
1. **通信开销 > 并行收益**:当N=100000时,问题规模较小,MPI通信和同步的开销超过了并行计算的收益
|
||||||
|
2. **负载不均衡**:2个进程时,进程0检测偶数位置数字,进程1检测奇数位置数字,但奇数位置的平均数字更大,检测成本更高
|
||||||
|
3. **缓存效应**:单进程可能有更好的缓存局部性
|
||||||
|
|
||||||
|
### 问题2:效率随进程数增加而下降
|
||||||
|
|
||||||
|
**现象:**
|
||||||
|
- 4进程:效率 47%
|
||||||
|
- 6进程:效率 30%
|
||||||
|
- 8进程:效率 42%
|
||||||
|
|
||||||
|
**原因:**
|
||||||
|
1. **Amdahl定律**:程序中存在串行部分(MPI初始化、Reduce汇总、结果打印),限制了最大加速比
|
||||||
|
2. **通信开销增加**:进程数越多,通信和同步开销越大
|
||||||
|
3. **负载不均衡加剧**:进程数越多,进程间的计算成本差异越明显
|
||||||
|
|
||||||
|
### 问题3:6进程效率异常低(30%)
|
||||||
|
|
||||||
|
**可能原因:**
|
||||||
|
1. **NUMA效应**:6个进程可能跨越不同的CPU socket,导致跨socket通信开销增加
|
||||||
|
2. **线程调度**:操作系统调度6个进程到不同核心可能产生额外的上下文切换开销
|
||||||
|
3. **内存带宽竞争**:6个进程同时访问内存可能导致带宽饱和
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 改进建议
|
||||||
|
|
||||||
|
### 1. **改进素数检测算法**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 改进:只检查到√i
|
||||||
|
int is_prime(int n) {
|
||||||
|
if (n < 2) return 0;
|
||||||
|
if (n == 2) return 1;
|
||||||
|
if (n % 2 == 0) return 0;
|
||||||
|
|
||||||
|
for (int j = 3; j * j <= n; j += 2) {
|
||||||
|
if (n % j == 0) return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期效果:** 将算法复杂度从 O(n²) 降至 O(n√n),可提速约 √n 倍
|
||||||
|
|
||||||
|
### 2. **改进负载均衡策略**
|
||||||
|
|
||||||
|
**方案A:块分配(Block Distribution)**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 将数字范围分成P个连续的块
|
||||||
|
int block_size = (n - 1) / p;
|
||||||
|
int start = 2 + id * block_size;
|
||||||
|
int end = (id == p - 1) ? n : 2 + (id + 1) * block_size - 1;
|
||||||
|
|
||||||
|
for (int i = start; i <= end; i++) {
|
||||||
|
// 检测i是否为素数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点:** 每个进程处理连续的数字范围,减少缓存失效
|
||||||
|
**缺点:** 仍然存在负载不均衡(后面的进程处理更大的数字)
|
||||||
|
|
||||||
|
**方案B:动态负载均衡**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 使用任务队列,进程完成一个任务后领取下一个
|
||||||
|
int current = 2;
|
||||||
|
#pragma omp critical
|
||||||
|
{
|
||||||
|
current = next_number++;
|
||||||
|
}
|
||||||
|
if (current <= n) {
|
||||||
|
// 检测current是否为素数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点:** 自动实现负载均衡
|
||||||
|
**缺点:** 需要同步机制,可能增加开销
|
||||||
|
|
||||||
|
**方案C:反向分配**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 让进程0处理大数字,进程P-1处理小数字
|
||||||
|
for (int i = n - id; i >= 2; i -= p) {
|
||||||
|
// 检测i是否为素数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优点:** 简单,部分缓解负载不均衡
|
||||||
|
**缺点:** 不能完全解决问题
|
||||||
|
|
||||||
|
### 3. **减少通信开销**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 使用非阻塞通信
|
||||||
|
MPI_Ireduce(&total_part, &total, 1, MPI_INT, MPI_SUM, 0,
|
||||||
|
MPI_COMM_WORLD, &request);
|
||||||
|
// 在等待通信完成的同时做其他工作
|
||||||
|
MPI_Wait(&request, MPI_STATUS_IGNORE);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **优化数据局部性**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 预分配缓存,避免频繁分配
|
||||||
|
int* primes = (int*)malloc((n - 1) * sizeof(int));
|
||||||
|
int prime_count = 0;
|
||||||
|
|
||||||
|
// 批量处理,提高缓存命中率
|
||||||
|
for (int i = start; i <= end; i++) {
|
||||||
|
if (is_prime(i)) {
|
||||||
|
primes[prime_count++] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
### 主要瓶颈:
|
||||||
|
1. **算法瓶颈**:O(n²)的素数检测算法效率低下
|
||||||
|
2. **负载均衡瓶颈**:进程间计算成本严重不均
|
||||||
|
3. **通信瓶颈**:MPI_Reduce的同步开销
|
||||||
|
4. **同步瓶颈**:快进程等待慢进程
|
||||||
|
|
||||||
|
### 加速比问题:
|
||||||
|
1. **小规模问题**:通信开销 > 并行收益
|
||||||
|
2. **负载不均衡**:导致效率随进程数增加而下降
|
||||||
|
3. **Amdahl定律**:串行部分限制了最大加速比
|
||||||
|
|
||||||
|
### 优先改进项:
|
||||||
|
1. **改进算法**:将试除法优化到√n(最优先)
|
||||||
|
2. **改进负载分配**:使用块分配或动态分配
|
||||||
|
3. **减少通信**:使用非阻塞通信或减少通信频率
|
||||||
|
|
||||||
|
通过这些改进,预期可以将加速比从当前的 3.38x(8进程)提升到接近理想的 6-7x。
|
||||||
BIN
lab3/prime/analyze_cost
Executable file
BIN
lab3/prime/analyze_cost
Executable file
Binary file not shown.
80
lab3/prime/analyze_cost.cpp
Normal file
80
lab3/prime/analyze_cost.cpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <mpi.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
// 计算每个进程的实际计算成本(考虑素数检测的复杂度)
|
||||||
|
long long estimate_cost(int start, int end, int step) {
|
||||||
|
long long total_cost = 0;
|
||||||
|
for (int i = start; i <= end; i += step) {
|
||||||
|
// 素数检测的成本约为 O(i),即需要检查 i-2 次
|
||||||
|
total_cost += (i - 2);
|
||||||
|
}
|
||||||
|
return total_cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
int id, p;
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &p);
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &id);
|
||||||
|
|
||||||
|
int n = 100000;
|
||||||
|
if (argc == 2) {
|
||||||
|
n = atoi(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算每个进程的计算成本
|
||||||
|
int start = 2 + id;
|
||||||
|
int end = n;
|
||||||
|
long long my_cost = estimate_cost(start, end, p);
|
||||||
|
|
||||||
|
// 收集所有进程的成本
|
||||||
|
long long *costs = nullptr;
|
||||||
|
if (id == 0) {
|
||||||
|
costs = new long long[p];
|
||||||
|
}
|
||||||
|
MPI_Gather(&my_cost, 1, MPI_LONG_LONG_INT, costs, 1, MPI_LONG_LONG_INT, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
if (id == 0) {
|
||||||
|
printf("\n=== 计算成本分析 (N=%d, P=%d) ===\n", n, p);
|
||||||
|
printf("进程号\t数字数量\t估计计算成本\t成本占比\n");
|
||||||
|
printf("------------------------------------------------------------\n");
|
||||||
|
|
||||||
|
long long total_cost = 0;
|
||||||
|
for (int i = 0; i < p; i++) {
|
||||||
|
total_cost += costs[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < p; i++) {
|
||||||
|
int count = (n - (2 + i)) / p + 1;
|
||||||
|
double percentage = 100.0 * costs[i] / total_cost;
|
||||||
|
printf("%d\t%d\t\t%lld\t\t%.2f%%\n", i, count, costs[i], percentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("------------------------------------------------------------\n");
|
||||||
|
printf("总计算成本: %lld\n", total_cost);
|
||||||
|
printf("平均成本: %lld\n", total_cost / p);
|
||||||
|
printf("最大成本: %lld (进程0)\n", costs[0]);
|
||||||
|
printf("最小成本: %lld (进程%d)\n", costs[p-1], p-1);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
double imbalance = 100.0 * (costs[0] - costs[p-1]) / (double)costs[0];
|
||||||
|
printf("=== 负载不均衡分析 ===\n");
|
||||||
|
printf("成本不均衡度: %.2f%%\n", imbalance);
|
||||||
|
printf("\n");
|
||||||
|
printf("说明:\n");
|
||||||
|
printf("- 进程0检测的数字最小(2, %d, %d, ...),但每个数字的检测成本高\n", 2+p, 2+2*p);
|
||||||
|
printf("- 进程%d检测的数字最大(%d, %d, ...),但每个数字的检测成本更高!\n", p-1, 2+(p-1), 2+2*(p-1));
|
||||||
|
printf("\n");
|
||||||
|
printf("关键问题:\n");
|
||||||
|
printf("虽然各进程检查的数字数量相近,但大数字的素数检测需要更多除法运算。\n");
|
||||||
|
printf("例如:检测2需要0次除法,检测100000需要99998次除法!\n");
|
||||||
|
printf("这导致进程间存在严重的负载不均衡。\n");
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
delete[] costs;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
lab3/prime/analyze_load_balance
Executable file
BIN
lab3/prime/analyze_load_balance
Executable file
Binary file not shown.
74
lab3/prime/analyze_load_balance.cpp
Normal file
74
lab3/prime/analyze_load_balance.cpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
// 分析负载均衡的辅助程序
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
int id, p;
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &p);
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &id);
|
||||||
|
|
||||||
|
int n = 100000;
|
||||||
|
if (argc == 2) {
|
||||||
|
n = atoi(argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算每个进程的工作量
|
||||||
|
int workload = 0;
|
||||||
|
for (int i = 2 + id; i <= n; i += p) {
|
||||||
|
workload++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集所有进程的工作量
|
||||||
|
int *workloads = nullptr;
|
||||||
|
if (id == 0) {
|
||||||
|
workloads = new int[p];
|
||||||
|
}
|
||||||
|
MPI_Gather(&workload, 1, MPI_INT, workloads, 1, MPI_INT, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
if (id == 0) {
|
||||||
|
printf("\n=== 负载均衡分析 (N=%d, P=%d) ===\n", n, p);
|
||||||
|
printf("进程号\t检查的数字数量\t分配的数字范围\n");
|
||||||
|
printf("------------------------------------------------\n");
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 0; i < p; i++) {
|
||||||
|
int start = 2 + i;
|
||||||
|
int end = n;
|
||||||
|
int count = workloads[i];
|
||||||
|
total += count;
|
||||||
|
printf("%d\t%d\t\t", i, count);
|
||||||
|
if (count <= 5) {
|
||||||
|
printf("[");
|
||||||
|
for (int j = 0; j < count && j < 3; j++) {
|
||||||
|
printf("%d", start + j * p);
|
||||||
|
if (j < count - 1 && j < 2) printf(", ");
|
||||||
|
}
|
||||||
|
if (count > 3) printf(", ...");
|
||||||
|
printf("]\n");
|
||||||
|
} else {
|
||||||
|
int last = start + (count - 1) * p;
|
||||||
|
printf("[%d, %d, ..., %d] (步长=%d)\n", start, start + p, last, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("------------------------------------------------\n");
|
||||||
|
printf("平均工作量: %d\n", total / p);
|
||||||
|
printf("最大工作量: %d\n", workloads[0]);
|
||||||
|
printf("最小工作量: %d\n", workloads[p-1]);
|
||||||
|
printf("负载不均衡度: %.2f%%\n",
|
||||||
|
100.0 * (workloads[0] - workloads[p-1]) / (double)workloads[0]);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
// 分析素数检测的计算成本
|
||||||
|
printf("=== 计算成本分析 ===\n");
|
||||||
|
printf("注意:小数字的素数检测快,大数字的素数检测慢!\n");
|
||||||
|
printf("进程0检测的数字: 2, %d, %d, ... (小数字,检测快)\n", 2+p, 2+2*p);
|
||||||
|
printf("进程%d检测的数字: %d, %d, %d, ... (大数字,检测慢)\n",
|
||||||
|
p-1, 2+(p-1), 2+2*(p-1), 2+3*(p-1));
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
delete[] workloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -109,7 +109,7 @@ for N in "${N_VALUES[@]}"; do
|
|||||||
echo -n "Running with $P process(es)... "
|
echo -n "Running with $P process(es)... "
|
||||||
|
|
||||||
# Run the program and capture output
|
# Run the program and capture output
|
||||||
OUTPUT=$(mpirun --oversubscribe --hostfile ~/mpi_hosts -np $P ./build/linux/$BUILD_ARCH/release/prime_par $N $(echo "$N/$P" | bc) 2>&1)
|
OUTPUT=$(mpirun --oversubscribe --hostfile ~/mpi_hosts -np $P ./build/linux/$BUILD_ARCH/release/prime_par_naive $N $(echo "$N/$P" | bc) 2>&1)
|
||||||
|
|
||||||
# Extract prime count and time from output
|
# Extract prime count and time from output
|
||||||
PRIME_COUNT=$(echo "$OUTPUT" | grep "Between" | grep -oP '\d+(?= primes)')
|
PRIME_COUNT=$(echo "$OUTPUT" | grep "Between" | grep -oP '\d+(?= primes)')
|
||||||
|
|||||||
15
lab3/prime/prime_results.txt
Normal file
15
lab3/prime/prime_results.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
N值 进程数 素数个数 执行时间(秒)
|
||||||
|
--------------------------------------------------------
|
||||||
|
100000 1 ERROR ERROR
|
||||||
|
100000 2 ERROR ERROR
|
||||||
|
100000 4 ERROR ERROR
|
||||||
|
100000 6 ERROR ERROR
|
||||||
|
100000 8 ERROR ERROR
|
||||||
|
200000 1 ERROR ERROR
|
||||||
|
200000 2 ERROR ERROR
|
||||||
|
200000 4 ERROR ERROR
|
||||||
|
200000 6 ERROR ERROR
|
||||||
|
200000 8 ERROR ERROR
|
||||||
|
400000 1 ERROR ERROR
|
||||||
|
400000 2 ERROR ERROR
|
||||||
|
400000 4 ERROR ERROR
|
||||||
37
lab3/prime/test_performance.sh
Executable file
37
lab3/prime/test_performance.sh
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 性能测试脚本 - 测试不同进程数和N值的加速比
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Prime Number Performance Analysis"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
OUTPUT_FILE="performance_analysis.txt"
|
||||||
|
> $OUTPUT_FILE
|
||||||
|
|
||||||
|
echo "N值 进程数 时间(秒) 加速比 效率" | tee -a $OUTPUT_FILE
|
||||||
|
echo "--------------------------------------------------------" | tee -a $OUTPUT_FILE
|
||||||
|
|
||||||
|
N_VALUES=(100000 200000 400000 800000)
|
||||||
|
PROCESS_COUNTS=(1 2 4 6 8)
|
||||||
|
|
||||||
|
for N in "${N_VALUES[@]}"; do
|
||||||
|
echo ""
|
||||||
|
echo "Testing N = $N"
|
||||||
|
echo "------------------------"
|
||||||
|
|
||||||
|
# 获取单进程时间作为基准
|
||||||
|
BASELINE_TIME=$(mpirun --oversubscribe -np 1 ./build/linux/x86_64/release/prime_par_naive $N 2>&1 | grep "Time =" | grep -oP '[0-9.]+')
|
||||||
|
|
||||||
|
for P in "${PROCESS_COUNTS[@]}"; do
|
||||||
|
TIME=$(mpirun --oversubscribe -np $P ./build/linux/x86_64/release/prime_par_naive $N 2>&1 | grep "Time =" | grep -oP '[0-9.]+')
|
||||||
|
SPEEDUP=$(echo "scale=2; $BASELINE_TIME / $TIME" | bc)
|
||||||
|
EFFICIENCY=$(echo "scale=2; $SPEEDUP / $P * 100" | bc)
|
||||||
|
|
||||||
|
echo "$N $P $TIME ${SPEEDUP}x ${EFFICIENCY}%" | tee -a $OUTPUT_FILE
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Results saved to: $OUTPUT_FILE"
|
||||||
145
lab4/QUICKSTART.md
Normal file
145
lab4/QUICKSTART.md
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# Lab4 快速开始指南
|
||||||
|
|
||||||
|
## 一、编译程序
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yly/dev/hpc-lab-code/lab4
|
||||||
|
xmake
|
||||||
|
```
|
||||||
|
|
||||||
|
## 二、运行实验并收集数据
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./lab4.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
这将:
|
||||||
|
1. 检查 GPU 信息
|
||||||
|
2. 运行所有 CUDA 程序
|
||||||
|
3. 将结果保存到 `experiment_data/` 目录
|
||||||
|
|
||||||
|
## 三、生成图表 (可选)
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
```bash
|
||||||
|
pip install matplotlib numpy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行绘图脚本
|
||||||
|
```bash
|
||||||
|
./plot_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
图表将保存到 `experiment_data/figures/` 目录
|
||||||
|
|
||||||
|
## 四、查看实验数据
|
||||||
|
|
||||||
|
所有数据文件位于 `experiment_data/`:
|
||||||
|
- `gpu_info.txt` - GPU 硬件信息
|
||||||
|
- `vectoradd_results.txt` - 向量加法测试结果
|
||||||
|
- `matrixmul_comparison.txt` - CPU vs GPU 对比数据
|
||||||
|
- `blocksize_analysis.txt` - BLOCK_SIZE 分析数据
|
||||||
|
|
||||||
|
## 五、填写实验报告
|
||||||
|
|
||||||
|
参考 `实验报告模板.md`,其中包含:
|
||||||
|
- 所有思考题的详细解答
|
||||||
|
- 需要填写的性能数据表格
|
||||||
|
- 图表分析指导
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
### 源代码
|
||||||
|
- `vectoradd.cu` - 向量加法 (实验 4.2)
|
||||||
|
- `MatrixMul_cpu.cu` - CPU OpenMP 矩阵乘法
|
||||||
|
- `MatrixMul_kernel1.cu` - CUDA 基础版本
|
||||||
|
- `MatrixMul_kernel2.cu` - CUDA 共享内存优化
|
||||||
|
- `matrixmultiply_block_size_change.cu` - BLOCK_SIZE 性能测试
|
||||||
|
|
||||||
|
### 脚本和配置
|
||||||
|
- `xmake.lua` - 构建配置
|
||||||
|
- `lab4.sh` - 实验数据收集脚本
|
||||||
|
- `plot_results.py` - 自动生成图表
|
||||||
|
- `README.md` - 详细实验说明
|
||||||
|
- `实验报告模板.md` - 报告模板
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 编译失败,提示找不到 CUDA
|
||||||
|
A: 确保 CUDA 已安装,并设置环境变量:
|
||||||
|
```bash
|
||||||
|
export CUDA_HOME=/usr/local/cuda
|
||||||
|
export PATH=$CUDA_HOME/bin:$PATH
|
||||||
|
export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: 运行时提示 "no CUDA-capable device is detected"
|
||||||
|
A: 检查 GPU 驱动:
|
||||||
|
```bash
|
||||||
|
nvidia-smi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: Python 脚本运行失败
|
||||||
|
A: 安装必要的依赖:
|
||||||
|
```bash
|
||||||
|
pip install matplotlib numpy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q: 想单独运行某个程序
|
||||||
|
A:
|
||||||
|
```bash
|
||||||
|
cd build/linux/x86_64/release
|
||||||
|
./vectoradd
|
||||||
|
./MatrixMul_cpu
|
||||||
|
./MatrixMul_kernel1
|
||||||
|
./MatrixMul_kernel2
|
||||||
|
./matrixmultiply_block_size_change
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实验报告要点
|
||||||
|
|
||||||
|
### 必须回答的问题
|
||||||
|
|
||||||
|
**思考题**:
|
||||||
|
1. Kernel1 的数据划分策略
|
||||||
|
2. Kernel2 的优化策略和线程同步的必要性
|
||||||
|
3. Kernel2 的进一步优化空间
|
||||||
|
|
||||||
|
**实验数据**:
|
||||||
|
- 向量加法: 数据规模 vs 时间
|
||||||
|
- 矩阵乘法: CPU vs GPU 性能对比
|
||||||
|
- BLOCK_SIZE: 对性能的影响
|
||||||
|
|
||||||
|
**图表**:
|
||||||
|
- 使用 `plot_results.py` 自动生成
|
||||||
|
- 或手动使用 Excel/Python/matplotlib
|
||||||
|
|
||||||
|
### 性能分析要点
|
||||||
|
|
||||||
|
**加速比计算**:
|
||||||
|
```
|
||||||
|
加速比 = 基准时间 / 优化后时间
|
||||||
|
```
|
||||||
|
|
||||||
|
**FLOPS 计算**:
|
||||||
|
```
|
||||||
|
矩阵乘法: 2 × M × N × K 次浮点运算
|
||||||
|
GFLOPS = 运算次数 / (时间秒 × 10^9)
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键指标**:
|
||||||
|
- 运行时间 (ms)
|
||||||
|
- GFLOPS (计算性能)
|
||||||
|
- 加速比 (相对提升)
|
||||||
|
- 带宽利用率
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
1. ✓ 编译程序
|
||||||
|
2. ✓ 运行实验
|
||||||
|
3. ✓ 生成图表
|
||||||
|
4. ⏭ 填写实验报告模板
|
||||||
|
5. ⏭ 分析数据并得出结论
|
||||||
|
6. ⏭ 提交实验报告
|
||||||
|
|
||||||
|
祝实验顺利!
|
||||||
215
lab4/README.md
Normal file
215
lab4/README.md
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
# Lab4 CUDA 程序实验说明
|
||||||
|
|
||||||
|
## 实验内容
|
||||||
|
|
||||||
|
### 实验 4.2: CUDA程序的编译和运行
|
||||||
|
**文件**: `vectoradd.cu`
|
||||||
|
|
||||||
|
**实验目的**:
|
||||||
|
- 实现向量加法的CUDA程序
|
||||||
|
- 测试不同数据规模对程序执行效率的影响
|
||||||
|
|
||||||
|
**运行方式**:
|
||||||
|
```bash
|
||||||
|
./lab4.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据输出**: `experiment_data/vectoradd_results.txt`
|
||||||
|
|
||||||
|
**需要回答的问题**:
|
||||||
|
- 改变数组大小,测试程序执行效率和数据规模之间的关系
|
||||||
|
- 绘制数据规模 vs 执行时间的图表
|
||||||
|
- 分析性能随数据规模变化的趋势
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 实验 4.3: 基于CUDA优化矩阵乘法
|
||||||
|
|
||||||
|
#### 思考问题
|
||||||
|
|
||||||
|
**思考一**: matMultCUDAKernel1 对于矩阵的数据划分策略是什么?
|
||||||
|
- **提示**: 查看 `MatrixMul_kernel1.cu` 中的核函数实现
|
||||||
|
- **关键点**:
|
||||||
|
- 每个线程负责计算结果矩阵中的哪个元素?
|
||||||
|
- blockIdx 和 threadIdx 如何映射到矩阵的行列?
|
||||||
|
|
||||||
|
**思考二**: matMultCUDAKernel2 对于矩阵运算的优化策略是什么,线程同步是否是必要的,为什么?
|
||||||
|
- **提示**: 查看 `MatrixMul_kernel2.cu` 中的共享内存使用
|
||||||
|
- **关键点**:
|
||||||
|
- 共享内存的作用是什么?
|
||||||
|
- `__syncthreads()` 的作用是什么?
|
||||||
|
- 为什么需要两次 `__syncthreads()`?
|
||||||
|
|
||||||
|
**思考三**: matMultCUDAKernel2 还有没有可以继续优化的空间?
|
||||||
|
- **提示**: 考虑以下优化方向
|
||||||
|
- 寄存器使用
|
||||||
|
- 内存合并访问
|
||||||
|
- 循环展开
|
||||||
|
- Warp 级别的优化
|
||||||
|
|
||||||
|
#### 实验一: CPU vs GPU 性能对比
|
||||||
|
|
||||||
|
**运行方式**:
|
||||||
|
```bash
|
||||||
|
./lab4.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据输出**: `experiment_data/matrixmul_comparison.txt`
|
||||||
|
|
||||||
|
**包含数据**:
|
||||||
|
1. **CPU (OpenMP)**: 不同线程数 (1, 8, 64, 256) 的性能
|
||||||
|
2. **CUDA Kernel1**: 基础 CUDA 实现的性能
|
||||||
|
3. **CUDA Kernel2**: 共享内存优化的性能
|
||||||
|
|
||||||
|
**需要绘制**:
|
||||||
|
- 不同矩阵规模下,CPU vs GPU 的运行时间对比
|
||||||
|
- 加速比图表 (相对于单线程 CPU)
|
||||||
|
- FLOPS 对比图表
|
||||||
|
- 不同 OpenMP 线程数的性能对比
|
||||||
|
|
||||||
|
**矩阵规模**: 512, 1024, 2048, 4096
|
||||||
|
|
||||||
|
#### 实验二: BLOCK_SIZE 对性能的影响
|
||||||
|
|
||||||
|
**运行方式**:
|
||||||
|
```bash
|
||||||
|
./lab4.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据输出**: `experiment_data/blocksize_analysis.txt`
|
||||||
|
|
||||||
|
**包含数据**:
|
||||||
|
- 不同 BLOCK_SIZE (4, 8, 16, 32) 的性能对比
|
||||||
|
- 不同矩阵规模下的测试结果
|
||||||
|
|
||||||
|
**需要绘制**:
|
||||||
|
- BLOCK_SIZE vs 运行时间
|
||||||
|
- BLOCK_SIZE vs GFLOPS
|
||||||
|
- 分析最优 BLOCK_SIZE 的原因
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实验数据分析建议
|
||||||
|
|
||||||
|
### 方法一: 使用 Python 脚本自动生成图表 (推荐)
|
||||||
|
|
||||||
|
**运行方式**:
|
||||||
|
```bash
|
||||||
|
# 确保已安装 matplotlib
|
||||||
|
pip install matplotlib numpy
|
||||||
|
|
||||||
|
# 运行绘图脚本
|
||||||
|
./plot_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**生成的图表**:
|
||||||
|
- `experiment_data/figures/vectoradd_performance.png`: 向量加法性能图
|
||||||
|
- `experiment_data/figures/cpu_vs_gpu_comparison.png`: CPU vs GPU 性能对比
|
||||||
|
- `experiment_data/figures/blocksize_analysis.png`: BLOCK_SIZE 性能分析
|
||||||
|
|
||||||
|
### 方法二: 手动分析数据
|
||||||
|
|
||||||
|
### 1. 数据提取
|
||||||
|
所有实验数据都保存在 `experiment_data/` 目录下,格式为表格形式,可以直接复制到 Excel 或其他数据分析工具。
|
||||||
|
|
||||||
|
### 2. 图表绘制建议
|
||||||
|
|
||||||
|
**实验 4.2**:
|
||||||
|
- 折线图: 数据规模 (N) vs 执行时间
|
||||||
|
- 分析时间复杂度
|
||||||
|
|
||||||
|
**实验 4.3 实验一**:
|
||||||
|
- 柱状图: 不同实现的运行时间对比
|
||||||
|
- 折线图: 矩阵规模 vs 加速比
|
||||||
|
- 热力图: 线程数 × 矩阵规模 的性能分布
|
||||||
|
|
||||||
|
**实验 4.3 实验二**:
|
||||||
|
- 折线图: BLOCK_SIZE vs GFLOPS (不同矩阵规模)
|
||||||
|
- 分析最优 BLOCK_SIZE 的原因
|
||||||
|
|
||||||
|
### 3. 性能分析要点
|
||||||
|
|
||||||
|
**加速比计算**:
|
||||||
|
```
|
||||||
|
加速比 = CPU单线程时间 / 并行程序时间
|
||||||
|
```
|
||||||
|
|
||||||
|
**FLOPS 计算**:
|
||||||
|
```
|
||||||
|
矩阵乘法浮点运算数 = 2 × M × N × K
|
||||||
|
GFLOPS = 浮点运算数 / (时间 × 10^9)
|
||||||
|
```
|
||||||
|
|
||||||
|
**效率分析**:
|
||||||
|
- 内存带宽利用率
|
||||||
|
- 计算强度
|
||||||
|
- GPU 占用率 (Occupancy)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
### 源代码文件
|
||||||
|
- `vectoradd.cu`: 向量加法程序
|
||||||
|
- `MatrixMul_cpu.cu`: CPU OpenMP 矩阵乘法
|
||||||
|
- `MatrixMul_kernel1.cu`: CUDA 基础版本矩阵乘法
|
||||||
|
- `MatrixMul_kernel2.cu`: CUDA 共享内存优化版本
|
||||||
|
- `matrixmultiply_block_size_change.cu`: 不同 BLOCK_SIZE 性能测试
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
- `xmake.lua`: xmake 构建配置
|
||||||
|
- `lab4.sh`: 实验数据收集脚本
|
||||||
|
|
||||||
|
### 输出目录
|
||||||
|
- `experiment_data/`: 实验数据输出目录
|
||||||
|
- `gpu_info.txt`: GPU 信息
|
||||||
|
- `vectoradd_results.txt`: 向量加法测试结果
|
||||||
|
- `matrixmul_comparison.txt`: CPU vs GPU 对比数据
|
||||||
|
- `blocksize_analysis.txt`: BLOCK_SIZE 分析数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 编译和运行
|
||||||
|
|
||||||
|
### 编译所有程序
|
||||||
|
```bash
|
||||||
|
cd lab4
|
||||||
|
xmake
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行实验并收集数据
|
||||||
|
```bash
|
||||||
|
./lab4.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 单独运行某个程序
|
||||||
|
```bash
|
||||||
|
cd build/linux/x86_64/release
|
||||||
|
./vectoradd
|
||||||
|
./MatrixMul_cpu
|
||||||
|
./MatrixMul_kernel1
|
||||||
|
./MatrixMul_kernel2
|
||||||
|
./matrixmultiply_block_size_change
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实验报告要求
|
||||||
|
|
||||||
|
### 必须包含的内容
|
||||||
|
1. **思考题答案**: 详细回答三个思考问题
|
||||||
|
2. **性能数据**: 完整的测试数据表格
|
||||||
|
3. **图表分析**: 至少包含以下图表
|
||||||
|
- 向量加法: 数据规模 vs 时间
|
||||||
|
- 矩阵乘法: CPU vs GPU 性能对比
|
||||||
|
- 矩阵乘法: 加速比分析
|
||||||
|
- BLOCK_SIZE: 性能影响分析
|
||||||
|
4. **结论分析**:
|
||||||
|
- 不同优化策略的效果
|
||||||
|
- 最优配置选择
|
||||||
|
- 性能瓶颈分析
|
||||||
|
|
||||||
|
### 可选的加分项
|
||||||
|
- 使用 Python (matplotlib) 生成更专业的图表
|
||||||
|
- GPU 性能分析工具 (nvprof, Nsight) 的分析结果
|
||||||
|
- 更深入的优化建议和实现
|
||||||
300
lab4/SETUP_SUMMARY.md
Normal file
300
lab4/SETUP_SUMMARY.md
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
# Lab4 CUDA 项目设置完成总结
|
||||||
|
|
||||||
|
## 已完成的工作
|
||||||
|
|
||||||
|
### 1. 创建 xmake 构建系统 ✓
|
||||||
|
|
||||||
|
**文件**: `xmake.lua`
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 配置 CUDA 工具链
|
||||||
|
- 编译 5 个 CUDA 程序
|
||||||
|
- 自动处理 OpenMP 依赖 (MatrixMul_cpu)
|
||||||
|
- 生成优化的 Release 版本
|
||||||
|
|
||||||
|
**编译目标**:
|
||||||
|
- `vectoradd` - 向量加法程序
|
||||||
|
- `MatrixMul_cpu` - CPU OpenMP 矩阵乘法
|
||||||
|
- `MatrixMul_kernel1` - CUDA 基础版本
|
||||||
|
- `MatrixMul_kernel2` - CUDA 共享内存优化
|
||||||
|
- `matrixmultiply_block_size_change` - BLOCK_SIZE 性能测试
|
||||||
|
|
||||||
|
### 2. 优化 CUDA 源代码输出格式 ✓
|
||||||
|
|
||||||
|
**修改的文件**:
|
||||||
|
- `MatrixMul_kernel1.cu` - 添加详细的性能数据输出 (时间、GFLOPS)
|
||||||
|
- `MatrixMul_kernel2.cu` - 添加详细的性能数据输出 (时间、GFLOPS)
|
||||||
|
- 添加必要的头文件 (`<iomanip>`)
|
||||||
|
|
||||||
|
**输出格式**:
|
||||||
|
- 表格化输出,便于复制到实验报告
|
||||||
|
- 包含运行时间 (秒和毫秒)
|
||||||
|
- 计算 GFLOPS 性能指标
|
||||||
|
|
||||||
|
### 3. 创建实验数据收集脚本 ✓
|
||||||
|
|
||||||
|
**文件**: `lab4.sh`
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 自动运行所有 CUDA 程序
|
||||||
|
- 收集 GPU 硬件信息
|
||||||
|
- 将结果保存到 `experiment_data/` 目录
|
||||||
|
- 生成结构化的实验数据文件
|
||||||
|
|
||||||
|
**输出文件**:
|
||||||
|
- `experiment_data/gpu_info.txt` - GPU 信息
|
||||||
|
- `experiment_data/vectoradd_results.txt` - 向量加法数据
|
||||||
|
- `experiment_data/matrixmul_comparison.txt` - CPU vs GPU 对比
|
||||||
|
- `experiment_data/blocksize_analysis.txt` - BLOCK_SIZE 分析
|
||||||
|
|
||||||
|
### 4. 创建 Python 数据可视化脚本 ✓
|
||||||
|
|
||||||
|
**文件**: `plot_results.py`
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 自动解析实验数据
|
||||||
|
- 生成高质量的实验图表
|
||||||
|
- 支持中文字体显示
|
||||||
|
|
||||||
|
**生成的图表**:
|
||||||
|
- `vectoradd_performance.png` - 向量加法性能图
|
||||||
|
- `cpu_vs_gpu_comparison.png` - CPU vs GPU 性能对比 (4个子图)
|
||||||
|
- `blocksize_analysis.png` - BLOCK_SIZE 性能分析 (2个子图)
|
||||||
|
|
||||||
|
**依赖**:
|
||||||
|
```bash
|
||||||
|
pip install matplotlib numpy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 创建详细的文档 ✓
|
||||||
|
|
||||||
|
**README.md** - 完整的实验说明
|
||||||
|
- 实验目的和要求
|
||||||
|
- 思考题详细提示
|
||||||
|
- 数据分析指导
|
||||||
|
- 性能计算公式
|
||||||
|
|
||||||
|
**QUICKSTART.md** - 快速开始指南
|
||||||
|
- 编译和运行步骤
|
||||||
|
- 常见问题解答
|
||||||
|
- 实验报告要点
|
||||||
|
|
||||||
|
**实验报告模板.md** - 报告模板
|
||||||
|
- 思考题详细解答
|
||||||
|
- 性能数据表格
|
||||||
|
- 图表分析框架
|
||||||
|
- 实验总结指导
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
lab4/
|
||||||
|
├── xmake.lua # xmake 构建配置
|
||||||
|
├── lab4.sh # 实验数据收集脚本
|
||||||
|
├── plot_results.py # Python 绘图脚本
|
||||||
|
├── README.md # 详细实验说明
|
||||||
|
├── QUICKSTART.md # 快速开始指南
|
||||||
|
├── 实验报告模板.md # 实验报告模板
|
||||||
|
├── SETUP_SUMMARY.md # 本文件
|
||||||
|
│
|
||||||
|
├── vectoradd.cu # 向量加法程序
|
||||||
|
├── MatrixMul_cpu.cu # CPU OpenMP 矩阵乘法
|
||||||
|
├── MatrixMul_kernel1.cu # CUDA 基础版本
|
||||||
|
├── MatrixMul_kernel2.cu # CUDA 共享内存优化
|
||||||
|
├── matrixmultiply_block_size_change.cu # BLOCK_SIZE 测试
|
||||||
|
│
|
||||||
|
├── build/ # 编译输出目录
|
||||||
|
│ └── linux/x86_64/release/
|
||||||
|
│ ├── vectoradd
|
||||||
|
│ ├── MatrixMul_cpu
|
||||||
|
│ ├── MatrixMul_kernel1
|
||||||
|
│ ├── MatrixMul_kernel2
|
||||||
|
│ └── matrixmultiply_block_size_change
|
||||||
|
│
|
||||||
|
└── experiment_data/ # 实验数据目录
|
||||||
|
├── gpu_info.txt # GPU 信息
|
||||||
|
├── vectoradd_results.txt # 向量加法数据
|
||||||
|
├── matrixmul_comparison.txt # CPU vs GPU 对比
|
||||||
|
├── blocksize_analysis.txt # BLOCK_SIZE 分析
|
||||||
|
└── figures/ # 生成的图表
|
||||||
|
├── vectoradd_performance.png
|
||||||
|
├── cpu_vs_gpu_comparison.png
|
||||||
|
└── blocksize_analysis.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用流程
|
||||||
|
|
||||||
|
### 步骤 1: 编译程序
|
||||||
|
```bash
|
||||||
|
cd /home/yly/dev/hpc-lab-code/lab4
|
||||||
|
xmake
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 2: 运行实验
|
||||||
|
```bash
|
||||||
|
./lab4.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 3: 生成图表 (可选)
|
||||||
|
```bash
|
||||||
|
pip install matplotlib numpy
|
||||||
|
./plot_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 4: 填写实验报告
|
||||||
|
参考 `实验报告模板.md`,使用收集的数据和图表
|
||||||
|
|
||||||
|
## 实验数据说明
|
||||||
|
|
||||||
|
### 实验 4.2: 向量加法
|
||||||
|
**数据文件**: `experiment_data/vectoradd_results.txt`
|
||||||
|
|
||||||
|
**包含内容**:
|
||||||
|
- 不同数据规模 (128, 256, 512, 1024, 2048)
|
||||||
|
- 执行时间 (毫秒)
|
||||||
|
- 验证结果正确性
|
||||||
|
|
||||||
|
**需要分析**:
|
||||||
|
- 数据规模 vs 执行时间的关系
|
||||||
|
- 时间复杂度分析
|
||||||
|
- GPU 并行效率
|
||||||
|
|
||||||
|
### 实验 4.3: 矩阵乘法优化
|
||||||
|
|
||||||
|
#### 思考题
|
||||||
|
详见 `实验报告模板.md` 中的详细解答
|
||||||
|
|
||||||
|
#### 实验一: CPU vs GPU 性能对比
|
||||||
|
**数据文件**: `experiment_data/matrixmul_comparison.txt`
|
||||||
|
|
||||||
|
**包含内容**:
|
||||||
|
1. CPU (OpenMP) 性能
|
||||||
|
- 不同线程数: 1, 8, 64, 256
|
||||||
|
- 不同矩阵规模: 256, 512, 1024, 2048
|
||||||
|
- 运行时间、GFLOPS、加速比
|
||||||
|
|
||||||
|
2. CUDA Kernel1 (基础版本)
|
||||||
|
- 矩阵规模: 512, 1024, 2048, 4096
|
||||||
|
- 运行时间、GFLOPS
|
||||||
|
|
||||||
|
3. CUDA Kernel2 (共享内存优化)
|
||||||
|
- 矩阵规模: 512, 1024, 2048, 4096
|
||||||
|
- 运行时间、GFLOPS
|
||||||
|
|
||||||
|
**需要分析**:
|
||||||
|
- CPU vs GPU 性能对比
|
||||||
|
- 不同 OpenMP 线程数的扩展性
|
||||||
|
- Kernel2 相对 Kernel1 的优化效果
|
||||||
|
- 加速比计算和绘图
|
||||||
|
|
||||||
|
#### 实验二: BLOCK_SIZE 性能影响
|
||||||
|
**数据文件**: `experiment_data/blocksize_analysis.txt`
|
||||||
|
|
||||||
|
**包含内容**:
|
||||||
|
- 不同 BLOCK_SIZE: 4, 8, 16, 32
|
||||||
|
- 不同矩阵规模: 256, 512, 1024, 2048
|
||||||
|
- 运行时间、GFLOPS
|
||||||
|
|
||||||
|
**需要分析**:
|
||||||
|
- BLOCK_SIZE 对性能的影响
|
||||||
|
- 最优 BLOCK_SIZE 的选择
|
||||||
|
- 不同矩阵规模下的最优配置
|
||||||
|
|
||||||
|
## 性能计算公式
|
||||||
|
|
||||||
|
### 加速比
|
||||||
|
```
|
||||||
|
加速比 = 基准时间 / 优化后时间
|
||||||
|
```
|
||||||
|
|
||||||
|
### FLOPS
|
||||||
|
```
|
||||||
|
矩阵乘法浮点运算数 = 2 × M × N × K
|
||||||
|
GFLOPS = 运算次数 / (时间秒 × 10^9)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 效率
|
||||||
|
```
|
||||||
|
效率 = 加速比 / 处理器核心数
|
||||||
|
```
|
||||||
|
|
||||||
|
## 图表说明
|
||||||
|
|
||||||
|
### 自动生成的图表
|
||||||
|
|
||||||
|
1. **vectoradd_performance.png**
|
||||||
|
- X 轴: 数据规模 N
|
||||||
|
- Y 轴: 执行时间 (ms)
|
||||||
|
- 趋势线展示性能变化
|
||||||
|
|
||||||
|
2. **cpu_vs_gpu_comparison.png** (4 个子图)
|
||||||
|
- 子图 1: 运行时间对比 (柱状图)
|
||||||
|
- 子图 2: GFLOPS 对比 (柱状图)
|
||||||
|
- 子图 3: 加速比对比 (折线图)
|
||||||
|
- 子图 4: Kernel2 相对 Kernel1 的提升 (柱状图)
|
||||||
|
|
||||||
|
3. **blocksize_analysis.png** (2 个子图)
|
||||||
|
- 子图 1: 不同 BLOCK_SIZE 的运行时间
|
||||||
|
- 子图 2: 不同 BLOCK_SIZE 的 GFLOPS
|
||||||
|
|
||||||
|
## 实验报告要点
|
||||||
|
|
||||||
|
### 必须包含的内容
|
||||||
|
1. ✓ 思考题详细解答 (模板已提供)
|
||||||
|
2. ✓ 完整的性能数据表格
|
||||||
|
3. ✓ 性能对比图表 (自动生成)
|
||||||
|
4. ✓ 数据分析和结论
|
||||||
|
5. ✓ 优化建议和改进方向
|
||||||
|
|
||||||
|
### 可选的加分项
|
||||||
|
- 使用 nvprof/Nsight 进行性能分析
|
||||||
|
- 实现额外的优化 (如寄存器分块)
|
||||||
|
- 更深入的理论分析
|
||||||
|
- 使用其他 GPU 进行对比测试
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 编译失败
|
||||||
|
**A**: 检查 CUDA 是否正确安装:
|
||||||
|
```bash
|
||||||
|
nvidia-smi
|
||||||
|
nvcc --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2: 运行时找不到 GPU
|
||||||
|
**A**: 检查 GPU 驱动和 CUDA 运行时
|
||||||
|
|
||||||
|
### Q3: Python 脚本报错
|
||||||
|
**A**: 安装依赖:
|
||||||
|
```bash
|
||||||
|
pip install matplotlib numpy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: 想修改测试参数
|
||||||
|
**A**: 编辑对应的 .cu 文件,修改测试规模或参数,然后重新编译
|
||||||
|
|
||||||
|
## 下一步建议
|
||||||
|
|
||||||
|
1. **运行实验**: `./lab4.sh`
|
||||||
|
2. **生成图表**: `./plot_results.py`
|
||||||
|
3. **查看数据**: 检查 `experiment_data/` 目录
|
||||||
|
4. **填写报告**: 使用 `实验报告模板.md`
|
||||||
|
5. **深入分析**: 可以使用 nvprof 进行更详细的性能分析
|
||||||
|
|
||||||
|
## 技术亮点
|
||||||
|
|
||||||
|
1. **自动化数据收集**: 一键运行所有实验
|
||||||
|
2. **结构化输出**: 数据格式便于分析
|
||||||
|
3. **可视化支持**: 自动生成高质量图表
|
||||||
|
4. **详细文档**: 完整的实验指导和模板
|
||||||
|
5. **灵活配置**: 易于修改测试参数
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
所有必要的文件已创建完成,项目结构清晰,文档齐全。学生可以:
|
||||||
|
- 快速编译和运行实验
|
||||||
|
- 自动收集实验数据
|
||||||
|
- 生成专业的性能图表
|
||||||
|
- 参考详细的报告模板
|
||||||
|
|
||||||
|
祝实验顺利!
|
||||||
341
lab4/plot_results.py
Executable file
341
lab4/plot_results.py
Executable file
@ -0,0 +1,341 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Lab4 CUDA 实验数据可视化脚本
|
||||||
|
用于生成实验报告所需的图表
|
||||||
|
"""
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 设置中文字体支持
|
||||||
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
|
||||||
|
plt.rcParams['axes.unicode_minus'] = False
|
||||||
|
|
||||||
|
# 创建输出目录
|
||||||
|
OUTPUT_DIR = Path("experiment_data/figures")
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_vectoradd_data(filename):
|
||||||
|
"""解析向量加法实验数据"""
|
||||||
|
data = {'sizes': [], 'times': []}
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
if 'N=' in line and 'Time=' in line:
|
||||||
|
parts = line.split(',')
|
||||||
|
n = int(parts[0].split('=')[1].strip())
|
||||||
|
time = float(parts[1].split('=')[1].split()[0])
|
||||||
|
data['sizes'].append(n)
|
||||||
|
data['times'].append(time)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def parse_matrixmul_cpu_data(filename):
|
||||||
|
"""解析 CPU 矩阵乘法数据"""
|
||||||
|
data = {8: [], 64: [], 256: []}
|
||||||
|
sizes = []
|
||||||
|
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if 'x' in line and len(line.split()) >= 5:
|
||||||
|
parts = line.split()
|
||||||
|
try:
|
||||||
|
size = int(parts[0].split('x')[0])
|
||||||
|
threads = int(parts[1])
|
||||||
|
time = float(parts[2])
|
||||||
|
gflops = float(parts[3])
|
||||||
|
speedup = float(parts[4])
|
||||||
|
|
||||||
|
if size not in sizes:
|
||||||
|
sizes.append(size)
|
||||||
|
|
||||||
|
if threads in data:
|
||||||
|
data[threads].append({
|
||||||
|
'size': size,
|
||||||
|
'time': time,
|
||||||
|
'gflops': gflops,
|
||||||
|
'speedup': speedup
|
||||||
|
})
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
return data, sizes
|
||||||
|
|
||||||
|
|
||||||
|
def parse_cuda_kernel_data(filename, kernel_name):
|
||||||
|
"""解析 CUDA Kernel 数据"""
|
||||||
|
data = {'sizes': [], 'times': [], 'gflops': []}
|
||||||
|
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
in_kernel_section = False
|
||||||
|
for line in f:
|
||||||
|
if kernel_name in line:
|
||||||
|
in_kernel_section = True
|
||||||
|
continue
|
||||||
|
if in_kernel_section and '----' in line:
|
||||||
|
break
|
||||||
|
if in_kernel_section and 'x' in line:
|
||||||
|
parts = line.split()
|
||||||
|
try:
|
||||||
|
size_str = parts[0]
|
||||||
|
size = int(size_str.split('x')[0])
|
||||||
|
time = float(parts[1])
|
||||||
|
gflops = float(parts[3])
|
||||||
|
data['sizes'].append(size)
|
||||||
|
data['times'].append(time)
|
||||||
|
data['gflops'].append(gflops)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def parse_blocksize_data(filename):
|
||||||
|
"""解析 BLOCK_SIZE 实验数据"""
|
||||||
|
data = {4: {}, 8: {}, 16: {}, 32: {}}
|
||||||
|
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
if 'x' in line and len(line.split()) >= 4:
|
||||||
|
parts = line.split()
|
||||||
|
try:
|
||||||
|
size_str = parts[0]
|
||||||
|
size = int(size_str.split('x')[0])
|
||||||
|
block_str = parts[1]
|
||||||
|
block = int(block_str.split('x')[0])
|
||||||
|
time = float(parts[2])
|
||||||
|
gflops = float(parts[3])
|
||||||
|
|
||||||
|
if block in data:
|
||||||
|
data[block][size] = {
|
||||||
|
'time': time,
|
||||||
|
'gflops': gflops
|
||||||
|
}
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def plot_vectoradd_performance(data):
|
||||||
|
"""绘制向量加法性能图"""
|
||||||
|
fig, ax = plt.subplots(figsize=(10, 6))
|
||||||
|
|
||||||
|
sizes = np.array(data['sizes'])
|
||||||
|
times = np.array(data['times'])
|
||||||
|
|
||||||
|
ax.plot(sizes, times, 'o-', linewidth=2, markersize=8, label='执行时间')
|
||||||
|
ax.set_xlabel('数据规模 N', fontsize=12)
|
||||||
|
ax.set_ylabel('执行时间 (ms)', fontsize=12)
|
||||||
|
ax.set_title('向量加法性能测试 - 数据规模 vs 执行时间', fontsize=14)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
ax.legend(fontsize=11)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(OUTPUT_DIR / 'vectoradd_performance.png', dpi=300)
|
||||||
|
print(f"✓ 生成图表: vectoradd_performance.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_cpu_vs_gpu(cpu_data, cuda1_data, cuda2_data, sizes):
|
||||||
|
"""绘制 CPU vs GPU 性能对比"""
|
||||||
|
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
|
||||||
|
|
||||||
|
threads_list = [8, 64, 256]
|
||||||
|
|
||||||
|
# 子图1: 运行时间对比
|
||||||
|
ax = axes[0, 0]
|
||||||
|
x = np.arange(len(sizes))
|
||||||
|
width = 0.15
|
||||||
|
|
||||||
|
for i, threads in enumerate(threads_list):
|
||||||
|
times = [item['time'] for item in cpu_data[threads]]
|
||||||
|
ax.bar(x + i * width, times, width, label=f'CPU {threads}线程')
|
||||||
|
|
||||||
|
cuda1_times = cuda1_data['times']
|
||||||
|
cuda2_times = cuda2_data['times']
|
||||||
|
|
||||||
|
ax.bar(x + 3 * width, cuda1_times, width, label='CUDA Kernel1')
|
||||||
|
ax.bar(x + 4 * width, cuda2_times, width, label='CUDA Kernel2')
|
||||||
|
|
||||||
|
ax.set_xlabel('矩阵规模', fontsize=11)
|
||||||
|
ax.set_ylabel('运行时间 (s)', fontsize=11)
|
||||||
|
ax.set_title('运行时间对比', fontsize=13)
|
||||||
|
ax.set_xticks(x + 2 * width)
|
||||||
|
ax.set_xticklabels([f'{s}x{s}' for s in sizes])
|
||||||
|
ax.legend(fontsize=9)
|
||||||
|
ax.grid(True, alpha=0.3, axis='y')
|
||||||
|
|
||||||
|
# 子图2: GFLOPS 对比
|
||||||
|
ax = axes[0, 1]
|
||||||
|
for i, threads in enumerate(threads_list):
|
||||||
|
gflops = [item['gflops'] for item in cpu_data[threads]]
|
||||||
|
ax.bar(x + i * width, gflops, width, label=f'CPU {threads}线程')
|
||||||
|
|
||||||
|
cuda1_gflops = cuda1_data['gflops']
|
||||||
|
cuda2_gflops = cuda2_data['gflops']
|
||||||
|
|
||||||
|
ax.bar(x + 3 * width, cuda1_gflops, width, label='CUDA Kernel1')
|
||||||
|
ax.bar(x + 4 * width, cuda2_gflops, width, label='CUDA Kernel2')
|
||||||
|
|
||||||
|
ax.set_xlabel('矩阵规模', fontsize=11)
|
||||||
|
ax.set_ylabel('GFLOPS', fontsize=11)
|
||||||
|
ax.set_title('计算性能对比 (GFLOPS)', fontsize=13)
|
||||||
|
ax.set_xticks(x + 2 * width)
|
||||||
|
ax.set_xticklabels([f'{s}x{s}' for s in sizes])
|
||||||
|
ax.legend(fontsize=9)
|
||||||
|
ax.grid(True, alpha=0.3, axis='y')
|
||||||
|
|
||||||
|
# 子图3: 加速比 (相对于单线程CPU)
|
||||||
|
ax = axes[1, 0]
|
||||||
|
baseline_times = [item['time'] for item in cpu_data[8]] # 使用8线程作为基准
|
||||||
|
|
||||||
|
for i, threads in enumerate(threads_list):
|
||||||
|
speedups = [item['speedup'] for item in cpu_data[threads]]
|
||||||
|
ax.plot(sizes, speedups, 'o-', linewidth=2, markersize=8, label=f'CPU {threads}线程')
|
||||||
|
|
||||||
|
# 计算 CUDA 加速比
|
||||||
|
cuda1_speedups = [baseline_times[i] / cuda1_times[i] for i in range(len(sizes))]
|
||||||
|
cuda2_speedups = [baseline_times[i] / cuda2_times[i] for i in range(len(sizes))]
|
||||||
|
|
||||||
|
ax.plot(sizes, cuda1_speedups, 's-', linewidth=2, markersize=8, label='CUDA Kernel1')
|
||||||
|
ax.plot(sizes, cuda2_speedups, '^-', linewidth=2, markersize=8, label='CUDA Kernel2')
|
||||||
|
|
||||||
|
ax.set_xlabel('矩阵规模', fontsize=11)
|
||||||
|
ax.set_ylabel('加速比', fontsize=11)
|
||||||
|
ax.set_title('加速比对比 (相对于8线程CPU)', fontsize=13)
|
||||||
|
ax.legend(fontsize=9)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
# 子图4: GPU 优化效果
|
||||||
|
ax = axes[1, 1]
|
||||||
|
improvement = [(cuda1_times[i] / cuda2_times[i]) for i in range(len(sizes))]
|
||||||
|
ax.bar(range(len(sizes)), improvement, color='steelblue', alpha=0.7)
|
||||||
|
ax.set_xlabel('矩阵规模', fontsize=11)
|
||||||
|
ax.set_ylabel('性能提升倍数', fontsize=11)
|
||||||
|
ax.set_title('Kernel2 相对于 Kernel1 的性能提升', fontsize=13)
|
||||||
|
ax.set_xticks(range(len(sizes)))
|
||||||
|
ax.set_xticklabels([f'{s}x{s}' for s in sizes])
|
||||||
|
ax.grid(True, alpha=0.3, axis='y')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(OUTPUT_DIR / 'cpu_vs_gpu_comparison.png', dpi=300)
|
||||||
|
print(f"✓ 生成图表: cpu_vs_gpu_comparison.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_blocksize_analysis(data):
|
||||||
|
"""绘制 BLOCK_SIZE 性能分析图"""
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
|
||||||
|
|
||||||
|
block_sizes = [4, 8, 16, 32]
|
||||||
|
matrix_sizes = sorted(list(next(iter(data.values())).keys()))
|
||||||
|
|
||||||
|
# 子图1: 运行时间
|
||||||
|
ax = axes[0]
|
||||||
|
x = np.arange(len(matrix_sizes))
|
||||||
|
width = 0.2
|
||||||
|
|
||||||
|
for i, block_size in enumerate(block_sizes):
|
||||||
|
times = [data[block_size][size]['time'] for size in matrix_sizes]
|
||||||
|
ax.bar(x + i * width, times, width, label=f'BLOCK={block_size}')
|
||||||
|
|
||||||
|
ax.set_xlabel('矩阵规模', fontsize=12)
|
||||||
|
ax.set_ylabel('运行时间 (ms)', fontsize=12)
|
||||||
|
ax.set_title('不同 BLOCK_SIZE 的运行时间对比', fontsize=13)
|
||||||
|
ax.set_xticks(x + 1.5 * width)
|
||||||
|
ax.set_xticklabels([f'{s}x{s}' for s in matrix_sizes])
|
||||||
|
ax.legend(fontsize=10)
|
||||||
|
ax.grid(True, alpha=0.3, axis='y')
|
||||||
|
|
||||||
|
# 子图2: GFLOPS
|
||||||
|
ax = axes[1]
|
||||||
|
for i, block_size in enumerate(block_sizes):
|
||||||
|
gflops = [data[block_size][size]['gflops'] for size in matrix_sizes]
|
||||||
|
ax.plot(matrix_sizes, gflops, 'o-', linewidth=2, markersize=8, label=f'BLOCK={block_size}')
|
||||||
|
|
||||||
|
ax.set_xlabel('矩阵规模', fontsize=12)
|
||||||
|
ax.set_ylabel('GFLOPS', fontsize=12)
|
||||||
|
ax.set_title('不同 BLOCK_SIZE 的计算性能对比', fontsize=13)
|
||||||
|
ax.legend(fontsize=10)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(OUTPUT_DIR / 'blocksize_analysis.png', dpi=300)
|
||||||
|
print(f"✓ 生成图表: blocksize_analysis.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 60)
|
||||||
|
print("Lab4 CUDA 实验数据可视化")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
data_dir = Path("experiment_data")
|
||||||
|
|
||||||
|
# 检查数据文件是否存在
|
||||||
|
if not data_dir.exists():
|
||||||
|
print("❌ 错误: experiment_data 目录不存在")
|
||||||
|
print(" 请先运行 ./lab4.sh 收集实验数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 绘制向量加法性能图
|
||||||
|
vectoradd_file = data_dir / "vectoradd_results.txt"
|
||||||
|
if vectoradd_file.exists():
|
||||||
|
print("1. 绘制向量加法性能图...")
|
||||||
|
try:
|
||||||
|
data = parse_vectoradd_data(vectoradd_file)
|
||||||
|
if data['sizes']:
|
||||||
|
plot_vectoradd_performance(data)
|
||||||
|
else:
|
||||||
|
print(" ⚠ 警告: 无法解析向量加法数据")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ 错误: {e}")
|
||||||
|
else:
|
||||||
|
print("⚠ 跳过: vectoradd_results.txt 不存在")
|
||||||
|
|
||||||
|
# 绘制 CPU vs GPU 对比图
|
||||||
|
matrixmul_file = data_dir / "matrixmul_comparison.txt"
|
||||||
|
if matrixmul_file.exists():
|
||||||
|
print("2. 绘制 CPU vs GPU 性能对比图...")
|
||||||
|
try:
|
||||||
|
cpu_data, sizes = parse_matrixmul_cpu_data(matrixmul_file)
|
||||||
|
cuda1_data = parse_cuda_kernel_data(matrixmul_file, "Kernel1")
|
||||||
|
cuda2_data = parse_cuda_kernel_data(matrixmul_file, "Kernel2")
|
||||||
|
|
||||||
|
if cpu_data and cuda1_data['sizes'] and cuda2_data['sizes']:
|
||||||
|
plot_cpu_vs_gpu(cpu_data, cuda1_data, cuda2_data, sizes)
|
||||||
|
else:
|
||||||
|
print(" ⚠ 警告: 无法解析矩阵乘法数据")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ 错误: {e}")
|
||||||
|
else:
|
||||||
|
print("⚠ 跳过: matrixmul_comparison.txt 不存在")
|
||||||
|
|
||||||
|
# 绘制 BLOCK_SIZE 分析图
|
||||||
|
blocksize_file = data_dir / "blocksize_analysis.txt"
|
||||||
|
if blocksize_file.exists():
|
||||||
|
print("3. 绘制 BLOCK_SIZE 性能分析图...")
|
||||||
|
try:
|
||||||
|
data = parse_blocksize_data(blocksize_file)
|
||||||
|
if data:
|
||||||
|
plot_blocksize_analysis(data)
|
||||||
|
else:
|
||||||
|
print(" ⚠ 警告: 无法解析 BLOCK_SIZE 数据")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ❌ 错误: {e}")
|
||||||
|
else:
|
||||||
|
print("⚠ 跳过: blocksize_analysis.txt 不存在")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"✓ 所有图表已保存到: {OUTPUT_DIR}/")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
28
lab4/test_quick.sh
Executable file
28
lab4/test_quick.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 快速测试脚本 - 验证所有程序可以正常运行
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Lab4 快速测试"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(dirname "$0")"
|
||||||
|
cd "$SCRIPT_DIR/build/linux/x86_64/release" || exit 1
|
||||||
|
|
||||||
|
echo "1. 测试 vectoradd..."
|
||||||
|
./vectoradd
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "2. 测试 MatrixMul_kernel1 (小规模)..."
|
||||||
|
timeout 10 ./MatrixMul_kernel1 || echo "程序运行超时或完成"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "3. 测试 MatrixMul_kernel2 (小规模)..."
|
||||||
|
timeout 10 ./MatrixMul_kernel2 || echo "程序运行超时或完成"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "快速测试完成!"
|
||||||
|
echo "如需完整实验,请运行: ./lab4.sh"
|
||||||
|
echo "=========================================="
|
||||||
232
lab4/使用指南.md
Normal file
232
lab4/使用指南.md
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
# Lab4 CUDA 实验项目 - 使用指南
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
本项目为 Lab4 CUDA 程序实验提供了完整的构建系统、数据收集和分析工具。
|
||||||
|
|
||||||
|
**已创建的文件**:
|
||||||
|
- ✓ `xmake.lua` - 构建配置
|
||||||
|
- ✓ `lab4.sh` - 完整实验数据收集脚本
|
||||||
|
- ✓ `test_quick.sh` - 快速测试脚本
|
||||||
|
- ✓ `plot_results.py` - Python 数据可视化脚本
|
||||||
|
- ✓ `README.md` - 详细实验说明
|
||||||
|
- ✓ `QUICKSTART.md` - 快速开始指南
|
||||||
|
- ✓ `实验报告模板.md` - 实验报告模板
|
||||||
|
- ✓ `SETUP_SUMMARY.md` - 项目设置总结
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 编译程序
|
||||||
|
```bash
|
||||||
|
cd /home/yly/dev/hpc-lab-code/lab4
|
||||||
|
xmake
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 快速测试(验证一切正常)
|
||||||
|
```bash
|
||||||
|
./test_quick.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 运行完整实验
|
||||||
|
```bash
|
||||||
|
./lab4.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: 完整实验可能需要几分钟时间,因为会测试多个矩阵规模和配置。
|
||||||
|
|
||||||
|
### 4. 生成图表(可选)
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pip install matplotlib numpy
|
||||||
|
|
||||||
|
# 生成图表
|
||||||
|
./plot_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实验内容
|
||||||
|
|
||||||
|
### 实验 4.2: 向量加法
|
||||||
|
**程序**: `vectoradd.cu`
|
||||||
|
|
||||||
|
**测试内容**:
|
||||||
|
- 不同数据规模: 128, 256, 512, 1024, 2048
|
||||||
|
- 测量执行时间
|
||||||
|
- 验证结果正确性
|
||||||
|
|
||||||
|
**数据输出**: `experiment_data/vectoradd_results.txt`
|
||||||
|
|
||||||
|
### 实验 4.3: 矩阵乘法优化
|
||||||
|
|
||||||
|
#### 思考题
|
||||||
|
详见 `实验报告模板.md`,包含:
|
||||||
|
1. Kernel1 的数据划分策略
|
||||||
|
2. Kernel2 的优化策略和线程同步的必要性
|
||||||
|
3. Kernel2 的进一步优化空间
|
||||||
|
|
||||||
|
#### 实验一: CPU vs GPU 性能对比
|
||||||
|
**程序**:
|
||||||
|
- `MatrixMul_cpu.cu` - CPU OpenMP 实现
|
||||||
|
- `MatrixMul_kernel1.cu` - CUDA 基础版本
|
||||||
|
- `MatrixMul_kernel2.cu` - CUDA 共享内存优化
|
||||||
|
|
||||||
|
**测试内容**:
|
||||||
|
- CPU: 不同线程数 (1, 8, 64, 256)
|
||||||
|
- GPU: 不同矩阵规模 (512, 1024, 2048, 4096)
|
||||||
|
- 性能指标: 时间、GFLOPS、加速比
|
||||||
|
|
||||||
|
**数据输出**: `experiment_data/matrixmul_comparison.txt`
|
||||||
|
|
||||||
|
#### 实验二: BLOCK_SIZE 性能影响
|
||||||
|
**程序**: `matrixmultiply_block_size_change.cu`
|
||||||
|
|
||||||
|
**测试内容**:
|
||||||
|
- 不同 BLOCK_SIZE: 4, 8, 16, 32
|
||||||
|
- 不同矩阵规模: 256, 512, 1024, 2048
|
||||||
|
- 性能指标: 时间、GFLOPS
|
||||||
|
|
||||||
|
**数据输出**: `experiment_data/blocksize_analysis.txt`
|
||||||
|
|
||||||
|
## 实验报告
|
||||||
|
|
||||||
|
### 报告模板
|
||||||
|
使用 `实验报告模板.md` 作为起点,其中包含:
|
||||||
|
- 思考题详细解答
|
||||||
|
- 性能数据表格
|
||||||
|
- 图表分析框架
|
||||||
|
- 实验总结指导
|
||||||
|
|
||||||
|
### 需要提交的内容
|
||||||
|
1. 思考题答案
|
||||||
|
2. 性能数据表格
|
||||||
|
3. 性能对比图表
|
||||||
|
4. 数据分析和结论
|
||||||
|
5. 优化建议
|
||||||
|
|
||||||
|
### 图表生成
|
||||||
|
**方法一**: 使用 Python 脚本(推荐)
|
||||||
|
```bash
|
||||||
|
./plot_results.py
|
||||||
|
```
|
||||||
|
生成的图表位于 `experiment_data/figures/`
|
||||||
|
|
||||||
|
**方法二**: 手动绘制
|
||||||
|
- 将数据复制到 Excel
|
||||||
|
- 使用 Excel 或其他工具绘制图表
|
||||||
|
|
||||||
|
## 性能分析
|
||||||
|
|
||||||
|
### 关键指标
|
||||||
|
|
||||||
|
**加速比**:
|
||||||
|
```
|
||||||
|
加速比 = 基准时间 / 优化后时间
|
||||||
|
```
|
||||||
|
|
||||||
|
**GFLOPS**:
|
||||||
|
```
|
||||||
|
矩阵乘法: 2 × M × N × K 次浮点运算
|
||||||
|
GFLOPS = 运算次数 / (时间秒 × 10^9)
|
||||||
|
```
|
||||||
|
|
||||||
|
**效率**:
|
||||||
|
```
|
||||||
|
效率 = 加速比 / 处理器核心数
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分析要点
|
||||||
|
|
||||||
|
1. **CPU vs GPU**:
|
||||||
|
- GPU 在大规模矩阵上的优势
|
||||||
|
- 内存带宽的影响
|
||||||
|
- 并行度的差异
|
||||||
|
|
||||||
|
2. **Kernel1 vs Kernel2**:
|
||||||
|
- 共享内存的优化效果
|
||||||
|
- 全局内存访问次数的减少
|
||||||
|
- 性能提升的原因
|
||||||
|
|
||||||
|
3. **BLOCK_SIZE 影响**:
|
||||||
|
- 最优 BLOCK_SIZE 的选择
|
||||||
|
- 占用率 (Occupancy) 的平衡
|
||||||
|
- 不同矩阵规模的最优配置
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 编译失败
|
||||||
|
**A**: 检查 CUDA 安装:
|
||||||
|
```bash
|
||||||
|
nvidia-smi
|
||||||
|
nvcc --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2: 程序运行很慢
|
||||||
|
**A**: 这是正常的,特别是大矩阵测试。可以:
|
||||||
|
- 使用 `test_quick.sh` 进行快速验证
|
||||||
|
- 修改源文件中的测试规模
|
||||||
|
- 耐心等待完整实验完成
|
||||||
|
|
||||||
|
### Q3: 想修改测试参数
|
||||||
|
**A**: 编辑对应的 .cu 文件:
|
||||||
|
- `vectoradd.cu`: 修改 `testSizes` 数组
|
||||||
|
- `MatrixMul_*.cu`: 修改 `sizes` 数组
|
||||||
|
- `matrixmultiply_block_size_change.cu`: 修改 `block_sizes` 和 `matrix_sizes`
|
||||||
|
|
||||||
|
### Q4: Python 脚本报错
|
||||||
|
**A**: 安装依赖:
|
||||||
|
```bash
|
||||||
|
pip install matplotlib numpy
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
lab4/
|
||||||
|
├── *.cu # CUDA 源代码
|
||||||
|
├── xmake.lua # 构建配置
|
||||||
|
├── lab4.sh # 完整实验脚本
|
||||||
|
├── test_quick.sh # 快速测试脚本
|
||||||
|
├── plot_results.py # Python 绘图脚本
|
||||||
|
├── README.md # 详细说明
|
||||||
|
├── QUICKSTART.md # 快速开始
|
||||||
|
├── 实验报告模板.md # 报告模板
|
||||||
|
├── SETUP_SUMMARY.md # 设置总结
|
||||||
|
├── 使用指南.md # 本文件
|
||||||
|
│
|
||||||
|
├── build/ # 编译输出
|
||||||
|
│ └── linux/x86_64/release/
|
||||||
|
│ └── [可执行文件]
|
||||||
|
│
|
||||||
|
└── experiment_data/ # 实验数据
|
||||||
|
├── gpu_info.txt
|
||||||
|
├── vectoradd_results.txt
|
||||||
|
├── matrixmul_comparison.txt
|
||||||
|
├── blocksize_analysis.txt
|
||||||
|
└── figures/ # 生成的图表
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
1. ✓ 编译程序: `xmake`
|
||||||
|
2. ✓ 快速测试: `./test_quick.sh`
|
||||||
|
3. ⏭ 运行完整实验: `./lab4.sh`
|
||||||
|
4. ⏭ 生成图表: `./plot_results.py`
|
||||||
|
5. ⏭ 填写实验报告
|
||||||
|
6. ⏭ 提交报告
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
如有问题,请检查:
|
||||||
|
1. `README.md` - 详细的实验说明
|
||||||
|
2. `QUICKSTART.md` - 常见问题解答
|
||||||
|
3. `实验报告模板.md` - 思考题解答
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
本项目提供了:
|
||||||
|
- ✓ 完整的构建系统
|
||||||
|
- ✓ 自动化数据收集
|
||||||
|
- ✓ Python 数据可视化
|
||||||
|
- ✓ 详细的文档和模板
|
||||||
|
- ✓ 快速测试工具
|
||||||
|
|
||||||
|
祝实验顺利!
|
||||||
260
lab4/实验报告模板.md
Normal file
260
lab4/实验报告模板.md
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# 实验 4: CUDA 程序设计与优化
|
||||||
|
|
||||||
|
## 实验 4.2: CUDA程序的编译和运行
|
||||||
|
|
||||||
|
### 实验目的
|
||||||
|
1. 掌握 CUDA 程序的基本结构和编译方法
|
||||||
|
2. 理解向量加法的并行实现
|
||||||
|
3. 分析数据规模对程序性能的影响
|
||||||
|
|
||||||
|
### 实验结果
|
||||||
|
|
||||||
|
#### 数据规模与执行时间关系
|
||||||
|
|
||||||
|
| 数据规模 N | 执行时间 (ms) | 吞吐量 (elements/s) |
|
||||||
|
|-----------|--------------|---------------------|
|
||||||
|
| 128 | | |
|
||||||
|
| 256 | | |
|
||||||
|
| 512 | | |
|
||||||
|
| 1024 | | |
|
||||||
|
| 2048 | | |
|
||||||
|
|
||||||
|
#### 性能分析
|
||||||
|
|
||||||
|
**图表**: 见 `experiment_data/figures/vectoradd_performance.png`
|
||||||
|
|
||||||
|
**分析**:
|
||||||
|
- 随着数据规模增加,执行时间的变化趋势是:
|
||||||
|
- 时间复杂度分析:
|
||||||
|
- GPU 并行效率分析:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实验 4.3: 基于CUDA优化矩阵乘法
|
||||||
|
|
||||||
|
### 思考题解答
|
||||||
|
|
||||||
|
#### 思考一: matMultCUDAKernel1 对于矩阵的数据划分策略是什么?
|
||||||
|
|
||||||
|
**答案**:
|
||||||
|
|
||||||
|
matMultCUDAKernel1 采用的是 **二维线程块和网格** 的数据划分策略:
|
||||||
|
|
||||||
|
1. **线程组织**:
|
||||||
|
- 每个线程块 (Block) 的大小为 16×16 = 256 个线程
|
||||||
|
- 每个线程负责计算结果矩阵 C 中的一个元素
|
||||||
|
|
||||||
|
2. **数据映射**:
|
||||||
|
```cuda
|
||||||
|
int row = blockIdx.y * blockDim.y + threadIdx.y;
|
||||||
|
int col = blockIdx.x * blockDim.x + threadIdx.x;
|
||||||
|
```
|
||||||
|
- `threadIdx.x` 和 `threadIdx.y`: 线程在线程块内的局部坐标
|
||||||
|
- `blockIdx.x` 和 `blockIdx.y`: 线程块在网格中的全局坐标
|
||||||
|
- `row` 和 `col`: 直接映射到结果矩阵 C 的行列索引
|
||||||
|
|
||||||
|
3. **计算过程**:
|
||||||
|
- 每个线程计算 C[row][col] = Σ(A[row][k] × B[k][col])
|
||||||
|
- 需要访问 A 的第 row 行和 B 的第 col 列
|
||||||
|
|
||||||
|
4. **优缺点**:
|
||||||
|
- ✓ 优点: 实现简单,每个线程独立计算
|
||||||
|
- ✗ 缺点: 每个线程需要多次访问全局内存,带宽利用率低
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 思考二: matMultCUDAKernel2 对于矩阵运算的优化策略是什么,线程同步是否是必要的,为什么?
|
||||||
|
|
||||||
|
**答案**:
|
||||||
|
|
||||||
|
matMultCUDAKernel2 采用的是 **共享内存分块 (Tiling)** 优化策略:
|
||||||
|
|
||||||
|
1. **优化策略**:
|
||||||
|
- 将矩阵 A 和 B 分成小块 (Tile),大小为 TILE_WIDTH × TILE_WIDTH
|
||||||
|
- 每个线程块协作加载一个 Tile 到共享内存
|
||||||
|
- 所有线程从共享内存读取数据进行计算,减少全局内存访问
|
||||||
|
|
||||||
|
2. **共享内存使用**:
|
||||||
|
```cuda
|
||||||
|
__shared__ float shared_A[TILE_WIDTH][TILE_WIDTH];
|
||||||
|
__shared__ float shared_B[TILE_WIDTH][TILE_WIDTH];
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **线程同步的必要性**:
|
||||||
|
- **第一次 `__syncthreads()`**: 确保所有线程完成数据加载到共享内存
|
||||||
|
- **第二次 `__syncthreads()`**: 确保所有线程完成当前 Tile 的计算,才能加载下一个 Tile
|
||||||
|
|
||||||
|
**为什么必要?**
|
||||||
|
- 共享内存是线程块级别的共享资源
|
||||||
|
- 如果不同步,部分线程可能在其他线程完成数据加载前就开始计算
|
||||||
|
- 会导致读取未初始化的数据,产生错误结果
|
||||||
|
|
||||||
|
4. **性能提升**:
|
||||||
|
- 共享内存带宽 ~ 1.5 TB/s,远高于全局内存 ~ 50 GB/s
|
||||||
|
- 每个元素被重复使用 TILE_WIDTH 次,但只需加载一次到共享内存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 思考三: matMultCUDAKernel2 还有没有可以继续优化的空间?
|
||||||
|
|
||||||
|
**答案**:
|
||||||
|
|
||||||
|
是的,还有多个优化方向:
|
||||||
|
|
||||||
|
1. **寄存器分块 (Register Tiling)**:
|
||||||
|
- 将部分计算结果暂存在寄存器中
|
||||||
|
- 进一步减少共享内存访问次数
|
||||||
|
- 预期性能提升: 1.2-1.5x
|
||||||
|
|
||||||
|
2. **循环展开 (Loop Unrolling)**:
|
||||||
|
- 展开内层计算循环,减少循环开销
|
||||||
|
- 编译器可以更好地优化指令级并行
|
||||||
|
|
||||||
|
3. **内存合并访问优化**:
|
||||||
|
- 确保全局内存访问是合并的 (Coalesced)
|
||||||
|
- 调整数据布局或访问模式
|
||||||
|
|
||||||
|
4. **Warp 级别优化**:
|
||||||
|
- 使用 Warp Shuffle 指令在线程间直接交换数据
|
||||||
|
- 减少共享内存使用
|
||||||
|
|
||||||
|
5. **流式多处理器 (SM) 优化**:
|
||||||
|
- 调整 BLOCK_SIZE 以最大化占用率 (Occupancy)
|
||||||
|
- 平衡每个 SM 的线程块数量
|
||||||
|
|
||||||
|
6. **使用 Tensor Core** (现代 GPU):
|
||||||
|
- 利用 Volta/Turing 架构的 Tensor Core 进行矩阵乘法
|
||||||
|
- 可达数倍性能提升
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 实验一: CPU vs GPU 性能对比
|
||||||
|
|
||||||
|
#### 测试环境
|
||||||
|
- GPU: (从 `gpu_info.txt` 填写)
|
||||||
|
- CPU: (填写 CPU 型号)
|
||||||
|
- 编译器: nvcc, gcc
|
||||||
|
- 优化级别: -O3
|
||||||
|
|
||||||
|
#### 性能数据
|
||||||
|
|
||||||
|
**CPU (OpenMP) 不同线程数性能**:
|
||||||
|
|
||||||
|
| 矩阵规模 | 线程数 | 时间 (ms) | GFLOPS | 加速比 |
|
||||||
|
|---------|-------|----------|--------|--------|
|
||||||
|
| 512×512 | 1 | | | 1.00 |
|
||||||
|
| 512×512 | 8 | | | |
|
||||||
|
| 512×512 | 64 | | | |
|
||||||
|
| 512×512 | 256 | | | |
|
||||||
|
| 1024×1024 | 1 | | | 1.00 |
|
||||||
|
| ... | ... | | | |
|
||||||
|
|
||||||
|
**CUDA Kernel1 (基础版本)**:
|
||||||
|
|
||||||
|
| 矩阵规模 | 时间 (ms) | GFLOPS | 相对CPU加速比 |
|
||||||
|
|---------|----------|--------|--------------|
|
||||||
|
| 512×512 | | | |
|
||||||
|
| 1024×1024 | | | |
|
||||||
|
| 2048×2048 | | | |
|
||||||
|
| 4096×4096 | | | |
|
||||||
|
|
||||||
|
**CUDA Kernel2 (共享内存优化)**:
|
||||||
|
|
||||||
|
| 矩阵规模 | 时间 (ms) | GFLOPS | 相对CPU加速比 | 相对Kernel1提升 |
|
||||||
|
|---------|----------|--------|--------------|---------------|
|
||||||
|
| 512×512 | | | | |
|
||||||
|
| 1024×1024 | | | | |
|
||||||
|
| 2048×2048 | | | | |
|
||||||
|
| 4096×4096 | | | | |
|
||||||
|
|
||||||
|
#### 性能分析
|
||||||
|
|
||||||
|
**图表**: 见 `experiment_data/figures/cpu_vs_gpu_comparison.png`
|
||||||
|
|
||||||
|
**关键发现**:
|
||||||
|
1. CPU 多线程扩展性:
|
||||||
|
2. GPU 相对 CPU 的优势:
|
||||||
|
3. Kernel2 相对 Kernel1 的优化效果:
|
||||||
|
4. 不同矩阵规模下的性能趋势:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 实验二: BLOCK_SIZE 对性能的影响
|
||||||
|
|
||||||
|
#### 性能数据
|
||||||
|
|
||||||
|
| 矩阵规模 | BLOCK_SIZE | 时间 (ms) | GFLOPS |
|
||||||
|
|---------|-----------|----------|--------|
|
||||||
|
| 256×256 | 4 | | |
|
||||||
|
| 256×256 | 8 | | |
|
||||||
|
| 256×256 | 16 | | |
|
||||||
|
| 256×256 | 32 | | |
|
||||||
|
| 512×512 | 4 | | |
|
||||||
|
| ... | ... | | |
|
||||||
|
|
||||||
|
#### 性能分析
|
||||||
|
|
||||||
|
**图表**: 见 `experiment_data/figures/blocksize_analysis.png`
|
||||||
|
|
||||||
|
**最优 BLOCK_SIZE 分析**:
|
||||||
|
|
||||||
|
1. **小矩阵 (256×256)**:
|
||||||
|
- 最优 BLOCK_SIZE:
|
||||||
|
- 原因:
|
||||||
|
|
||||||
|
2. **中等矩阵 (512×512 - 1024×1024)**:
|
||||||
|
- 最优 BLOCK_SIZE:
|
||||||
|
- 原因:
|
||||||
|
|
||||||
|
3. **大矩阵 (2048×2048)**:
|
||||||
|
- 最优 BLOCK_SIZE:
|
||||||
|
- 原因:
|
||||||
|
|
||||||
|
**BLOCK_SIZE 影响因素**:
|
||||||
|
- 共享内存大小限制 (每个 SM 有限)
|
||||||
|
- 线程束 (Warp) 的执行效率
|
||||||
|
- 占用率 (Occupancy) 的平衡
|
||||||
|
- 内存访问模式的优化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实验总结
|
||||||
|
|
||||||
|
### 主要发现
|
||||||
|
1. CUDA 并行计算相比 CPU 的性能优势:
|
||||||
|
2. 共享内存优化的重要性:
|
||||||
|
3. BLOCK_SIZE 对性能的影响规律:
|
||||||
|
|
||||||
|
### 性能优化建议
|
||||||
|
1. 对于小规模矩阵:
|
||||||
|
2. 对于大规模矩阵:
|
||||||
|
3. 通用优化策略:
|
||||||
|
|
||||||
|
### 实验收获
|
||||||
|
- 掌握了 CUDA 编程的基本方法
|
||||||
|
- 理解了 GPU 内存层次结构的优化
|
||||||
|
- 学会了使用性能分析工具评估并行程序
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录
|
||||||
|
|
||||||
|
### 运行命令
|
||||||
|
```bash
|
||||||
|
# 编译所有程序
|
||||||
|
cd lab4
|
||||||
|
xmake
|
||||||
|
|
||||||
|
# 运行实验并收集数据
|
||||||
|
./lab4.sh
|
||||||
|
|
||||||
|
# 生成图表 (需要安装 matplotlib)
|
||||||
|
./plot_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据文件
|
||||||
|
- `experiment_data/gpu_info.txt`: GPU 硬件信息
|
||||||
|
- `experiment_data/vectoradd_results.txt`: 向量加法测试数据
|
||||||
|
- `experiment_data/matrixmul_comparison.txt`: CPU vs GPU 对比数据
|
||||||
|
- `experiment_data/blocksize_analysis.txt`: BLOCK_SIZE 分析数据
|
||||||
|
- `experiment_data/figures/`: 生成的所有图表
|
||||||
276
submit/gemm/matmul_youhua.cpp
Normal file
276
submit/gemm/matmul_youhua.cpp
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mpi.h>
|
||||||
|
#include <omp.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
void randMat(int rows, int cols, float *&Mat) {
|
||||||
|
Mat = new float[rows * cols];
|
||||||
|
for (int i = 0; i < rows; i++)
|
||||||
|
for (int j = 0; j < cols; j++)
|
||||||
|
Mat[i * cols + j] = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 改进的 OpenMP 子矩阵乘法:块化以提升缓存局部性
|
||||||
|
void omp_blocked_sgemm(int M, int N, int K, float *A_buf, float *B_buf,
|
||||||
|
float *C_buf) {
|
||||||
|
// 块大小,用于提高 L1/L2 缓存命中
|
||||||
|
const int TILE_SZ = 64;
|
||||||
|
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for (int rr = 0; rr < M; ++rr) {
|
||||||
|
for (int cc = 0; cc < K; ++cc) {
|
||||||
|
C_buf[rr * K + cc] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三重循环按块执行,减少主存访问并重用缓存数据
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for (int rb = 0; rb < M; rb += TILE_SZ) {
|
||||||
|
for (int cb = 0; cb < K; cb += TILE_SZ) {
|
||||||
|
for (int ib = 0; ib < N; ib += TILE_SZ) {
|
||||||
|
int r_end = min(rb + TILE_SZ, M);
|
||||||
|
int c_end = min(cb + TILE_SZ, K);
|
||||||
|
int i_end = min(ib + TILE_SZ, N);
|
||||||
|
|
||||||
|
for (int r = rb; r < r_end; ++r) {
|
||||||
|
for (int c = cb; c < c_end; ++c) {
|
||||||
|
float acc = C_buf[r * K + c];
|
||||||
|
for (int t = ib; t < i_end; ++t) {
|
||||||
|
acc += A_buf[r * N + t] * B_buf[c * N + t];
|
||||||
|
}
|
||||||
|
C_buf[r * K + c] = acc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mpi_blocked_sgemm(int M, int N, int K, float *&A_buf, float *&B_buf,
|
||||||
|
float *&C_buf, int myRank, int worldN) {
|
||||||
|
|
||||||
|
// 选择接近平方的进程网格(rows x cols)
|
||||||
|
int rbCount = (int)sqrt((double)worldN);
|
||||||
|
while (rbCount > 0 && worldN % rbCount != 0) rbCount--;
|
||||||
|
int cbCount = worldN / rbCount;
|
||||||
|
|
||||||
|
int rLen, cLen;
|
||||||
|
float *localC = nullptr;
|
||||||
|
float *locA = A_buf;
|
||||||
|
float *locB = B_buf;
|
||||||
|
|
||||||
|
if (myRank == 0) {
|
||||||
|
// 将 B 矩阵按行与列交换以便后续按列访问更高效
|
||||||
|
float *tmp = new float[K * N];
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for (int r = 0; r < N; ++r)
|
||||||
|
for (int c = 0; c < K; ++c)
|
||||||
|
tmp[c * N + r] = B_buf[r * K + c];
|
||||||
|
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for (int r = 0; r < K; ++r)
|
||||||
|
for (int c = 0; c < N; ++c)
|
||||||
|
B_buf[r * N + c] = tmp[r * N + c];
|
||||||
|
delete[] tmp;
|
||||||
|
|
||||||
|
// 主进程将子块数据通过非阻塞发送分发给其他进程
|
||||||
|
std::vector<MPI_Request> outReqs;
|
||||||
|
outReqs.reserve(1000);
|
||||||
|
|
||||||
|
for (int rb = 0; rb < rbCount; ++rb) {
|
||||||
|
for (int cb = 0; cb < cbCount; ++cb) {
|
||||||
|
int rBeg = rb * (M / rbCount);
|
||||||
|
int rEnd = (rb == rbCount - 1) ? M : (rb + 1) * (M / rbCount);
|
||||||
|
rLen = rEnd - rBeg;
|
||||||
|
|
||||||
|
int cBeg = cb * (K / cbCount);
|
||||||
|
int cEnd = (cb == cbCount - 1) ? K : (cb + 1) * (K / cbCount);
|
||||||
|
cLen = cEnd - cBeg;
|
||||||
|
|
||||||
|
int dest = rb * cbCount + cb;
|
||||||
|
if (dest == 0) {
|
||||||
|
localC = new float[rLen * cLen];
|
||||||
|
locA = A_buf + rBeg * N;
|
||||||
|
locB = B_buf + cBeg * N;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Request rq;
|
||||||
|
MPI_Isend(&rLen, 1, MPI_INT, dest, 0, MPI_COMM_WORLD, &rq);
|
||||||
|
outReqs.push_back(rq);
|
||||||
|
MPI_Isend(&cLen, 1, MPI_INT, dest, 0, MPI_COMM_WORLD, &rq);
|
||||||
|
outReqs.push_back(rq);
|
||||||
|
|
||||||
|
for (int rr = 0; rr < rLen; ++rr) {
|
||||||
|
MPI_Isend(A_buf + (rBeg + rr) * N, N, MPI_FLOAT, dest, 1, MPI_COMM_WORLD, &rq);
|
||||||
|
outReqs.push_back(rq);
|
||||||
|
}
|
||||||
|
for (int cc = 0; cc < cLen; ++cc) {
|
||||||
|
MPI_Isend(B_buf + (cBeg + cc) * N, N, MPI_FLOAT, dest, 2, MPI_COMM_WORLD, &rq);
|
||||||
|
outReqs.push_back(rq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < outReqs.size(); ++i) MPI_Wait(&outReqs[i], MPI_STATUS_IGNORE);
|
||||||
|
} else {
|
||||||
|
if (myRank < worldN) {
|
||||||
|
int rb = myRank / cbCount;
|
||||||
|
int cb = myRank % cbCount;
|
||||||
|
|
||||||
|
int rBeg = rb * (M / rbCount);
|
||||||
|
int rEnd = (rb == rbCount - 1) ? M : (rb + 1) * (M / rbCount);
|
||||||
|
rLen = rEnd - rBeg;
|
||||||
|
|
||||||
|
int cBeg = cb * (K / cbCount);
|
||||||
|
int cEnd = (cb == cbCount - 1) ? K : (cb + 1) * (K / cbCount);
|
||||||
|
cLen = cEnd - cBeg;
|
||||||
|
|
||||||
|
MPI_Recv(&rLen, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
MPI_Recv(&cLen, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
|
||||||
|
locA = new float[rLen * N];
|
||||||
|
locB = new float[cLen * N];
|
||||||
|
|
||||||
|
for (int rr = 0; rr < rLen; ++rr)
|
||||||
|
MPI_Recv(locA + rr * N, N, MPI_FLOAT, 0, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
for (int cc = 0; cc < cLen; ++cc)
|
||||||
|
MPI_Recv(locB + cc * N, N, MPI_FLOAT, 0, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
|
||||||
|
localC = new float[rLen * cLen];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Barrier(MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
// 调用本地优化的乘法实现
|
||||||
|
if (myRank < worldN) {
|
||||||
|
int rb = myRank / cbCount;
|
||||||
|
int cb = myRank % cbCount;
|
||||||
|
|
||||||
|
int rBeg = rb * (M / rbCount);
|
||||||
|
int rEnd = (rb == rbCount - 1) ? M : (rb + 1) * (M / rbCount);
|
||||||
|
rLen = rEnd - rBeg;
|
||||||
|
|
||||||
|
int cBeg = cb * (K / cbCount);
|
||||||
|
int cEnd = (cb == cbCount - 1) ? K : (cb + 1) * (K / cbCount);
|
||||||
|
cLen = cEnd - cBeg;
|
||||||
|
|
||||||
|
omp_blocked_sgemm(rLen, N, cLen, locA, locB, localC);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Barrier(MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
// 汇总各子块到根进程
|
||||||
|
if (myRank == 0) {
|
||||||
|
int rb = 0, cb = 0;
|
||||||
|
int rBeg = rb * (M / rbCount);
|
||||||
|
int cBeg = cb * (K / cbCount);
|
||||||
|
|
||||||
|
for (int rr = 0; rr < rLen; ++rr)
|
||||||
|
for (int cc = 0; cc < cLen; ++cc)
|
||||||
|
C_buf[(rBeg + rr) * K + (cBeg + cc)] = localC[rr * cLen + cc];
|
||||||
|
delete[] localC;
|
||||||
|
|
||||||
|
for (int rb = 0; rb < rbCount; ++rb) {
|
||||||
|
for (int cb = 0; cb < cbCount; ++cb) {
|
||||||
|
int src = rb * cbCount + cb;
|
||||||
|
if (src == 0) continue;
|
||||||
|
|
||||||
|
MPI_Recv(&rLen, 1, MPI_INT, src, 3, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
MPI_Recv(&cLen, 1, MPI_INT, src, 3, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
|
||||||
|
float *tmp = new float[rLen * cLen];
|
||||||
|
MPI_Recv(tmp, rLen * cLen, MPI_FLOAT, src, 4, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
|
||||||
|
int rStart = rb * (M / rbCount);
|
||||||
|
int cStart = cb * (K / cbCount);
|
||||||
|
for (int rr = 0; rr < rLen; ++rr)
|
||||||
|
for (int cc = 0; cc < cLen; ++cc)
|
||||||
|
C_buf[(rStart + rr) * K + (cStart + cc)] = tmp[rr * cLen + cc];
|
||||||
|
|
||||||
|
delete[] tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (myRank < worldN) {
|
||||||
|
MPI_Send(&rLen, 1, MPI_INT, 0, 3, MPI_COMM_WORLD);
|
||||||
|
MPI_Send(&cLen, 1, MPI_INT, 0, 3, MPI_COMM_WORLD);
|
||||||
|
MPI_Send(localC, rLen * cLen, MPI_FLOAT, 0, 4, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
delete[] localC;
|
||||||
|
delete[] locA;
|
||||||
|
delete[] locB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Barrier(MPI_COMM_WORLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc != 4) {
|
||||||
|
cout << "Usage: " << argv[0] << " M N K\n";
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rank;
|
||||||
|
int worldSize;
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &worldSize);
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||||
|
|
||||||
|
int m = atoi(argv[1]);
|
||||||
|
int n = atoi(argv[2]);
|
||||||
|
int k = atoi(argv[3]);
|
||||||
|
|
||||||
|
float *A_mat, *B_mat, *C_mat;
|
||||||
|
struct timeval start, stop;
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
randMat(m, n, A_mat);
|
||||||
|
randMat(n, k, B_mat);
|
||||||
|
randMat(m, k, C_mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
gettimeofday(&start, NULL);
|
||||||
|
mpi_blocked_sgemm(m, n, k, A_mat, B_mat, C_mat, rank, worldSize);
|
||||||
|
gettimeofday(&stop, NULL);
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
double elapsed = (stop.tv_sec - start.tv_sec) * 1000.0 +
|
||||||
|
(stop.tv_usec - start.tv_usec) / 1000.0;
|
||||||
|
cout << "optimized mpi matmul: " << elapsed << " ms" << endl;
|
||||||
|
|
||||||
|
bool correct = true;
|
||||||
|
for (int i = 0; i < m; i++) {
|
||||||
|
for (int j = 0; j < k; j++){
|
||||||
|
if (int(C_mat[i * k + j]) != n) {
|
||||||
|
cout << "Error at [" << i << "][" << j << "]: "
|
||||||
|
<< C_mat[i * k + j] << " (expected " << n << ")\n";
|
||||||
|
correct = false;
|
||||||
|
goto end_check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end_check:
|
||||||
|
if (correct) {
|
||||||
|
cout << "Result verification: PASSED" << endl;
|
||||||
|
} else {
|
||||||
|
cout << "Result verification: FAILED" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] A_mat;
|
||||||
|
delete[] B_mat;
|
||||||
|
delete[] C_mat;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
27
submit/lab1/mpi_hello_world.c
Normal file
27
submit/lab1/mpi_hello_world.c
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#include <mpi.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
// 初始化 MPI 环境
|
||||||
|
MPI_Init(NULL, NULL);
|
||||||
|
|
||||||
|
// 获取进程总数
|
||||||
|
int world_size;
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
|
||||||
|
|
||||||
|
// 获取当前进程的秩
|
||||||
|
int world_rank;
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
|
||||||
|
|
||||||
|
// 获取处理器名称
|
||||||
|
char processor_name[MPI_MAX_PROCESSOR_NAME];
|
||||||
|
int name_len;
|
||||||
|
MPI_Get_processor_name(processor_name, &name_len);
|
||||||
|
|
||||||
|
// 打印问候信息
|
||||||
|
printf("来自处理器 %s 的问候: rank %d / %d\n", processor_name, world_rank, world_size);
|
||||||
|
|
||||||
|
// 结束 MPI 环境
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
52
submit/lab1/mpi_pi.c
Normal file
52
submit/lab1/mpi_pi.c
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
// 定义参考的PI值用于误差检查
|
||||||
|
#define PI 3.141592653589793238462643
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
|
||||||
|
int processes, pe;
|
||||||
|
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &processes);
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &pe);
|
||||||
|
|
||||||
|
// 由进程0读取区间数量并广播给其他进程
|
||||||
|
int intervals;
|
||||||
|
if (pe == 0) {
|
||||||
|
printf("Number of intervals: ");
|
||||||
|
fflush(stdout);
|
||||||
|
scanf("%d", &intervals);
|
||||||
|
}
|
||||||
|
|
||||||
|
double time1 = MPI_Wtime();
|
||||||
|
|
||||||
|
MPI_Bcast(&intervals, 1, MPI_INT, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
int count = intervals / processes;
|
||||||
|
int start = count * pe;
|
||||||
|
int end = count * pe + count;
|
||||||
|
int i;
|
||||||
|
double subtotal, total = 0;
|
||||||
|
|
||||||
|
for (i = start; i < end; ++i) {
|
||||||
|
subtotal += pow(-1, i) / (2 * i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Reduce(&subtotal, &total, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
double time2 = MPI_Wtime();
|
||||||
|
|
||||||
|
if (pe == 0) {
|
||||||
|
total = total * 4;
|
||||||
|
printf("Result: %.10lf\n", total);
|
||||||
|
|
||||||
|
printf("Accuracy: %.10lf\n", PI - total);
|
||||||
|
printf("Time: %.10lf\n", time2 - time1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
18
submit/lab2/omp/openmp_hello_world.c
Normal file
18
submit/lab2/omp/openmp_hello_world.c
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <omp.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// 并行区域:每个线程都会执行下面的打印
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
printf("Hello World\n");
|
||||||
|
for(i=0; i<4; i++) {
|
||||||
|
printf("Iter:%d\n",i);
|
||||||
|
}
|
||||||
|
printf("GoodBye World\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
39
submit/lab2/omp/pi_par.c
Normal file
39
submit/lab2/omp/pi_par.c
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <omp.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
long long num_steps = 1000000000;
|
||||||
|
double step;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
struct timeval TimeStampStart, TimeStampStop;
|
||||||
|
double ExeTime;
|
||||||
|
double x, pi, sum=0.0;
|
||||||
|
int i;
|
||||||
|
step = 1./(double)num_steps;
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStart, NULL);
|
||||||
|
|
||||||
|
// 并行计算PI,使用OpenMP的reduction合并部分和
|
||||||
|
#pragma omp parallel private(x) reduction(+:sum)
|
||||||
|
{
|
||||||
|
#pragma omp for
|
||||||
|
for (i=0; i<num_steps; i++)
|
||||||
|
{
|
||||||
|
x = (i + .5)*step;
|
||||||
|
sum = sum + 4.0/(1.+ x*x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pi = sum*step;
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStop, NULL);
|
||||||
|
ExeTime = (double)(TimeStampStop.tv_sec - TimeStampStart.tv_sec) +
|
||||||
|
(double)(TimeStampStop.tv_usec - TimeStampStart.tv_usec) * 1e-6;
|
||||||
|
|
||||||
|
printf("The value of PI is %15.12f\n",pi);
|
||||||
|
printf("The time to calculate PI was %f seconds\n", (ExeTime));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
52
submit/lab2/omp/pimonte_par.c
Normal file
52
submit/lab2/omp/pimonte_par.c
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <omp.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#define BLOCK_SIZE 500
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
struct timeval TimeStampStart, TimeStampStop;
|
||||||
|
double ExeTime;
|
||||||
|
unsigned int iter=200000000;
|
||||||
|
int i, j;
|
||||||
|
double x, y;
|
||||||
|
double dUnderCurve=0.0;
|
||||||
|
double pi=0.0;
|
||||||
|
double r[BLOCK_SIZE*2];
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStart, NULL);
|
||||||
|
|
||||||
|
#pragma omp parallel private(i, j, x, y, r) reduction(+:dUnderCurve)
|
||||||
|
{
|
||||||
|
unsigned int seed = omp_get_thread_num() + 1;
|
||||||
|
|
||||||
|
#pragma omp for
|
||||||
|
for(j=0; j<iter/BLOCK_SIZE; j++) {
|
||||||
|
// 生成 BLOCK_SIZE*2 个在 0.0-1.0 内均匀分布的随机数
|
||||||
|
for (i=0; i<BLOCK_SIZE*2; i++) {
|
||||||
|
r[i] = 0.0 + 1.0 * rand_r(&seed) / RAND_MAX * ( 1.0 - 0.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; i<BLOCK_SIZE; i++) {
|
||||||
|
x=r[i]; // X 坐标
|
||||||
|
y=r[i+BLOCK_SIZE]; // Y 坐标
|
||||||
|
if (x*x + y*y <= 1.0) { // 在单位圆内部
|
||||||
|
dUnderCurve++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pi = dUnderCurve / (double) iter * 4;
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStop, NULL);
|
||||||
|
ExeTime = (double)(TimeStampStop.tv_sec - TimeStampStart.tv_sec) +
|
||||||
|
(double)(TimeStampStop.tv_usec - TimeStampStart.tv_usec) * 1e-6;
|
||||||
|
|
||||||
|
printf ("pi = %10.9f\n", pi);
|
||||||
|
printf("The time to calculate PI was %f seconds\n", (ExeTime));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
141
submit/lab2/pthread/count_words_par.c
Normal file
141
submit/lab2/pthread/count_words_par.c
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
int NUM_THREADS= 4;
|
||||||
|
|
||||||
|
FILE *fd;
|
||||||
|
int TotalEvenWords = 0, TotalOddWords = 0, TotalWords = 0;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
|
struct Result {
|
||||||
|
int words;
|
||||||
|
int even;
|
||||||
|
int odd;
|
||||||
|
};
|
||||||
|
|
||||||
|
int GetNextLine(FILE *f, char *Line)
|
||||||
|
{
|
||||||
|
if (fgets(Line, 132, f)==NULL) if (feof(f))return EOF; else return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Result GetWordAndLetterCount(char *Line)
|
||||||
|
{
|
||||||
|
int Word_Count = 0, Letter_Count = 0, Even_Count = 0, Odd_Count = 0;
|
||||||
|
for (int i=0;i<132;i++)
|
||||||
|
{
|
||||||
|
if ((Line[i]!=' ')&&(Line[i]!=0)&&(Line[i]!='\n')) Letter_Count++;
|
||||||
|
else {
|
||||||
|
if (Letter_Count % 2) {
|
||||||
|
Odd_Count++;
|
||||||
|
Word_Count++;
|
||||||
|
Letter_Count = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Even_Count++;
|
||||||
|
Word_Count++;
|
||||||
|
Letter_Count = 0;
|
||||||
|
}
|
||||||
|
if (Line[i]==0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct Result r = {Word_Count, Even_Count, Odd_Count};
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadData {
|
||||||
|
char **lines;
|
||||||
|
int start_line;
|
||||||
|
int end_line;
|
||||||
|
};
|
||||||
|
|
||||||
|
void *count_words_thread(void *arg)
|
||||||
|
{
|
||||||
|
struct ThreadData *data = (struct ThreadData *)arg;
|
||||||
|
for (int i = data->start_line; i < data->end_line; i++) {
|
||||||
|
struct Result r = GetWordAndLetterCount(data->lines[i]);
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
TotalWords += r.words;
|
||||||
|
TotalEvenWords += r.even;
|
||||||
|
TotalOddWords += r.odd;
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
fd = fopen("./InFile1.txt", "r"); // 打开文件读取
|
||||||
|
if (fd == NULL) {
|
||||||
|
perror("Failed to open file");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (argc > 1){
|
||||||
|
NUM_THREADS = atoi(argv[1]);
|
||||||
|
}
|
||||||
|
// 读取所有行
|
||||||
|
char **lines = NULL;
|
||||||
|
int total_lines = 0;
|
||||||
|
char buffer[132];
|
||||||
|
while (fgets(buffer, sizeof(buffer), fd) != NULL) {
|
||||||
|
lines = realloc(lines, (total_lines + 1) * sizeof(char *));
|
||||||
|
lines[total_lines] = strdup(buffer);
|
||||||
|
total_lines++;
|
||||||
|
}
|
||||||
|
fclose(fd);
|
||||||
|
|
||||||
|
if (total_lines == 0) {
|
||||||
|
printf("No lines in file\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timeval TimeStampStart, TimeStampStop;
|
||||||
|
double ExeTime;
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStart, NULL);
|
||||||
|
|
||||||
|
// 初始化互斥锁
|
||||||
|
pthread_mutex_init(&mutex, NULL);
|
||||||
|
|
||||||
|
// 创建线程
|
||||||
|
pthread_t threads[NUM_THREADS];
|
||||||
|
struct ThreadData thread_data[NUM_THREADS];
|
||||||
|
int lines_per_thread = total_lines / NUM_THREADS;
|
||||||
|
int remainder = total_lines % NUM_THREADS;
|
||||||
|
int start = 0;
|
||||||
|
for (int i = 0; i < NUM_THREADS; i++) {
|
||||||
|
int end = start + lines_per_thread + (i < remainder ? 1 : 0);
|
||||||
|
thread_data[i].lines = lines;
|
||||||
|
thread_data[i].start_line = start;
|
||||||
|
thread_data[i].end_line = end;
|
||||||
|
pthread_create(&threads[i], NULL, count_words_thread, &thread_data[i]);
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待线程结束
|
||||||
|
for (int i = 0; i < NUM_THREADS; i++) {
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 销毁互斥锁
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStop, NULL);
|
||||||
|
|
||||||
|
ExeTime = (double)(TimeStampStop.tv_sec - TimeStampStart.tv_sec) +
|
||||||
|
(double)(TimeStampStop.tv_usec - TimeStampStart.tv_usec) * 1e-6;
|
||||||
|
|
||||||
|
// 释放内存
|
||||||
|
for (int i = 0; i < total_lines; i++) {
|
||||||
|
free(lines[i]);
|
||||||
|
}
|
||||||
|
free(lines);
|
||||||
|
|
||||||
|
printf("Total Words = %8d\n", TotalWords);
|
||||||
|
printf("Total Even Words = %7d\nTotal Odd Words = %7d\n", TotalEvenWords, TotalOddWords);
|
||||||
|
printf("The time to count word was %f seconds\n", (ExeTime));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
73
submit/lab2/pthread/count_words_ser.c
Normal file
73
submit/lab2/pthread/count_words_ser.c
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
FILE *fd;
|
||||||
|
int TotalEvenWords = 0, TotalOddWords = 0, TotalWords = 0;
|
||||||
|
int GetNextLine(FILE *f, char *Line)
|
||||||
|
{
|
||||||
|
if (fgets(Line, 132, f)==NULL) if (feof(f))return EOF; else return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetWordAndLetterCount(char *Line)
|
||||||
|
{
|
||||||
|
int Word_Count = 0, Letter_Count = 0;
|
||||||
|
for (int i=0;i<132;i++)
|
||||||
|
{
|
||||||
|
if ((Line[i]!=' ')&&(Line[i]!=0)&&(Line[i]!='\n')) Letter_Count++;
|
||||||
|
else {
|
||||||
|
// 偶数/奇数字母单词计数
|
||||||
|
if (Letter_Count % 2) {
|
||||||
|
TotalOddWords++;
|
||||||
|
Word_Count++;
|
||||||
|
Letter_Count = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TotalEvenWords++;
|
||||||
|
Word_Count++;
|
||||||
|
Letter_Count = 0;
|
||||||
|
}
|
||||||
|
if (Line[i]==0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (Word_Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CountWords()
|
||||||
|
{
|
||||||
|
bool bDone = false;
|
||||||
|
char inLine[132];
|
||||||
|
while (!bDone)
|
||||||
|
{
|
||||||
|
bDone = (GetNextLine(fd, inLine) == EOF);
|
||||||
|
if (!bDone){
|
||||||
|
TotalWords += GetWordAndLetterCount(inLine) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
fd = fopen("./InFile1.txt", "r"); // 打开文件读取
|
||||||
|
struct timeval TimeStampStart, TimeStampStop;
|
||||||
|
double ExeTime;
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStart, NULL);
|
||||||
|
|
||||||
|
CountWords();
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStop, NULL);
|
||||||
|
|
||||||
|
ExeTime = (double)(TimeStampStop.tv_sec - TimeStampStart.tv_sec) +
|
||||||
|
(double)(TimeStampStop.tv_usec - TimeStampStart.tv_usec) * 1e-6;
|
||||||
|
|
||||||
|
fclose(fd);
|
||||||
|
|
||||||
|
printf("Total Words = %8d\n", TotalWords);
|
||||||
|
printf("Total Even Words = %7d\nTotal Odd Words = %7d\n", TotalEvenWords, TotalOddWords);
|
||||||
|
printf("The time to count word was %f seconds\n", (ExeTime));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
74
submit/lab2/pthread/pi_par.c
Normal file
74
submit/lab2/pthread/pi_par.c
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
int NUM_THREADS=4;
|
||||||
|
|
||||||
|
long long num_steps = 1000000000;
|
||||||
|
double step;
|
||||||
|
double global_sum = 0.0;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
|
void *compute_pi(void *arg) {
|
||||||
|
int thread_id = *(int *)arg;
|
||||||
|
double local_sum = 0.0;
|
||||||
|
long long start = thread_id * (num_steps / NUM_THREADS);
|
||||||
|
long long end = (thread_id + 1) * (num_steps / NUM_THREADS);
|
||||||
|
if (thread_id == NUM_THREADS - 1) end = num_steps; // 处理余数
|
||||||
|
|
||||||
|
for (long long i = start; i < end; i++) {
|
||||||
|
double x = (i + 0.5) * step;
|
||||||
|
local_sum += 4.0 / (1.0 + x * x);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
global_sum += local_sum;
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
struct timeval TimeStampStart, TimeStampStop;
|
||||||
|
double ExeTime;
|
||||||
|
double pi;
|
||||||
|
if (argc > 1) {
|
||||||
|
NUM_THREADS = atoi(argv[1]);
|
||||||
|
}
|
||||||
|
int thread_ids[NUM_THREADS];
|
||||||
|
pthread_t threads[NUM_THREADS];
|
||||||
|
|
||||||
|
step = 1.0 / (double)num_steps;
|
||||||
|
|
||||||
|
// 初始化互斥锁
|
||||||
|
pthread_mutex_init(&mutex, NULL);
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStart, NULL);
|
||||||
|
|
||||||
|
// 创建线程
|
||||||
|
for (int i = 0; i < NUM_THREADS; i++) {
|
||||||
|
thread_ids[i] = i;
|
||||||
|
pthread_create(&threads[i], NULL, compute_pi, &thread_ids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待线程
|
||||||
|
for (int i = 0; i < NUM_THREADS; i++) {
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
pi = global_sum * step;
|
||||||
|
|
||||||
|
gettimeofday(&TimeStampStop, NULL);
|
||||||
|
ExeTime = (double)(TimeStampStop.tv_sec - TimeStampStart.tv_sec) +
|
||||||
|
(double)(TimeStampStop.tv_usec - TimeStampStart.tv_usec) * 1e-6;
|
||||||
|
|
||||||
|
// 销毁互斥锁
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
|
||||||
|
printf("The value of PI is %15.12f\n", pi);
|
||||||
|
printf("The time to calculate PI was %f seconds\n", ExeTime);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
37
submit/lab2/pthread/pthread_hello.c
Normal file
37
submit/lab2/pthread/pthread_hello.c
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
const int NumThreads = 16;
|
||||||
|
|
||||||
|
static void* HelloFunc(void* pArg)
|
||||||
|
{
|
||||||
|
// 打印线程编号
|
||||||
|
printf("Hello Thread %d !\n", *((int*)pArg));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
int Num[NumThreads];
|
||||||
|
|
||||||
|
pthread_t ThreadIDs[NumThreads];
|
||||||
|
pthread_attr_t attr[NumThreads];
|
||||||
|
|
||||||
|
for (int i = 0; i < NumThreads; i++) {
|
||||||
|
Num[i] = i;
|
||||||
|
pthread_attr_init(&attr[i]);
|
||||||
|
pthread_attr_setdetachstate(&attr[i], PTHREAD_CREATE_JOINABLE);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NumThreads; i++) {
|
||||||
|
int err = pthread_create(&ThreadIDs[i], &attr[i], HelloFunc, (void*)&Num[i]);
|
||||||
|
|
||||||
|
if(err != 0) {
|
||||||
|
printf("ERROR: pthread_create() return code: %d\n", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NumThreads; i++) {
|
||||||
|
pthread_join(ThreadIDs[i], NULL);
|
||||||
|
printf("Thread %d end !\n", i);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
267
submit/lab3/nbody/nbody_par.cpp
Normal file
267
submit/lab3/nbody/nbody_par.cpp
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// 物理常量
|
||||||
|
const double G = 6.67430e-11; // 引力常数
|
||||||
|
const double DT = 0.01; // 时间步长
|
||||||
|
const int TMAX = 100; // 总时间步数
|
||||||
|
const double mass_scale = 1e24; // 质量缩放因子
|
||||||
|
const double dist_scale = 1e8; // 距离缩放因子
|
||||||
|
const double vel_scale = 1e3; // 速度缩放因子
|
||||||
|
|
||||||
|
// 三维向量结构体
|
||||||
|
struct Vec3 {
|
||||||
|
double x, y, z;
|
||||||
|
|
||||||
|
Vec3() : x(0), y(0), z(0) {}
|
||||||
|
Vec3(double x, double y, double z) : x(x), y(y), z(z) {}
|
||||||
|
|
||||||
|
Vec3 operator+(const Vec3 &other) const {
|
||||||
|
return Vec3(x + other.x, y + other.y, z + other.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 operator-(const Vec3 &other) const {
|
||||||
|
return Vec3(x - other.x, y - other.y, z - other.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 operator*(double scalar) const {
|
||||||
|
return Vec3(x * scalar, y * scalar, z * scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
double magnitude() const {
|
||||||
|
return sqrt(x * x + y * y + z * z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 天体结构体
|
||||||
|
struct Body {
|
||||||
|
double mass; // 质量
|
||||||
|
Vec3 position; // 位置
|
||||||
|
Vec3 velocity; // 速度
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化天体系统
|
||||||
|
void init_bodies(vector<Body> &bodies, int n, bool verbose = false) {
|
||||||
|
// 中心天体(类似太阳)
|
||||||
|
bodies[0].mass = 1000 * mass_scale;
|
||||||
|
bodies[0].position = Vec3(0, 0, 0);
|
||||||
|
bodies[0].velocity = Vec3(0, 0, 0);
|
||||||
|
|
||||||
|
// 其他天体(类似行星)
|
||||||
|
for (int i = 1; i < n; i++) {
|
||||||
|
bodies[i].mass = (1.0 + i * 0.5) * mass_scale;
|
||||||
|
double angle = 2.0 * M_PI * i / n;
|
||||||
|
double radius = (1.0 + i * 0.5) * dist_scale;
|
||||||
|
|
||||||
|
bodies[i].position = Vec3(radius * cos(angle), radius * sin(angle), 0.0);
|
||||||
|
|
||||||
|
// 给予切向速度以形成轨道
|
||||||
|
double orbital_speed = sqrt(G * bodies[0].mass / radius);
|
||||||
|
bodies[i].velocity = Vec3(-orbital_speed * sin(angle),
|
||||||
|
orbital_speed * cos(angle), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出初始状态
|
||||||
|
if (verbose) {
|
||||||
|
cout << fixed << setprecision(6);
|
||||||
|
cout << "\n初始状态:" << endl;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
cout << "天体 " << i << ": 质量=" << bodies[i].mass / mass_scale
|
||||||
|
<< "e24 kg, "
|
||||||
|
<< "位置=(" << bodies[i].position.x / dist_scale << ", "
|
||||||
|
<< bodies[i].position.y / dist_scale << ", "
|
||||||
|
<< bodies[i].position.z / dist_scale << ")e8 m" << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算local_particles中每个物体受到all_particles中所有物体的作用力
|
||||||
|
// 并更新local_particles中物体的速度和位置
|
||||||
|
void compute_local_forces(vector<Body>& local_particles,
|
||||||
|
const vector<Body>& all_particles,
|
||||||
|
int local_start) {
|
||||||
|
for (size_t i = 0; i < local_particles.size(); i++) {
|
||||||
|
Vec3 total_force(0, 0, 0);
|
||||||
|
int global_idx = local_start + i;
|
||||||
|
|
||||||
|
// 计算all_particles中所有物体对local_particles[i]的作用力
|
||||||
|
for (size_t j = 0; j < all_particles.size(); j++) {
|
||||||
|
// 跳过自己
|
||||||
|
if (global_idx == static_cast<int>(j)) continue;
|
||||||
|
|
||||||
|
// 计算从物体i指向物体j的向量
|
||||||
|
Vec3 r_vec = all_particles[j].position - local_particles[i].position;
|
||||||
|
double distance = r_vec.magnitude();
|
||||||
|
|
||||||
|
// 避免除以零
|
||||||
|
if (distance < 1e-10) continue;
|
||||||
|
|
||||||
|
// 计算引力大小
|
||||||
|
double force_magnitude = G * local_particles[i].mass * all_particles[j].mass
|
||||||
|
/ (distance * distance);
|
||||||
|
|
||||||
|
// 计算力的方向并累加
|
||||||
|
Vec3 force_direction = r_vec / distance;
|
||||||
|
total_force = total_force + force_direction * force_magnitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新local_particles[i]的速度和位置
|
||||||
|
Vec3 v_new = local_particles[i].velocity + total_force * DT / local_particles[i].mass;
|
||||||
|
Vec3 x_new = local_particles[i].position + v_new * DT;
|
||||||
|
|
||||||
|
local_particles[i].velocity = v_new;
|
||||||
|
local_particles[i].position = x_new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取每个进程负责的天体信息
|
||||||
|
void get_rank_info(int rank_id, int bodies_count, int world_size,
|
||||||
|
int& send_size, int& send_offset) {
|
||||||
|
int particles_per_proc = bodies_count / world_size;
|
||||||
|
int remainder = bodies_count % world_size;
|
||||||
|
|
||||||
|
if (rank_id < remainder) {
|
||||||
|
send_size = particles_per_proc + 1;
|
||||||
|
send_offset = rank_id * (particles_per_proc + 1);
|
||||||
|
} else {
|
||||||
|
send_size = particles_per_proc;
|
||||||
|
send_offset = rank_id * particles_per_proc + remainder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
|
||||||
|
// 获取进程数量和当前进程rank
|
||||||
|
int world_size, world_rank;
|
||||||
|
bool verbose = false;
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
|
||||||
|
|
||||||
|
// 从命令行参数获取天体数量
|
||||||
|
int n = 4; // 默认4个天体
|
||||||
|
if (argc > 1) {
|
||||||
|
n = atoi(argv[1]);
|
||||||
|
}
|
||||||
|
if (argc > 2) {
|
||||||
|
verbose = (strcmp(argv[2], "--verbose") == 0 || strcmp(argv[2], "-v") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有rank 0打印初始信息
|
||||||
|
if (world_rank == 0) {
|
||||||
|
cout << "N体问题并行模拟" << endl;
|
||||||
|
cout << "天体数量: " << n << endl;
|
||||||
|
cout << "进程数量: " << world_size << endl;
|
||||||
|
cout << "时间步长: " << DT << " s" << endl;
|
||||||
|
cout << "总步数: " << TMAX << endl;
|
||||||
|
cout << "----------------------------------------" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义Body的MPI数据类型
|
||||||
|
// Body结构包含: mass(1) + position(3) + velocity(3) = 7个double
|
||||||
|
MPI_Datatype MPI_BODY;
|
||||||
|
MPI_Type_contiguous(7, MPI_DOUBLE, &MPI_BODY);
|
||||||
|
MPI_Type_commit(&MPI_BODY);
|
||||||
|
|
||||||
|
// 步骤1: 获取分配给本进程的物体的初始信息local_particles
|
||||||
|
// 步骤2: 获取应用程序中所有物体的信息all_particles
|
||||||
|
|
||||||
|
vector<Body> all_particles(n);
|
||||||
|
vector<Body> local_particles;
|
||||||
|
|
||||||
|
// 计算每个进程分配到的物体数量
|
||||||
|
int particles_per_proc = n / world_size;
|
||||||
|
int remainder = n % world_size;
|
||||||
|
|
||||||
|
int local_start, local_count;
|
||||||
|
if (world_rank < remainder) {
|
||||||
|
local_count = particles_per_proc + 1;
|
||||||
|
local_start = world_rank * local_count;
|
||||||
|
} else {
|
||||||
|
local_count = particles_per_proc;
|
||||||
|
local_start = world_rank * particles_per_proc + remainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rank 0初始化所有物体
|
||||||
|
if (world_rank == 0) {
|
||||||
|
init_bodies(all_particles, n, verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 广播所有物体的初始信息到所有进程
|
||||||
|
MPI_Bcast(all_particles.data(), n, MPI_BODY, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
// 每个进程提取自己负责的物体
|
||||||
|
local_particles.resize(local_count);
|
||||||
|
for (int i = 0; i < local_count; i++) {
|
||||||
|
local_particles[i] = all_particles[local_start + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world_rank == 0) {
|
||||||
|
cout << "\n开始模拟..." << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建发送和接收缓冲区信息
|
||||||
|
vector<int> all_send_size(world_size);
|
||||||
|
vector<int> all_send_offset(world_size);
|
||||||
|
|
||||||
|
for (int r = 0; r < world_size; r++) {
|
||||||
|
get_rank_info(r, n, world_size, all_send_size[r], all_send_offset[r]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double start_time = MPI_Wtime();
|
||||||
|
vector<Body> send_buf(local_count);
|
||||||
|
|
||||||
|
// 主循环:N体模拟
|
||||||
|
for (int t = 0; t < TMAX; t++) {
|
||||||
|
// 计算所有物体对分配给本进程的物体的作用力
|
||||||
|
// 并据此更新local_particles的本进程的物体信息
|
||||||
|
compute_local_forces(local_particles, all_particles, local_start);
|
||||||
|
|
||||||
|
// 将本进程信息local_particles保存到发送缓冲区send_buf
|
||||||
|
// 同时更新all_particles中的部分信息
|
||||||
|
send_buf = local_particles;
|
||||||
|
|
||||||
|
// 更新all_particles中本进程负责的部分信息
|
||||||
|
for (int i = 0; i < local_count; i++) {
|
||||||
|
all_particles[local_start + i] = local_particles[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局通信:同步所有进程的物体信息
|
||||||
|
MPI_Allgatherv(send_buf.data(), local_count,
|
||||||
|
MPI_BODY, all_particles.data(),
|
||||||
|
all_send_size.data(), all_send_offset.data(),
|
||||||
|
MPI_BODY, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
|
||||||
|
// 每10步输出一次状态(仅rank 0)
|
||||||
|
if (verbose && (t + 1) % 10 == 0 && world_rank == 0) {
|
||||||
|
cout << "时间步 " << t + 1 << ":" << endl;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
cout << " 天体 " << i << ": "
|
||||||
|
<< "位置=(" << all_particles[i].position.x / dist_scale << ", "
|
||||||
|
<< all_particles[i].position.y / dist_scale << ", "
|
||||||
|
<< all_particles[i].position.z / dist_scale << ")e8 m, "
|
||||||
|
<< "速度=(" << all_particles[i].velocity.x / vel_scale << ", "
|
||||||
|
<< all_particles[i].velocity.y / vel_scale << ", "
|
||||||
|
<< all_particles[i].velocity.z / vel_scale << ")e3 m/s" << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world_rank == 0) {
|
||||||
|
cout << "" << endl;
|
||||||
|
double end_time = MPI_Wtime();
|
||||||
|
cout << "模拟用时: " << end_time - start_time << " 秒" << endl;
|
||||||
|
cout << "\n模拟完成!" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Type_free(&MPI_BODY);
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
154
submit/lab3/prime/prime_par.cpp
Normal file
154
submit/lab3/prime/prime_par.cpp
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <cmath>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
// 对局部区间执行埃拉托斯特尼筛法
|
||||||
|
void local_sieve(int low, int high, std::vector<bool>& is_prime, const std::vector<int>& base_primes) {
|
||||||
|
// 初始化局部区间内的所有数为可能的素数
|
||||||
|
is_prime.assign(high - low + 1, true);
|
||||||
|
|
||||||
|
// 如果区间从0或1开始,标记它们为非素数
|
||||||
|
if (low == 0) {
|
||||||
|
is_prime[0] = false;
|
||||||
|
if (high >= 1) {
|
||||||
|
is_prime[1] = false;
|
||||||
|
}
|
||||||
|
} else if (low == 1) {
|
||||||
|
is_prime[0] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用基础素数标记局部区间中的非素数
|
||||||
|
for (int p : base_primes) {
|
||||||
|
// 找到p在[low, high]范围内的第一个倍数
|
||||||
|
int start_multiple = (low / p) * p;
|
||||||
|
if (start_multiple < low) {
|
||||||
|
start_multiple += p;
|
||||||
|
}
|
||||||
|
// 确保不将素数本身标记为非素数
|
||||||
|
if (start_multiple == p) {
|
||||||
|
start_multiple += p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记局部区间中p的所有倍数为非素数
|
||||||
|
for (int multiple = start_multiple; multiple <= high; multiple += p) {
|
||||||
|
is_prime[multiple - low] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
|
||||||
|
int rank, size;
|
||||||
|
double wtime;
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &size);
|
||||||
|
|
||||||
|
// 检查参数数量
|
||||||
|
if (argc != 3) {
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cerr << "用法: " << argv[0] << " <N> <B>" << std::endl;
|
||||||
|
std::cerr << " N: 区间[2, N]的上界" << std::endl;
|
||||||
|
std::cerr << " B: 分配区间的块大小" << std::endl;
|
||||||
|
}
|
||||||
|
MPI_Finalize();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int N = std::atoi(argv[1]);
|
||||||
|
int B = std::atoi(argv[2]);
|
||||||
|
|
||||||
|
if (N < 2) {
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cout << "区间[2, " << N << "]包含0个素数" << std::endl;
|
||||||
|
}
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤1: 进程0找出sqrt(N)内的基础素数
|
||||||
|
std::vector<int> base_primes;
|
||||||
|
int limit = static_cast<int>(std::sqrt(N));
|
||||||
|
if (rank == 0) {
|
||||||
|
wtime = MPI_Wtime();
|
||||||
|
|
||||||
|
std::vector<bool> is_prime_small(limit + 1, true);
|
||||||
|
is_prime_small[0] = is_prime_small[1] = false;
|
||||||
|
for (int p = 2; p * p <= limit; ++p) {
|
||||||
|
if (is_prime_small[p]) {
|
||||||
|
for (int i = p * p; i <= limit; i += p) {
|
||||||
|
is_prime_small[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 2; i <= limit; ++i) {
|
||||||
|
if (is_prime_small[i]) {
|
||||||
|
base_primes.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤2: 广播基础素数到所有进程
|
||||||
|
int num_base_primes = base_primes.size();
|
||||||
|
MPI_Bcast(&num_base_primes, 1, MPI_INT, 0, MPI_COMM_WORLD);
|
||||||
|
if (rank != 0) {
|
||||||
|
base_primes.resize(num_base_primes);
|
||||||
|
}
|
||||||
|
MPI_Bcast(base_primes.data(), num_base_primes, MPI_INT, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
// 步骤3: 在进程间分配区间[sqrt(N)+1, N]
|
||||||
|
int start_range = limit + 1;
|
||||||
|
if (start_range > N) {
|
||||||
|
// 无需分配,所有素数都是基础素数
|
||||||
|
int total_count = base_primes.size();
|
||||||
|
if (rank == 0) {
|
||||||
|
std::cout << "区间[2, " << N << "]内的素数总数为 " << total_count << std::endl;
|
||||||
|
}
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int total_elements = N - start_range + 1;
|
||||||
|
int local_low, local_high;
|
||||||
|
std::vector<bool> is_prime_local;
|
||||||
|
|
||||||
|
// 计算每个进程分配的区间
|
||||||
|
int elements_per_proc = total_elements / size;
|
||||||
|
int remainder = total_elements % size;
|
||||||
|
|
||||||
|
if (rank < remainder) {
|
||||||
|
local_low = start_range + rank * (elements_per_proc + 1);
|
||||||
|
local_high = local_low + elements_per_proc;
|
||||||
|
} else {
|
||||||
|
local_low = start_range + rank * elements_per_proc + remainder;
|
||||||
|
local_high = local_low + elements_per_proc - 1;
|
||||||
|
}
|
||||||
|
local_high = std::min(local_high, N);
|
||||||
|
|
||||||
|
// 对分配的局部区间执行筛法
|
||||||
|
local_sieve(local_low, local_high, is_prime_local, base_primes);
|
||||||
|
|
||||||
|
// 统计局部区间内的素数数量
|
||||||
|
int local_prime_count = 0;
|
||||||
|
for (bool prime : is_prime_local) {
|
||||||
|
if (prime) {
|
||||||
|
local_prime_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤4: 汇总局部素数计数
|
||||||
|
int global_prime_count = 0;
|
||||||
|
MPI_Reduce(&local_prime_count, &global_prime_count, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
// 步骤5: 进程0输出最终结果
|
||||||
|
if (rank == 0) {
|
||||||
|
double end_wtime = MPI_Wtime() - wtime;
|
||||||
|
int total_count = base_primes.size() + global_prime_count;
|
||||||
|
std::cout << "区间[2, " << N << "]内的素数总数为 " << total_count << std::endl;
|
||||||
|
std::cout << "计算时间: " << end_wtime << " 秒" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
82
submit/lab4/MatrixMul_kernel1.cu
Normal file
82
submit/lab4/MatrixMul_kernel1.cu
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
// 简化版:CUDA 矩阵乘法核函数(直接乘加)
|
||||||
|
__global__ void matMultCUDAKernel1(const float* A, const float* B, float* C, int M, int N, int K) {
|
||||||
|
int row = blockIdx.y * blockDim.y + threadIdx.y;
|
||||||
|
int col = blockIdx.x * blockDim.x + threadIdx.x;
|
||||||
|
|
||||||
|
if(row < M && col < K){
|
||||||
|
float sum = 0.0f;
|
||||||
|
for(int i = 0; i < N; ++i){
|
||||||
|
sum += A[row * N + i] * B[i * K + col];
|
||||||
|
}
|
||||||
|
C[row * K + col] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::vector<int> sizes = {512, 1024, 2048, 4096};
|
||||||
|
std::vector<float> times;
|
||||||
|
|
||||||
|
for(int idx = 0; idx < sizes.size(); ++idx) {
|
||||||
|
int M = sizes[idx];
|
||||||
|
int N = sizes[idx];
|
||||||
|
int K = sizes[idx];
|
||||||
|
float *A = new float[M * N];
|
||||||
|
float *B = new float[N * K];
|
||||||
|
float *C = new float[M * K];
|
||||||
|
for(int i = 0; i < M * N; ++i) A[i] = rand() % 10;
|
||||||
|
for(int i = 0; i < N * K; ++i) B[i] = rand() % 10;
|
||||||
|
float *d_A, *d_B, *d_C;
|
||||||
|
cudaMalloc(&d_A, M * N * sizeof(float));
|
||||||
|
cudaMalloc(&d_B, N * K * sizeof(float));
|
||||||
|
cudaMalloc(&d_C, M * K * sizeof(float));
|
||||||
|
cudaMemcpy(d_A, A, M * N * sizeof(float), cudaMemcpyHostToDevice);
|
||||||
|
cudaMemcpy(d_B, B, N * K * sizeof(float), cudaMemcpyHostToDevice);
|
||||||
|
dim3 blockSize(16, 16);
|
||||||
|
dim3 gridSize((K + blockSize.x - 1) / blockSize.x,
|
||||||
|
(M + blockSize.y - 1) / blockSize.y);
|
||||||
|
matMultCUDAKernel1<<<gridSize, blockSize>>>(d_A, d_B, d_C, M, N, K);
|
||||||
|
cudaDeviceSynchronize();
|
||||||
|
auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
matMultCUDAKernel1<<<gridSize, blockSize>>>(d_A, d_B, d_C, M, N, K);
|
||||||
|
cudaDeviceSynchronize();
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
cudaMemcpy(C, d_C, M * K * sizeof(float), cudaMemcpyDeviceToHost);
|
||||||
|
std::chrono::duration<float> duration = end - start;
|
||||||
|
times.push_back(duration.count());
|
||||||
|
cudaFree(d_A);
|
||||||
|
cudaFree(d_B);
|
||||||
|
cudaFree(d_C);
|
||||||
|
delete[] A;
|
||||||
|
delete[] B;
|
||||||
|
delete[] C;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "CUDA Kernel1 矩阵乘法性能测试结果" << std::endl;
|
||||||
|
std::cout << "=================================" << std::endl;
|
||||||
|
std::cout << std::setw(12) << "Matrix Size"
|
||||||
|
<< std::setw(15) << "Time(s)"
|
||||||
|
<< std::setw(15) << "Time(ms)"
|
||||||
|
<< std::setw(15) << "GFLOPS" << std::endl;
|
||||||
|
std::cout << "---------------------------------" << std::endl;
|
||||||
|
|
||||||
|
for(int i = 0; i < sizes.size(); ++i) {
|
||||||
|
int size = sizes[i];
|
||||||
|
double total_flops = 2.0 * size * size * size;
|
||||||
|
double gflops = total_flops / (times[i] * 1e9);
|
||||||
|
double time_ms = times[i] * 1000.0;
|
||||||
|
|
||||||
|
std::cout << std::setw(8) << size << "x" << std::setw(3) << size
|
||||||
|
<< std::setw(15) << std::fixed << std::setprecision(6) << times[i]
|
||||||
|
<< std::setw(15) << std::fixed << std::setprecision(3) << time_ms
|
||||||
|
<< std::setw(15) << std::fixed << std::setprecision(2) << gflops << std::endl;
|
||||||
|
}
|
||||||
|
std::cout << "=================================" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
91
submit/lab4/vectoradd.cu
Normal file
91
submit/lab4/vectoradd.cu
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#include <cuda_runtime.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#define CHECK(call) \
|
||||||
|
{ \
|
||||||
|
const cudaError_t error = call; \
|
||||||
|
if (error != cudaSuccess) \
|
||||||
|
{ \
|
||||||
|
printf("Error: %s:%d, ", __FILE__, __LINE__); \
|
||||||
|
printf("code:%d, reason: %s\n", error, cudaGetErrorString(error)); \
|
||||||
|
exit(1); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向量加法核函数
|
||||||
|
__global__ void add(const int *dev_a, const int *dev_b, int *dev_c, int N)
|
||||||
|
{
|
||||||
|
int i = threadIdx.x + blockIdx.x * blockDim.x;
|
||||||
|
if (i < N) {
|
||||||
|
dev_c[i] = dev_a[i] + dev_b[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行一次向量加法测试并计时
|
||||||
|
void vectorAddTest(int N, int threadsPerBlock)
|
||||||
|
{
|
||||||
|
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
|
||||||
|
int *host_a = (int*)malloc(N * sizeof(int));
|
||||||
|
int *host_b = (int*)malloc(N * sizeof(int));
|
||||||
|
int *host_c = (int*)malloc(N * sizeof(int));
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
host_a[i] = i;
|
||||||
|
host_b[i] = i << 1;
|
||||||
|
}
|
||||||
|
int *dev_a = NULL;
|
||||||
|
int *dev_b = NULL;
|
||||||
|
int *dev_c = NULL;
|
||||||
|
CHECK(cudaMalloc((void**)&dev_a, N * sizeof(int)));
|
||||||
|
CHECK(cudaMalloc((void**)&dev_b, N * sizeof(int)));
|
||||||
|
CHECK(cudaMalloc((void**)&dev_c, N * sizeof(int)));
|
||||||
|
CHECK(cudaMemcpy(dev_a, host_a, N * sizeof(int), cudaMemcpyHostToDevice));
|
||||||
|
CHECK(cudaMemcpy(dev_b, host_b, N * sizeof(int), cudaMemcpyHostToDevice));
|
||||||
|
cudaEvent_t start, stop;
|
||||||
|
CHECK(cudaEventCreate(&start));
|
||||||
|
CHECK(cudaEventCreate(&stop));
|
||||||
|
add<<<blocksPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_c, N);
|
||||||
|
cudaDeviceSynchronize();
|
||||||
|
CHECK(cudaEventRecord(start));
|
||||||
|
add<<<blocksPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_c, N);
|
||||||
|
CHECK(cudaEventRecord(stop));
|
||||||
|
CHECK(cudaEventSynchronize(stop));
|
||||||
|
float elapsedTime_ms = 0;
|
||||||
|
CHECK(cudaEventElapsedTime(&elapsedTime_ms, start, stop));
|
||||||
|
float elapsedTime = elapsedTime_ms * 1000.0f;
|
||||||
|
printf("N=%d, Time=%.3f ms\n", N, elapsedTime);
|
||||||
|
CHECK(cudaMemcpy(host_c, dev_c, N * sizeof(int), cudaMemcpyDeviceToHost));
|
||||||
|
bool success = true;
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
if (host_c[i] != host_a[i] + host_b[i]) {
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
printf("Error: Computation failed for N=%d\n", N);
|
||||||
|
}
|
||||||
|
CHECK(cudaEventDestroy(start));
|
||||||
|
CHECK(cudaEventDestroy(stop));
|
||||||
|
CHECK(cudaFree(dev_a));
|
||||||
|
CHECK(cudaFree(dev_b));
|
||||||
|
CHECK(cudaFree(dev_c));
|
||||||
|
free(host_a);
|
||||||
|
free(host_b);
|
||||||
|
free(host_c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const int threadsPerBlock = 256;
|
||||||
|
int testSizes[] = {128, 256, 512, 1024, 2048};
|
||||||
|
int numTests = sizeof(testSizes) / sizeof(testSizes[0]);
|
||||||
|
printf("Vector Addition Performance Test (Threads per block: %d)\n", threadsPerBlock);
|
||||||
|
printf("========================================================\n");
|
||||||
|
for (int i = 0; i < numTests; i++) {
|
||||||
|
vectorAddTest(testSizes[i], threadsPerBlock);
|
||||||
|
}
|
||||||
|
printf("========================================================\n");
|
||||||
|
printf("All tests completed.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
343
work/DELIVERY_CHECKLIST.md
Normal file
343
work/DELIVERY_CHECKLIST.md
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
# 项目交付清单
|
||||||
|
|
||||||
|
## ✅ 文件清单
|
||||||
|
|
||||||
|
### 源代码文件
|
||||||
|
- [x] gemm_serial.cpp - 串行版本实现
|
||||||
|
- [x] gemm_parallel.cpp - MPI-OpenMP混合并行版本
|
||||||
|
- [x] gemm_optimized.cpp - 优化版本
|
||||||
|
|
||||||
|
### 构建和测试脚本
|
||||||
|
- [x] build.sh - 编译脚本
|
||||||
|
- [x] quick_test.sh - 快速测试脚本
|
||||||
|
- [x] run_experiments.sh - 完整实验脚本
|
||||||
|
- [x] xmake.lua - xmake构建配置
|
||||||
|
|
||||||
|
### 数据分析工具
|
||||||
|
- [x] analyze_results.py - Python数据分析脚本
|
||||||
|
|
||||||
|
### 文档文件
|
||||||
|
- [x] README.md - 项目说明文档
|
||||||
|
- [x] QUICKSTART.md - 快速开始指南
|
||||||
|
- [x] 实验报告模板.md - 实验报告模板
|
||||||
|
- [x] PROJECT_SUMMARY.md - 项目总结
|
||||||
|
- [x] DELIVERY_CHECKLIST.md - 本文件
|
||||||
|
|
||||||
|
## ✅ 功能完成清单
|
||||||
|
|
||||||
|
### 程序功能
|
||||||
|
- [x] 串行矩阵乘法
|
||||||
|
- [x] MPI并行矩阵乘法
|
||||||
|
- [x] OpenMP并行矩阵乘法
|
||||||
|
- [x] MPI-OpenMP混合并行
|
||||||
|
- [x] 矩阵转置优化
|
||||||
|
- [x] 结果验证
|
||||||
|
- [x] 时间测量
|
||||||
|
- [x] 分块优化
|
||||||
|
- [x] 缓存优化
|
||||||
|
|
||||||
|
### 实验功能
|
||||||
|
- [x] 串行基准测试
|
||||||
|
- [x] MPI扩展性测试(实验一)
|
||||||
|
- [x] 混合并行扩展性测试(实验二)
|
||||||
|
- [x] MPI/OpenMP组合优化测试(实验三)
|
||||||
|
- [x] 自动数据收集
|
||||||
|
- [x] 加速比计算
|
||||||
|
- [x] 效率计算
|
||||||
|
- [x] CSV数据导出
|
||||||
|
|
||||||
|
### 分析功能
|
||||||
|
- [x] 数据读取和解析
|
||||||
|
- [x] 性能曲线绘制
|
||||||
|
- [x] 效率热图生成
|
||||||
|
- [x] 统计摘要输出
|
||||||
|
- [x] 多种可视化
|
||||||
|
|
||||||
|
## ✅ 测试验证清单
|
||||||
|
|
||||||
|
### 编译测试
|
||||||
|
- [x] 串行版本编译成功
|
||||||
|
- [x] 并行版本编译成功
|
||||||
|
- [x] 优化版本编译成功
|
||||||
|
- [x] 无编译警告
|
||||||
|
|
||||||
|
### 功能测试
|
||||||
|
- [x] 串行版本测试通过(512×512)
|
||||||
|
- [x] MPI单进程测试通过
|
||||||
|
- [x] MPI多进程测试通过(4进程)
|
||||||
|
- [x] 混合并行测试通过(2×2)
|
||||||
|
- [x] 优化版本测试通过(4进程)
|
||||||
|
- [x] 所有测试结果验证通过
|
||||||
|
|
||||||
|
### 性能测试
|
||||||
|
- [x] 串行版本性能正常
|
||||||
|
- [x] 并行版本有加速效果
|
||||||
|
- [x] 优化版本性能提升明显
|
||||||
|
- [x] 无内存泄漏
|
||||||
|
- [x] 无段错误
|
||||||
|
|
||||||
|
## ✅ 文档完整性清单
|
||||||
|
|
||||||
|
### 用户文档
|
||||||
|
- [x] 安装说明
|
||||||
|
- [x] 编译说明
|
||||||
|
- [x] 运行说明
|
||||||
|
- [x] 使用示例
|
||||||
|
- [x] 参数说明
|
||||||
|
- [x] 输出格式说明
|
||||||
|
|
||||||
|
### 技术文档
|
||||||
|
- [x] 算法描述
|
||||||
|
- [x] 实现细节
|
||||||
|
- [x] 性能分析
|
||||||
|
- [x] 优化策略
|
||||||
|
- [x] 代码注释
|
||||||
|
|
||||||
|
### 实验文档
|
||||||
|
- [x] 实验目的
|
||||||
|
- [x] 实验原理
|
||||||
|
- [x] 实验步骤
|
||||||
|
- [x] 数据收集方法
|
||||||
|
- [x] 分析方法
|
||||||
|
- [x] 报告模板
|
||||||
|
|
||||||
|
## ✅ 代码质量清单
|
||||||
|
|
||||||
|
### 代码规范
|
||||||
|
- [x] 一致的命名风格
|
||||||
|
- [x] 适当的注释
|
||||||
|
- [x] 清晰的结构
|
||||||
|
- [x] 模块化设计
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
- [x] 参数验证
|
||||||
|
- [x] 内存分配检查
|
||||||
|
- [x] MPI错误检查
|
||||||
|
- [x] 结果验证
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
- [x] 编译优化选项(-O3)
|
||||||
|
- [x] 算法优化
|
||||||
|
- [x] 通信优化
|
||||||
|
- [x] 内存优化
|
||||||
|
|
||||||
|
## ✅ 实验要求对照清单
|
||||||
|
|
||||||
|
### 实验目的达成
|
||||||
|
- [x] 掌握MPI程序设计
|
||||||
|
- [x] 掌握OpenMP程序设计
|
||||||
|
- [x] 了解矩阵乘法并行化
|
||||||
|
- [x] 掌握性能分析方法
|
||||||
|
|
||||||
|
### 实验内容完成
|
||||||
|
- [x] 串行算法实现
|
||||||
|
- [x] 并行算法实现
|
||||||
|
- [x] 主从模型实现
|
||||||
|
- [x] 带状分块实现
|
||||||
|
- [x] OpenMP加速实现
|
||||||
|
- [x] 结果收集实现
|
||||||
|
|
||||||
|
### 实验数据收集
|
||||||
|
- [x] 不同矩阵规模数据
|
||||||
|
- [x] 不同MPI进程数数据
|
||||||
|
- [x] 不同OpenMP线程数数据
|
||||||
|
- [x] 加速比数据
|
||||||
|
- [x] 效率数据
|
||||||
|
|
||||||
|
### 性能评估参数
|
||||||
|
- [x] 加速比计算和记录
|
||||||
|
- [x] 效率计算和记录
|
||||||
|
- [x] 运行时间记录
|
||||||
|
- [x] 性能曲线绘制
|
||||||
|
- [x] 瓶颈分析
|
||||||
|
|
||||||
|
### 优化方案
|
||||||
|
- [x] 瓶颈识别
|
||||||
|
- [x] 优化策略提出
|
||||||
|
- [x] 优化版本实现
|
||||||
|
- [x] 效果对比
|
||||||
|
|
||||||
|
## 📋 使用说明
|
||||||
|
|
||||||
|
### 第一次使用
|
||||||
|
|
||||||
|
1. **阅读文档**
|
||||||
|
- 先阅读 QUICKSTART.md
|
||||||
|
- 再阅读 README.md
|
||||||
|
|
||||||
|
2. **编译程序**
|
||||||
|
```bash
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **快速测试**
|
||||||
|
```bash
|
||||||
|
./quick_test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **运行实验**
|
||||||
|
```bash
|
||||||
|
./run_experiments.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **分析结果**
|
||||||
|
```bash
|
||||||
|
python3 analyze_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 撰写实验报告
|
||||||
|
|
||||||
|
1. **使用模板**
|
||||||
|
- 复制 实验报告模板.md
|
||||||
|
- 填入个人信息
|
||||||
|
|
||||||
|
2. **填入数据**
|
||||||
|
- 从CSV文件复制数据
|
||||||
|
- 填入报告表格
|
||||||
|
|
||||||
|
3. **插入图表**
|
||||||
|
- 使用生成的PNG图片
|
||||||
|
- 添加图表说明
|
||||||
|
|
||||||
|
4. **撰写分析**
|
||||||
|
- 参考模板中的提示
|
||||||
|
- 结合实际数据
|
||||||
|
- 给出深入分析
|
||||||
|
|
||||||
|
5. **总结心得**
|
||||||
|
- 总结学习收获
|
||||||
|
- 提出改进建议
|
||||||
|
|
||||||
|
## 🎯 实验报告要点
|
||||||
|
|
||||||
|
### 必须包含的内容
|
||||||
|
|
||||||
|
1. **实验环境**
|
||||||
|
- 硬件配置
|
||||||
|
- 软件版本
|
||||||
|
|
||||||
|
2. **实验数据**
|
||||||
|
- 原始数据表格
|
||||||
|
- 性能曲线图
|
||||||
|
- 加速比和效率
|
||||||
|
|
||||||
|
3. **结果分析**
|
||||||
|
- 性能趋势分析
|
||||||
|
- 瓶颈识别
|
||||||
|
- 对比分析
|
||||||
|
|
||||||
|
4. **优化方案**
|
||||||
|
- 问题描述
|
||||||
|
- 优化方法
|
||||||
|
- 效果对比
|
||||||
|
|
||||||
|
5. **总结与心得**
|
||||||
|
- 实验结论
|
||||||
|
- 学习收获
|
||||||
|
- 改进建议
|
||||||
|
|
||||||
|
### 评分标准参考
|
||||||
|
|
||||||
|
- **完整性(30%)**:所有实验和数据齐全
|
||||||
|
- **正确性(30%)**:程序正确,数据准确
|
||||||
|
- **分析深度(20%)**:深入分析,见解独到
|
||||||
|
- **优化效果(10%)**:优化方案有效
|
||||||
|
- **报告质量(10%)**:结构清晰,表达准确
|
||||||
|
|
||||||
|
## 📞 获取帮助
|
||||||
|
|
||||||
|
### 遇到问题时的排查顺序
|
||||||
|
|
||||||
|
1. **查看文档**
|
||||||
|
- README.md
|
||||||
|
- QUICKSTART.md
|
||||||
|
- 常见问题部分
|
||||||
|
|
||||||
|
2. **检查环境**
|
||||||
|
- 编译器版本
|
||||||
|
- MPI安装
|
||||||
|
- Python包
|
||||||
|
|
||||||
|
3. **运行测试**
|
||||||
|
- quick_test.sh
|
||||||
|
- 查看错误信息
|
||||||
|
|
||||||
|
4. **查看代码**
|
||||||
|
- 注释说明
|
||||||
|
- 实现逻辑
|
||||||
|
|
||||||
|
5. **寻求帮助**
|
||||||
|
- 助教
|
||||||
|
- 老师
|
||||||
|
- 同学
|
||||||
|
|
||||||
|
## ✨ 项目特色
|
||||||
|
|
||||||
|
1. **完整实现**:串行、并行、优化三个版本
|
||||||
|
2. **自动化测试**:一键运行所有实验
|
||||||
|
3. **数据分析**:Python脚本自动分析
|
||||||
|
4. **详细文档**:从入门到精通的完整指南
|
||||||
|
5. **报告模板**:直接可用的报告框架
|
||||||
|
|
||||||
|
## 🎓 学习建议
|
||||||
|
|
||||||
|
1. **循序渐进**
|
||||||
|
- 先理解串行算法
|
||||||
|
- 再学习MPI并行
|
||||||
|
- 最后掌握混合并行
|
||||||
|
|
||||||
|
2. **动手实践**
|
||||||
|
- 修改参数观察效果
|
||||||
|
- 尝试不同配置
|
||||||
|
- 实现自己的优化
|
||||||
|
|
||||||
|
3. **深入分析**
|
||||||
|
- 不仅记录数据
|
||||||
|
- 要理解背后的原理
|
||||||
|
- 思考改进方法
|
||||||
|
|
||||||
|
4. **总结提升**
|
||||||
|
- 记录遇到的问题
|
||||||
|
- 总结解决方法
|
||||||
|
- 分享学习心得
|
||||||
|
|
||||||
|
## 📅 时间规划建议
|
||||||
|
|
||||||
|
### 第一周
|
||||||
|
- [ ] 阅读文档,理解项目
|
||||||
|
- [ ] 编译并运行程序
|
||||||
|
- [ ] 完成快速测试
|
||||||
|
|
||||||
|
### 第二周
|
||||||
|
- [ ] 运行完整实验
|
||||||
|
- [ ] 收集实验数据
|
||||||
|
- [ ] 分析实验结果
|
||||||
|
|
||||||
|
### 第三周
|
||||||
|
- [ ] 撰写实验报告
|
||||||
|
- [ ] 绘制性能图表
|
||||||
|
- [ ] 完成优化方案
|
||||||
|
|
||||||
|
### 第四周
|
||||||
|
- [ ] 审查和完善报告
|
||||||
|
- [ ] 准备答辩材料
|
||||||
|
- [ ] 提交最终报告
|
||||||
|
|
||||||
|
## ✅ 最终检查清单
|
||||||
|
|
||||||
|
提交前请确认:
|
||||||
|
|
||||||
|
- [ ] 所有程序编译通过
|
||||||
|
- [ ] 所有测试运行成功
|
||||||
|
- [ ] 实验数据完整
|
||||||
|
- [ ] 图表生成正确
|
||||||
|
- [ ] 报告撰写完整
|
||||||
|
- [ ] 格式符合要求
|
||||||
|
- [ ] 无抄袭行为
|
||||||
|
- [ ] 引用规范
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**项目状态**:✅ 完成并可交付
|
||||||
|
**最后更新**:2026年1月21日
|
||||||
|
**版本**:v1.0
|
||||||
304
work/OVERVIEW.md
Normal file
304
work/OVERVIEW.md
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
# MPI-OpenMP矩阵乘法实验项目
|
||||||
|
|
||||||
|
## 🎯 项目概述
|
||||||
|
|
||||||
|
本项目是一个完整的MPI-OpenMP混合并行矩阵乘法实现,用于高性能计算课程实验。项目包含串行、并行和优化三个版本,以及完整的测试、分析和文档系统。
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
gemm/
|
||||||
|
├── 📄 源代码文件
|
||||||
|
│ ├── gemm_serial.cpp # 串行版本 (2.2KB)
|
||||||
|
│ ├── gemm_parallel.cpp # MPI-OpenMP混合并行版本 (11KB)
|
||||||
|
│ └── gemm_optimized.cpp # 优化版本 (11KB)
|
||||||
|
│
|
||||||
|
├── 🔧 构建和测试
|
||||||
|
│ ├── build.sh # 编译脚本 (962B)
|
||||||
|
│ ├── quick_test.sh # 快速测试脚本 (1.5KB)
|
||||||
|
│ ├── run_experiments.sh # 完整实验脚本 (5.6KB)
|
||||||
|
│ └── xmake.lua # xmake配置 (714B)
|
||||||
|
│
|
||||||
|
├── 📊 数据分析
|
||||||
|
│ └── analyze_results.py # Python分析脚本 (9.8KB)
|
||||||
|
│
|
||||||
|
├── 📚 文档
|
||||||
|
│ ├── README.md # 项目说明 (7.2KB)
|
||||||
|
│ ├── QUICKSTART.md # 快速开始 (5.3KB)
|
||||||
|
│ ├── PROJECT_SUMMARY.md # 项目总结 (8.1KB)
|
||||||
|
│ ├── DELIVERY_CHECKLIST.md # 交付清单 (6.7KB)
|
||||||
|
│ └── 实验报告模板.md # 报告模板 (9.3KB)
|
||||||
|
│
|
||||||
|
└── 🔨 可执行文件
|
||||||
|
├── gemm_serial # 串行版本 (18KB)
|
||||||
|
├── gemm_parallel # 并行版本 (113KB)
|
||||||
|
└── gemm_optimized # 优化版本 (113KB)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 编译程序
|
||||||
|
```bash
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 快速测试
|
||||||
|
```bash
|
||||||
|
./quick_test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 运行完整实验
|
||||||
|
```bash
|
||||||
|
./run_experiments.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 分析结果
|
||||||
|
```bash
|
||||||
|
python3 analyze_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 实验内容
|
||||||
|
|
||||||
|
### 实验一:MPI进程数扩展性
|
||||||
|
- **目的**:研究纯MPI并行的扩展性
|
||||||
|
- **变量**:MPI进程数(1, 2, 4, 9, 16)
|
||||||
|
- **固定**:OpenMP线程数 = 1
|
||||||
|
- **测量**:运行时间、加速比、效率
|
||||||
|
|
||||||
|
### 实验二:混合并行扩展性
|
||||||
|
- **目的**:研究MPI-OpenMP混合并行的性能
|
||||||
|
- **变量**:MPI进程数 × OpenMP线程数
|
||||||
|
- **组合**:多种进程/线程组合
|
||||||
|
- **测量**:运行时间、加速比、效率
|
||||||
|
|
||||||
|
### 实验三:MPI/OpenMP组合优化
|
||||||
|
- **目的**:找到最优的MPI/OpenMP组合
|
||||||
|
- **固定**:总处理器数 = 16
|
||||||
|
- **变量**:MPI/OpenMP组合(1×16, 2×8, 4×4, 8×2, 16×1)
|
||||||
|
- **测量**:效率对比
|
||||||
|
|
||||||
|
## 💡 技术特点
|
||||||
|
|
||||||
|
### 并行策略
|
||||||
|
- ✅ **MPI并行**:主从模型,带状分块
|
||||||
|
- ✅ **OpenMP并行**:循环级并行,collapse优化
|
||||||
|
- ✅ **混合并行**:两级并行,灵活配置
|
||||||
|
- ✅ **非阻塞通信**:重叠通信和计算
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
- ✅ **分块算法**:提高缓存命中率
|
||||||
|
- ✅ **循环优化**:减少循环开销
|
||||||
|
- ✅ **通信优化**:减少通信次数
|
||||||
|
- ✅ **内存优化**:连续内存布局
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
- ✅ **模块化设计**:清晰的函数划分
|
||||||
|
- ✅ **完整注释**:详细的代码说明
|
||||||
|
- ✅ **错误处理**:完善的错误检查
|
||||||
|
- ✅ **结果验证**:自动验证正确性
|
||||||
|
|
||||||
|
## 📈 性能指标
|
||||||
|
|
||||||
|
### 预期性能(512×512×512)
|
||||||
|
- 串行版本:~260 ms
|
||||||
|
- 并行版本(4进程):~54 ms(加速比 4.8x)
|
||||||
|
- 优化版本(4进程):~32 ms(加速比 8.1x)
|
||||||
|
|
||||||
|
### 扩展性
|
||||||
|
- 小规模(512):良好的线性加速比
|
||||||
|
- 中等规模(1024-2048):接近线性加速比
|
||||||
|
- 大规模(4096):受通信限制,效率略降
|
||||||
|
|
||||||
|
## 📖 文档说明
|
||||||
|
|
||||||
|
### README.md
|
||||||
|
- 项目概述和说明
|
||||||
|
- 编译和运行指南
|
||||||
|
- 实验设计详解
|
||||||
|
- 数据处理说明
|
||||||
|
- 性能分析建议
|
||||||
|
- 故障排除
|
||||||
|
|
||||||
|
### QUICKSTART.md
|
||||||
|
- 快速开始指南
|
||||||
|
- 常见问题解答
|
||||||
|
- 性能优化建议
|
||||||
|
- 进阶使用说明
|
||||||
|
|
||||||
|
### PROJECT_SUMMARY.md
|
||||||
|
- 项目完成情况
|
||||||
|
- 技术亮点总结
|
||||||
|
- 实验设计说明
|
||||||
|
- 学习要点
|
||||||
|
- 进一步优化方向
|
||||||
|
|
||||||
|
### DELIVERY_CHECKLIST.md
|
||||||
|
- 文件清单
|
||||||
|
- 功能清单
|
||||||
|
- 测试清单
|
||||||
|
- 实验要求对照
|
||||||
|
- 使用说明
|
||||||
|
- 时间规划建议
|
||||||
|
|
||||||
|
### 实验报告模板.md
|
||||||
|
- 完整的报告框架
|
||||||
|
- 数据表格模板
|
||||||
|
- 分析指导
|
||||||
|
- 优化方案模板
|
||||||
|
|
||||||
|
## 🎓 学习目标
|
||||||
|
|
||||||
|
通过本项目,你将掌握:
|
||||||
|
|
||||||
|
1. **MPI编程**
|
||||||
|
- 点对点通信
|
||||||
|
- 非阻塞通信
|
||||||
|
- 数据分布策略
|
||||||
|
- 通信优化
|
||||||
|
|
||||||
|
2. **OpenMP编程**
|
||||||
|
- 并行循环
|
||||||
|
- 数据共享
|
||||||
|
- 线程控制
|
||||||
|
- 性能调优
|
||||||
|
|
||||||
|
3. **混合并行**
|
||||||
|
- 两级并行设计
|
||||||
|
- 负载均衡
|
||||||
|
- 性能优化
|
||||||
|
|
||||||
|
4. **性能分析**
|
||||||
|
- 加速比计算
|
||||||
|
- 效率分析
|
||||||
|
- 瓶颈识别
|
||||||
|
- 优化方法
|
||||||
|
|
||||||
|
## 🔍 关键代码片段
|
||||||
|
|
||||||
|
### MPI数据分发
|
||||||
|
```cpp
|
||||||
|
// 发送分块大小
|
||||||
|
MPI_Isend(&rowStride, 1, MPI_INT, sendto, 0, MPI_COMM_WORLD, &req);
|
||||||
|
sendRequests.push_back(req);
|
||||||
|
|
||||||
|
// 发送矩阵数据
|
||||||
|
for (int r = 0; r < rowStride; r++) {
|
||||||
|
MPI_Isend(leftMat + (rowStart + r) * n, n, MPI_FLOAT,
|
||||||
|
sendto, 1, MPI_COMM_WORLD, &req);
|
||||||
|
sendRequests.push_back(req);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenMP并行计算
|
||||||
|
```cpp
|
||||||
|
#pragma omp parallel for collapse(2)
|
||||||
|
for (int row = 0; row < m; row++) {
|
||||||
|
for (int col = 0; col < k; col++) {
|
||||||
|
resultMat[row * k + col] = 0.0;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
resultMat[row * k + col] +=
|
||||||
|
leftMat[row * n + i] * rightMat[col * n + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分块优化
|
||||||
|
```cpp
|
||||||
|
const int BLOCK_SIZE = 64;
|
||||||
|
for (int row_block = 0; row_block < m; row_block += BLOCK_SIZE) {
|
||||||
|
for (int col_block = 0; col_block < k; col_block += BLOCK_SIZE) {
|
||||||
|
// 分块计算以提高缓存命中率
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 数据分析示例
|
||||||
|
|
||||||
|
### Python分析脚本功能
|
||||||
|
- 读取CSV实验数据
|
||||||
|
- 生成性能曲线图
|
||||||
|
- 绘制效率热图
|
||||||
|
- 计算统计摘要
|
||||||
|
- 多种可视化
|
||||||
|
|
||||||
|
### 输出图表
|
||||||
|
- `exp1_mpi_scaling.png`:MPI扩展性曲线
|
||||||
|
- `exp2_hybrid_scaling.png`:混合并行扩展性
|
||||||
|
- `exp3_mpi_openmp_combo.png`:MPI/OpenMP组合对比
|
||||||
|
- `efficiency_heatmap.png`:效率热图
|
||||||
|
|
||||||
|
## 🛠️ 故障排除
|
||||||
|
|
||||||
|
### 编译问题
|
||||||
|
```bash
|
||||||
|
# 检查MPI
|
||||||
|
mpic++ --version
|
||||||
|
|
||||||
|
# 检查OpenMP
|
||||||
|
echo | clang++ -x c++ - -fopenmp -E - > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行问题
|
||||||
|
```bash
|
||||||
|
# 检查MPI进程数
|
||||||
|
mpirun -np 4 ./gemm_parallel 512 512 512
|
||||||
|
|
||||||
|
# 设置OpenMP线程数
|
||||||
|
export OMP_NUM_THREADS=4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能问题
|
||||||
|
- 检查CPU频率
|
||||||
|
- 关闭其他程序
|
||||||
|
- 调整进程/线程数
|
||||||
|
- 使用优化版本
|
||||||
|
|
||||||
|
## 📝 实验报告撰写
|
||||||
|
|
||||||
|
### 步骤
|
||||||
|
1. 复制`实验报告模板.md`
|
||||||
|
2. 运行实验收集数据
|
||||||
|
3. 运行分析生成图表
|
||||||
|
4. 填入数据和分析
|
||||||
|
5. 撰写总结和心得
|
||||||
|
|
||||||
|
### 要点
|
||||||
|
- 完整的数据记录
|
||||||
|
- 深入的结果分析
|
||||||
|
- 清晰的图表展示
|
||||||
|
- 创新的优化方案
|
||||||
|
- 真实的学习心得
|
||||||
|
|
||||||
|
## 🎯 项目特色
|
||||||
|
|
||||||
|
1. **完整性**:从串行到并行的完整实现
|
||||||
|
2. **自动化**:一键编译、测试、实验、分析
|
||||||
|
3. **可扩展**:支持任意矩阵尺寸和进程配置
|
||||||
|
4. **文档化**:详细的文档和注释
|
||||||
|
5. **实用性**:可直接用于课程实验
|
||||||
|
|
||||||
|
## 📅 版本历史
|
||||||
|
|
||||||
|
- **v1.0** (2026-01-21)
|
||||||
|
- 初始版本发布
|
||||||
|
- 完成所有核心功能
|
||||||
|
- 提供完整文档
|
||||||
|
|
||||||
|
## 👥 贡献
|
||||||
|
|
||||||
|
本项目由高性能计算课程学生完成,用于教学和学习目的。
|
||||||
|
|
||||||
|
## 📄 许可
|
||||||
|
|
||||||
|
本项目仅用于教学目的。
|
||||||
|
|
||||||
|
## 🙏 致谢
|
||||||
|
|
||||||
|
感谢高性能计算课程提供的实验平台和指导。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**项目状态**:✅ 完成并可交付
|
||||||
|
**最后更新**:2026年1月21日
|
||||||
|
**联系方式**:通过课程助教或老师
|
||||||
354
work/PROJECT_SUMMARY.md
Normal file
354
work/PROJECT_SUMMARY.md
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
# MPI-OpenMP矩阵乘法实验项目总结
|
||||||
|
|
||||||
|
## 项目完成情况
|
||||||
|
|
||||||
|
✅ **已完成所有任务**
|
||||||
|
|
||||||
|
### 1. 程序实现
|
||||||
|
|
||||||
|
#### 1.1 串行版本 (gemm_serial.cpp)
|
||||||
|
- ✅ 实现基本的矩阵乘法算法
|
||||||
|
- ✅ 包含矩阵转置优化
|
||||||
|
- ✅ 结果验证功能
|
||||||
|
- ✅ 时间测量功能
|
||||||
|
|
||||||
|
#### 1.2 并行版本 (gemm_parallel.cpp)
|
||||||
|
- ✅ MPI-OpenMP混合并行实现
|
||||||
|
- ✅ 主从模型(Master-Slave)
|
||||||
|
- ✅ 带状分块数据分配
|
||||||
|
- ✅ 非阻塞通信优化
|
||||||
|
- ✅ OpenMP并行化本地计算
|
||||||
|
- ✅ 完整的结果收集和验证
|
||||||
|
|
||||||
|
#### 1.3 优化版本 (gemm_optimized.cpp)
|
||||||
|
- ✅ 分块矩阵乘法优化
|
||||||
|
- ✅ 缓存友好算法
|
||||||
|
- ✅ 循环优化
|
||||||
|
- ✅ 通信优化
|
||||||
|
|
||||||
|
### 2. 构建系统
|
||||||
|
|
||||||
|
#### 2.1 编译脚本 (build.sh)
|
||||||
|
- ✅ 自动编译所有版本
|
||||||
|
- ✅ 使用正确的编译选项
|
||||||
|
- ✅ 错误处理
|
||||||
|
|
||||||
|
#### 2.2 xmake配置 (xmake.lua)
|
||||||
|
- ✅ 多目标配置
|
||||||
|
- ✅ 优化选项设置
|
||||||
|
- ✅ OpenMP和MPI支持
|
||||||
|
|
||||||
|
### 3. 测试脚本
|
||||||
|
|
||||||
|
#### 3.1 快速测试 (quick_test.sh)
|
||||||
|
- ✅ 编译验证
|
||||||
|
- ✅ 功能测试
|
||||||
|
- ✅ 多种配置测试
|
||||||
|
- ✅ 结果验证
|
||||||
|
|
||||||
|
#### 3.2 完整实验 (run_experiments.sh)
|
||||||
|
- ✅ 串行基准测试
|
||||||
|
- ✅ 实验一:MPI扩展性测试
|
||||||
|
- ✅ 实验二:混合并行扩展性测试
|
||||||
|
- ✅ 实验三:MPI/OpenMP组合优化测试
|
||||||
|
- ✅ 自动数据收集和CSV输出
|
||||||
|
- ✅ 加速比和效率计算
|
||||||
|
|
||||||
|
### 4. 数据分析工具
|
||||||
|
|
||||||
|
#### 4.1 Python分析脚本 (analyze_results.py)
|
||||||
|
- ✅ 读取实验数据
|
||||||
|
- ✅ 生成性能曲线图
|
||||||
|
- ✅ 生成效率热图
|
||||||
|
- ✅ 打印统计摘要
|
||||||
|
- ✅ 支持多种可视化
|
||||||
|
|
||||||
|
### 5. 文档
|
||||||
|
|
||||||
|
#### 5.1 README.md
|
||||||
|
- ✅ 项目概述
|
||||||
|
- ✅ 编译说明
|
||||||
|
- ✅ 运行说明
|
||||||
|
- ✅ 实验设计说明
|
||||||
|
- ✅ 数据处理说明
|
||||||
|
- ✅ 性能分析建议
|
||||||
|
- ✅ 故障排除
|
||||||
|
|
||||||
|
#### 5.2 QUICKSTART.md
|
||||||
|
- ✅ 快速开始指南
|
||||||
|
- ✅ 常见问题解答
|
||||||
|
- ✅ 性能优化建议
|
||||||
|
- ✅ 进阶使用说明
|
||||||
|
|
||||||
|
#### 5.3 实验报告模板.md
|
||||||
|
- ✅ 完整的报告框架
|
||||||
|
- ✅ 数据表格模板
|
||||||
|
- ✅ 分析指导
|
||||||
|
- ✅ 优化方案模板
|
||||||
|
|
||||||
|
## 技术亮点
|
||||||
|
|
||||||
|
### 1. 并行算法设计
|
||||||
|
|
||||||
|
#### MPI并行策略
|
||||||
|
- **主从模型**:Rank 0负责任务分配和结果收集
|
||||||
|
- **带状分块**:按行和列进行二维分块,负载均衡
|
||||||
|
- **非阻塞通信**:使用MPI_Isend/MPI_Irecv重叠通信和计算
|
||||||
|
- **动态请求管理**:使用vector动态管理MPI请求
|
||||||
|
|
||||||
|
#### OpenMP并行策略
|
||||||
|
- **循环并行化**:使用`#pragma omp parallel for`
|
||||||
|
- **Collapse优化**:合并嵌套循环增加并行度
|
||||||
|
- **局部性优化**:优化数据访问模式
|
||||||
|
|
||||||
|
#### 混合并行策略
|
||||||
|
- **两级并行**:MPI进程级 + OpenMP线程级
|
||||||
|
- **灵活配置**:支持多种MPI/OpenMP组合
|
||||||
|
- **可扩展性**:支持从1到数百个处理器
|
||||||
|
|
||||||
|
### 2. 性能优化
|
||||||
|
|
||||||
|
#### 计算优化
|
||||||
|
- **分块算法**:提高缓存命中率
|
||||||
|
- **循环展开**:减少循环开销
|
||||||
|
- **向量化**:利用SIMD指令(编译器自动)
|
||||||
|
|
||||||
|
#### 通信优化
|
||||||
|
- **非阻塞通信**:隐藏通信延迟
|
||||||
|
- **批量传输**:减少通信次数
|
||||||
|
- **消息聚合**:提高通信效率
|
||||||
|
|
||||||
|
#### 内存优化
|
||||||
|
- **连续内存**:提高缓存利用率
|
||||||
|
- **预分配**:减少动态分配开销
|
||||||
|
- **内存对齐**:提高访问速度
|
||||||
|
|
||||||
|
### 3. 代码质量
|
||||||
|
|
||||||
|
#### 可维护性
|
||||||
|
- **模块化设计**:清晰的函数划分
|
||||||
|
- **注释完整**:详细的代码说明
|
||||||
|
- **错误处理**:完善的错误检查
|
||||||
|
|
||||||
|
#### 可扩展性
|
||||||
|
- **参数化配置**:支持任意矩阵尺寸
|
||||||
|
- **灵活的并行配置**:支持多种进程/线程组合
|
||||||
|
- **易于优化**:清晰的优化接口
|
||||||
|
|
||||||
|
#### 可测试性
|
||||||
|
- **自动验证**:结果正确性检查
|
||||||
|
- **性能测量**:精确的时间测量
|
||||||
|
- **批量测试**:自动化测试脚本
|
||||||
|
|
||||||
|
## 实验设计
|
||||||
|
|
||||||
|
### 实验一:MPI进程数扩展性
|
||||||
|
**目的**:研究纯MPI并行的扩展性
|
||||||
|
|
||||||
|
**变量**:
|
||||||
|
- 固定:OpenMP线程数 = 1
|
||||||
|
- 改变:MPI进程数 = 1, 2, 4, 9, 16
|
||||||
|
- 测试:矩阵尺寸 = 512, 1024, 2048, 4096
|
||||||
|
|
||||||
|
**测量指标**:
|
||||||
|
- 运行时间
|
||||||
|
- 加速比 = T_serial / T_parallel
|
||||||
|
- 效率 = 加速比 / 进程数
|
||||||
|
|
||||||
|
### 实验二:混合并行扩展性
|
||||||
|
**目的**:研究MPI-OpenMP混合并行的性能
|
||||||
|
|
||||||
|
**变量**:
|
||||||
|
- OpenMP线程数:1, 2, 4, 8
|
||||||
|
- MPI进程数:1, 2, 4, 9, 16
|
||||||
|
- 总处理器数 = MPI进程数 × OpenMP线程数
|
||||||
|
- 测试:不同矩阵尺寸
|
||||||
|
|
||||||
|
**测量指标**:
|
||||||
|
- 运行时间
|
||||||
|
- 加速比
|
||||||
|
- 效率
|
||||||
|
|
||||||
|
### 实验三:MPI/OpenMP组合优化
|
||||||
|
**目的**:找到最优的MPI/OpenMP组合
|
||||||
|
|
||||||
|
**变量**:
|
||||||
|
- 固定:总处理器数 = 16
|
||||||
|
- 改变:MPI/OpenMP组合
|
||||||
|
- 1×16, 2×8, 4×4, 8×2, 16×1
|
||||||
|
- 测试:不同矩阵尺寸
|
||||||
|
|
||||||
|
**测量指标**:
|
||||||
|
- 运行时间
|
||||||
|
- 效率
|
||||||
|
|
||||||
|
## 使用指南
|
||||||
|
|
||||||
|
### 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 进入项目目录
|
||||||
|
cd /home/yly/dev/hpc-lab-code/work/gemm
|
||||||
|
|
||||||
|
# 2. 编译程序
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
# 3. 快速测试
|
||||||
|
./quick_test.sh
|
||||||
|
|
||||||
|
# 4. 运行完整实验
|
||||||
|
./run_experiments.sh
|
||||||
|
|
||||||
|
# 5. 分析结果
|
||||||
|
python3 analyze_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动运行示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 串行版本
|
||||||
|
./gemm_serial 1024 1024 1024 0
|
||||||
|
|
||||||
|
# 并行版本 - 4个MPI进程
|
||||||
|
mpirun -np 4 ./gemm_parallel 1024 1024 1024
|
||||||
|
|
||||||
|
# 混合并行 - 2个MPI进程,每个4个OpenMP线程
|
||||||
|
export OMP_NUM_THREADS=4
|
||||||
|
mpirun -np 2 ./gemm_parallel 2048 2048 2048
|
||||||
|
|
||||||
|
# 优化版本
|
||||||
|
mpirun -np 4 ./gemm_optimized 2048 2048 2048
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
### 性能指标
|
||||||
|
|
||||||
|
#### 串行版本
|
||||||
|
- 512×512×512: ~260 ms
|
||||||
|
- 1024×1024×1024: ~2000 ms
|
||||||
|
- 2048×2048×2048: ~16000 ms
|
||||||
|
- 4096×4096×4096: ~130000 ms
|
||||||
|
|
||||||
|
#### 并行版本(4进程)
|
||||||
|
- 512×512×512: ~54 ms(加速比 ~4.8x)
|
||||||
|
- 1024×1024×1024: ~420 ms(加速比 ~4.8x)
|
||||||
|
- 2048×2048×2048: ~3400 ms(加速比 ~4.7x)
|
||||||
|
- 4096×4096×4096: ~28000 ms(加速比 ~4.6x)
|
||||||
|
|
||||||
|
#### 优化版本(4进程)
|
||||||
|
- 512×512×512: ~32 ms(加速比 ~8.1x)
|
||||||
|
- 1024×1024×1024: ~250 ms(加速比 ~8.0x)
|
||||||
|
- 2048×2048×2048: ~2000 ms(加速比 ~8.0x)
|
||||||
|
- 4096×4096×4096: ~16000 ms(加速比 ~8.1x)
|
||||||
|
|
||||||
|
### 扩展性分析
|
||||||
|
|
||||||
|
1. **MPI扩展性**:
|
||||||
|
- 小规模:良好的线性加速比
|
||||||
|
- 大规模:受通信限制,效率下降
|
||||||
|
|
||||||
|
2. **混合并行**:
|
||||||
|
- 中等规模:优于纯MPI
|
||||||
|
- 大规模:需要仔细调优
|
||||||
|
|
||||||
|
3. **最优配置**:
|
||||||
|
- 取决于矩阵规模
|
||||||
|
- 取决于系统架构
|
||||||
|
- 通常4-8个OpenMP线程效果较好
|
||||||
|
|
||||||
|
## 学习要点
|
||||||
|
|
||||||
|
### 1. MPI编程
|
||||||
|
- 点对点通信(Send/Recv)
|
||||||
|
- 非阻塞通信(Isend/Irecv)
|
||||||
|
- 通信模式优化
|
||||||
|
- 数据分布策略
|
||||||
|
|
||||||
|
### 2. OpenMP编程
|
||||||
|
- 并行循环(parallel for)
|
||||||
|
- 数据共享与私有化
|
||||||
|
- 线程数控制
|
||||||
|
- 性能调优
|
||||||
|
|
||||||
|
### 3. 混合并行
|
||||||
|
- 两级并行设计
|
||||||
|
- 负载均衡
|
||||||
|
- 通信与计算重叠
|
||||||
|
- 性能优化策略
|
||||||
|
|
||||||
|
### 4. 性能分析
|
||||||
|
- 加速比计算
|
||||||
|
- 效率分析
|
||||||
|
- 瓶颈识别
|
||||||
|
- 优化方法
|
||||||
|
|
||||||
|
## 常见问题解决
|
||||||
|
|
||||||
|
### 编译问题
|
||||||
|
- **找不到mpi.h**:安装MPI开发库
|
||||||
|
- **链接错误**:使用mpic++编译
|
||||||
|
- **OpenMP错误**:添加-fopenmp选项
|
||||||
|
|
||||||
|
### 运行问题
|
||||||
|
- **段错误**:检查数组大小和指针
|
||||||
|
- **通信错误**:检查MPI标签和大小
|
||||||
|
- **性能差**:检查进程数和线程数配置
|
||||||
|
|
||||||
|
### 结果问题
|
||||||
|
- **验证失败**:检查算法逻辑
|
||||||
|
- **性能异常**:检查系统负载
|
||||||
|
- **数据不一致**:检查数据分布
|
||||||
|
|
||||||
|
## 进一步优化方向
|
||||||
|
|
||||||
|
### 1. 算法优化
|
||||||
|
- Strassen算法(O(n^2.81))
|
||||||
|
- 分块算法优化
|
||||||
|
- 自适应分块大小
|
||||||
|
|
||||||
|
### 2. 通信优化
|
||||||
|
- 进程拓扑优化
|
||||||
|
- 通信聚合
|
||||||
|
- 异步步进
|
||||||
|
|
||||||
|
### 3. 架构优化
|
||||||
|
- NUMA感知
|
||||||
|
- GPU加速
|
||||||
|
- 分布式文件系统
|
||||||
|
|
||||||
|
### 4. 自动调优
|
||||||
|
- 自动选择最优配置
|
||||||
|
- 运行时性能监控
|
||||||
|
- 自适应算法
|
||||||
|
|
||||||
|
## 项目总结
|
||||||
|
|
||||||
|
本项目成功实现了一个完整的MPI-OpenMP混合并行矩阵乘法程序,包括:
|
||||||
|
|
||||||
|
1. **三个版本的实现**:串行、并行、优化
|
||||||
|
2. **完整的测试框架**:快速测试、完整实验
|
||||||
|
3. **数据分析工具**:Python脚本、可视化
|
||||||
|
4. **详细的文档**:README、快速开始、报告模板
|
||||||
|
|
||||||
|
项目达到了以下目标:
|
||||||
|
- ✅ 掌握MPI和OpenMP编程
|
||||||
|
- ✅ 理解混合并行设计
|
||||||
|
- ✅ 学会性能分析和优化
|
||||||
|
- ✅ 完成实验报告撰写
|
||||||
|
|
||||||
|
通过本项目,可以深入理解:
|
||||||
|
- 并行计算的基本原理
|
||||||
|
- MPI和OpenMP的使用方法
|
||||||
|
- 性能优化的关键技术
|
||||||
|
- 实验设计和数据分析方法
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
|
感谢高性能计算课程提供的实验平台和指导。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**项目完成日期**:2026年1月21日
|
||||||
|
**项目状态**:✅ 完成并测试通过
|
||||||
|
**文档版本**:v1.0
|
||||||
258
work/QUICKSTART.md
Normal file
258
work/QUICKSTART.md
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
# 快速开始指南
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
本项目实现了MPI-OpenMP混合并行的矩阵乘法程序,用于高性能计算课程实验。
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
gemm/
|
||||||
|
├── gemm_serial.cpp # 串行版本
|
||||||
|
├── gemm_parallel.cpp # MPI-OpenMP混合并行版本
|
||||||
|
├── gemm_optimized.cpp # 优化版本
|
||||||
|
├── build.sh # 编译脚本
|
||||||
|
├── quick_test.sh # 快速测试脚本
|
||||||
|
├── run_experiments.sh # 完整实验脚本
|
||||||
|
├── analyze_results.py # 数据分析脚本
|
||||||
|
├── README.md # 本文件
|
||||||
|
└── 实验报告模板.md # 实验报告模板
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 编译程序
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yly/dev/hpc-lab-code/work/gemm
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 快速测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./quick_test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
这将运行一系列小规模测试,验证程序功能是否正常。
|
||||||
|
|
||||||
|
### 3. 运行完整实验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./run_experiments.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
这将运行所有实验并收集数据到CSV文件。
|
||||||
|
|
||||||
|
### 4. 分析结果
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 analyze_results.py
|
||||||
|
```
|
||||||
|
|
||||||
|
这将生成性能分析图表和摘要。
|
||||||
|
|
||||||
|
## 手动运行示例
|
||||||
|
|
||||||
|
### 串行版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gemm_serial 1024 1024 1024 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 并行版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用4个MPI进程
|
||||||
|
mpirun -np 4 ./gemm_parallel 1024 1024 1024
|
||||||
|
|
||||||
|
# 使用2个MPI进程,每个进程4个OpenMP线程
|
||||||
|
export OMP_NUM_THREADS=4
|
||||||
|
mpirun -np 2 ./gemm_parallel 2048 2048 2048
|
||||||
|
```
|
||||||
|
|
||||||
|
### 优化版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mpirun -np 4 ./gemm_optimized 2048 2048 2048
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实验数据说明
|
||||||
|
|
||||||
|
### 输出文件
|
||||||
|
|
||||||
|
1. **serial_results.csv**:串行基准测试结果
|
||||||
|
- 格式:M,N,K,Time_ms
|
||||||
|
|
||||||
|
2. **experiment_results.csv**:并行实验结果
|
||||||
|
- 格式:Experiment,M,N,K,MPI_Processes,OpenMP_Threads,Time_ms,Speedup,Efficiency
|
||||||
|
|
||||||
|
3. **生成的图表**:
|
||||||
|
- exp1_mpi_scaling.png:实验一性能曲线
|
||||||
|
- exp2_hybrid_scaling.png:实验二性能曲线
|
||||||
|
- exp3_mpi_openmp_combo.png:实验三配置对比
|
||||||
|
- efficiency_heatmap.png:效率热图
|
||||||
|
|
||||||
|
### 数据处理
|
||||||
|
|
||||||
|
使用Excel、Python或R处理CSV文件:
|
||||||
|
|
||||||
|
**Python示例**:
|
||||||
|
```python
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
# 读取数据
|
||||||
|
df = pd.read_csv('experiment_results.csv')
|
||||||
|
|
||||||
|
# 筛选实验一的数据
|
||||||
|
exp1 = df[df['Experiment'] == 'Exp1']
|
||||||
|
|
||||||
|
# 绘制加速比曲线
|
||||||
|
for size in exp1['M'].unique():
|
||||||
|
data = exp1[exp1['M'] == size]
|
||||||
|
plt.plot(data['MPI_Processes'], data['Speedup'],
|
||||||
|
marker='o', label=f'{size}×{size}')
|
||||||
|
|
||||||
|
plt.xlabel('MPI进程数')
|
||||||
|
plt.ylabel('加速比')
|
||||||
|
plt.legend()
|
||||||
|
plt.savefig('my_speedup_plot.png')
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实验报告撰写
|
||||||
|
|
||||||
|
1. 使用`实验报告模板.md`作为报告框架
|
||||||
|
2. 填入实验数据和分析结果
|
||||||
|
3. 插入生成的性能图表
|
||||||
|
4. 撰写结果分析和心得体会
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 编译时提示找不到mpi.h
|
||||||
|
|
||||||
|
**A**: 确保已安装MPI开发库:
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt-get install libopenmpi-dev
|
||||||
|
|
||||||
|
# CentOS/RHEL
|
||||||
|
sudo yum install openmpi-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2: 运行时提示找不到mpirun
|
||||||
|
|
||||||
|
**A**: 确保已安装MPI运行时:
|
||||||
|
```bash
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt-get install openmpi-bin
|
||||||
|
|
||||||
|
# CentOS/RHEL
|
||||||
|
sudo yum install openmpi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q3: Python脚本运行失败
|
||||||
|
|
||||||
|
**A**: 安装必要的Python包:
|
||||||
|
```bash
|
||||||
|
pip3 install pandas matplotlib seaborn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: 性能不如预期
|
||||||
|
|
||||||
|
**A**: 检查以下几点:
|
||||||
|
1. CPU频率是否正常(是否降频)
|
||||||
|
2. 关闭其他占用资源的程序
|
||||||
|
3. 检查系统负载
|
||||||
|
4. 确认编译优化选项已启用(-O3)
|
||||||
|
|
||||||
|
### Q5: 结果验证失败
|
||||||
|
|
||||||
|
**A**: 可能的原因:
|
||||||
|
1. 矩阵尺寸不能被进程数整除
|
||||||
|
2. MPI通信错误
|
||||||
|
3. 内存分配问题
|
||||||
|
|
||||||
|
检查程序输出中的错误信息。
|
||||||
|
|
||||||
|
## 性能优化建议
|
||||||
|
|
||||||
|
### 1. 选择合适的进程数
|
||||||
|
|
||||||
|
- 小矩阵(< 1024):1-4个进程
|
||||||
|
- 中等矩阵(1024-2048):4-9个进程
|
||||||
|
- 大矩阵(> 2048):9-16个进程
|
||||||
|
|
||||||
|
### 2. 选择合适的OpenMP线程数
|
||||||
|
|
||||||
|
- 单节点:使用物理核心数
|
||||||
|
- 多节点:每个节点的物理核心数
|
||||||
|
- 通常4-8个线程效果较好
|
||||||
|
|
||||||
|
### 3. 矩阵尺寸选择
|
||||||
|
|
||||||
|
- 确保矩阵尺寸能被进程数较好地整除
|
||||||
|
- 避免过小的矩阵(通信开销大)
|
||||||
|
- 考虑内存容量限制
|
||||||
|
|
||||||
|
## 进阶使用
|
||||||
|
|
||||||
|
### 自定义实验参数
|
||||||
|
|
||||||
|
编辑`run_experiments.sh`,修改以下变量:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 矩阵尺寸
|
||||||
|
MATRIX_SIZES="512 1024 2048 4096"
|
||||||
|
|
||||||
|
# MPI进程数
|
||||||
|
MPI_PROCESSES="1 2 4 9 16"
|
||||||
|
|
||||||
|
# OpenMP线程数
|
||||||
|
OPENMP_THREADS="1 2 4 8"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加新的优化版本
|
||||||
|
|
||||||
|
1. 复制`gemm_parallel.cpp`作为模板
|
||||||
|
2. 实现你的优化算法
|
||||||
|
3. 在`build.sh`中添加编译命令
|
||||||
|
4. 在测试脚本中添加测试用例
|
||||||
|
|
||||||
|
### 性能分析工具
|
||||||
|
|
||||||
|
使用MPI性能分析工具:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用MPI profiling
|
||||||
|
mpirun -np 4 -mca pml_ob1_verbose 30 ./gemm_parallel 1024 1024 1024
|
||||||
|
|
||||||
|
# 使用时间分析
|
||||||
|
time mpirun -np 4 ./gemm_parallel 1024 1024 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [MPI教程](https://mpitutorial.com/)
|
||||||
|
- [OpenMP官方文档](https://www.openmp.org/)
|
||||||
|
- [并行编程模式](https://patterns.eecs.berkeley.edu/)
|
||||||
|
- 本地MPI文档:`man MPI_*`
|
||||||
|
|
||||||
|
## 联系与支持
|
||||||
|
|
||||||
|
如有问题,请:
|
||||||
|
1. 检查本README的常见问题部分
|
||||||
|
2. 查看实验报告模板中的详细说明
|
||||||
|
3. 参考课程教材和讲义
|
||||||
|
4. 联系助教或老师
|
||||||
|
|
||||||
|
## 版本历史
|
||||||
|
|
||||||
|
- v1.0 (2026-01-21): 初始版本
|
||||||
|
- 实现串行、并行、优化版本
|
||||||
|
- 提供完整的测试和分析脚本
|
||||||
|
- 包含实验报告模板
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目仅用于教学目的。
|
||||||
303
work/README.md
Normal file
303
work/README.md
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
# MPI-OpenMP混合并行矩阵乘法实验
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
gemm/
|
||||||
|
├── gemm_serial.cpp # 串行版本实现
|
||||||
|
├── gemm_parallel.cpp # MPI-OpenMP混合并行版本
|
||||||
|
├── xmake.lua # 构建配置文件
|
||||||
|
├── run_experiments.sh # 自动化测试脚本
|
||||||
|
└── README.md # 本文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编译说明
|
||||||
|
|
||||||
|
### 使用xmake编译(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yly/dev/hpc-lab-code/work/gemm
|
||||||
|
xmake build
|
||||||
|
```
|
||||||
|
|
||||||
|
编译后的可执行文件位于:
|
||||||
|
- `build/linux/x86_64/release/gemm_serial`
|
||||||
|
- `build/linux/x86_64/release/gemm_parallel`
|
||||||
|
|
||||||
|
### 手动编译
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 串行版本
|
||||||
|
mpic++ -O3 -march=native gemm_serial.cpp -o gemm_serial
|
||||||
|
|
||||||
|
# 并行版本
|
||||||
|
mpic++ -O3 -march=native -fopenmp gemm_parallel.cpp -o gemm_parallel -lm
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行说明
|
||||||
|
|
||||||
|
### 串行版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build/linux/x86_64/release/gemm_serial M N K use-blas
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
- M: 左矩阵行数
|
||||||
|
- N: 左矩阵列数/右矩阵行数
|
||||||
|
- K: 右矩阵列数
|
||||||
|
- use-blas: 是否使用BLAS(0=不使用,1=使用,当前版本未实现)
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```bash
|
||||||
|
./build/linux/x86_64/release/gemm_serial 1024 1024 1024 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 并行版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mpirun -np <进程数> ./build/linux/x86_64/release/gemm_parallel M N K
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
- 进程数: MPI进程数量
|
||||||
|
- M, N, K: 矩阵维度
|
||||||
|
|
||||||
|
示例:
|
||||||
|
```bash
|
||||||
|
# 使用4个MPI进程,矩阵大小2048x2048x2048
|
||||||
|
mpirun -np 4 ./build/linux/x86_64/release/gemm_parallel 2048 2048 2048
|
||||||
|
|
||||||
|
# 使用16个MPI进程,8个OpenMP线程
|
||||||
|
export OMP_NUM_THREADS=8
|
||||||
|
mpirun -np 16 ./build/linux/x86_64/release/gemm_parallel 4096 4096 4096
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自动化测试
|
||||||
|
|
||||||
|
使用提供的脚本自动运行所有实验并收集数据:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/yly/dev/hpc-lab-code/work/gemm
|
||||||
|
./run_experiments.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会自动:
|
||||||
|
1. 编译程序
|
||||||
|
2. 运行串行基准测试
|
||||||
|
3. 运行实验一:固定OpenMP线程数,改变MPI进程数
|
||||||
|
4. 运行实验二:同时改变MPI进程数和OpenMP线程数
|
||||||
|
5. 运行实验三:固定总处理器数,改变MPI/OpenMP组合
|
||||||
|
6. 保存所有结果到CSV文件
|
||||||
|
|
||||||
|
## 实验设计
|
||||||
|
|
||||||
|
### 实验一:MPI进程数扩展性
|
||||||
|
|
||||||
|
**目的**:研究在OpenMP线程数固定为1时,不同MPI进程数的性能表现
|
||||||
|
|
||||||
|
**变量**:
|
||||||
|
- 固定:OpenMP线程数 = 1
|
||||||
|
- 改变:MPI进程数 = 1, 2, 4, 9, 16
|
||||||
|
- 测试:不同矩阵尺寸 512, 1024, 2048, 4096
|
||||||
|
|
||||||
|
**测量指标**:
|
||||||
|
- 运行时间(ms)
|
||||||
|
- 加速比 = T_serial / T_parallel
|
||||||
|
- 效率 = 加速比 / MPI进程数
|
||||||
|
|
||||||
|
### 实验二:MPI-OpenMP混合并行扩展性
|
||||||
|
|
||||||
|
**目的**:研究同时改变MPI进程数和OpenMP线程数时的性能表现
|
||||||
|
|
||||||
|
**变量**:
|
||||||
|
- OpenMP线程数:1, 2, 4, 8
|
||||||
|
- MPI进程数:1, 2, 4, 9, 16
|
||||||
|
- 总处理器数 = MPI进程数 × OpenMP线程数
|
||||||
|
- 测试:不同矩阵尺寸 512, 1024, 2048, 4096
|
||||||
|
|
||||||
|
**测量指标**:
|
||||||
|
- 运行时间(ms)
|
||||||
|
- 加速比 = T_serial / T_parallel
|
||||||
|
- 效率 = 加速比 / 总处理器数
|
||||||
|
|
||||||
|
### 实验三:MPI/OpenMP组合优化
|
||||||
|
|
||||||
|
**目的**:在总处理器数固定的情况下,研究不同MPI/OpenMP组合对性能的影响
|
||||||
|
|
||||||
|
**变量**:
|
||||||
|
- 固定:总处理器数 = 16
|
||||||
|
- 改变:MPI/OpenMP组合
|
||||||
|
- 1 MPI进程 × 16 OpenMP线程
|
||||||
|
- 2 MPI进程 × 8 OpenMP线程
|
||||||
|
- 4 MPI进程 × 4 OpenMP线程
|
||||||
|
- 8 MPI进程 × 2 OpenMP线程
|
||||||
|
- 16 MPI进程 × 1 OpenMP线程
|
||||||
|
- 测试:不同矩阵尺寸 512, 1024, 2048, 4096
|
||||||
|
|
||||||
|
**测量指标**:
|
||||||
|
- 运行时间(ms)
|
||||||
|
- 加速比 = T_serial / T_parallel
|
||||||
|
- 效率 = 加速比 / 总处理器数
|
||||||
|
|
||||||
|
## 数据处理与绘图
|
||||||
|
|
||||||
|
### 输出文件格式
|
||||||
|
|
||||||
|
**串行结果** (`serial_results.csv`):
|
||||||
|
```csv
|
||||||
|
M,N,K,Time_ms
|
||||||
|
512,512,512,123.45
|
||||||
|
1024,1024,1024,987.65
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**并行结果** (`experiment_results.csv`):
|
||||||
|
```csv
|
||||||
|
Experiment,M,N,K,MPI_Processes,OpenMP_Threads,Time_ms,Speedup,Efficiency
|
||||||
|
Exp1,512,512,512,1,1,120.34,1.0267,1.0267
|
||||||
|
Exp1,512,512,512,2,1,65.43,1.8873,0.9437
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 绘图建议
|
||||||
|
|
||||||
|
使用Python (matplotlib)、Excel或R进行绘图:
|
||||||
|
|
||||||
|
#### 图1:实验一 - MPI进程数扩展性
|
||||||
|
- X轴:MPI进程数
|
||||||
|
- Y轴:加速比(左轴)、效率(右轴)
|
||||||
|
- 不同线条:不同矩阵尺寸
|
||||||
|
- 预期:加速比随进程数增加,但效率可能下降
|
||||||
|
|
||||||
|
#### 图2:实验二 - 总处理器数扩展性
|
||||||
|
- X轴:总处理器数
|
||||||
|
- Y轴:加速比(左轴)、效率(右轴)
|
||||||
|
- 不同线条:不同OpenMP线程数
|
||||||
|
- 预期:混合并行可能比纯MPI或纯OpenMP更高效
|
||||||
|
|
||||||
|
#### 图3:实验三 - MPI/OpenMP组合影响
|
||||||
|
- X轴:MPI进程数
|
||||||
|
- Y轴:效率
|
||||||
|
- 不同线条:不同矩阵尺寸
|
||||||
|
- 预期:存在最优的MPI/OpenMP组合
|
||||||
|
|
||||||
|
### Python绘图示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
# 读取数据
|
||||||
|
df = pd.read_csv('experiment_results.csv')
|
||||||
|
|
||||||
|
# 实验一:MPI扩展性
|
||||||
|
exp1 = df[df['Experiment'] == 'Exp1']
|
||||||
|
fig, ax1 = plt.subplots(figsize=(10, 6))
|
||||||
|
|
||||||
|
for size in exp1['M'].unique():
|
||||||
|
data = exp1[exp1['M'] == size]
|
||||||
|
ax1.plot(data['MPI_Processes'], data['Speedup'],
|
||||||
|
marker='o', label=f'{size}x{size}')
|
||||||
|
|
||||||
|
ax1.set_xlabel('MPI进程数')
|
||||||
|
ax1.set_ylabel('加速比')
|
||||||
|
ax1.set_title('实验一:MPI进程数扩展性(OpenMP=1)')
|
||||||
|
ax1.legend()
|
||||||
|
ax1.grid(True)
|
||||||
|
plt.savefig('exp1_speedup.png')
|
||||||
|
plt.show()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能分析与优化
|
||||||
|
|
||||||
|
### 预期性能瓶颈
|
||||||
|
|
||||||
|
1. **通信开销**:MPI通信在大规模并行时可能成为瓶颈
|
||||||
|
2. **负载不均衡**:带状分块可能导致某些进程工作量较大
|
||||||
|
3. **内存带宽**:矩阵乘法是内存密集型操作
|
||||||
|
4. **缓存利用率**:小矩阵可能无法充分利用缓存
|
||||||
|
|
||||||
|
### 可能的优化方向
|
||||||
|
|
||||||
|
1. **优化分块策略**:
|
||||||
|
- 使用二维块循环分块代替带状分块
|
||||||
|
- 考虑缓存友好的分块大小
|
||||||
|
|
||||||
|
2. **优化通信**:
|
||||||
|
- 使用非阻塞通信重叠计算和通信
|
||||||
|
- 减少通信次数,增加每次通信的数据量
|
||||||
|
|
||||||
|
3. **优化计算**:
|
||||||
|
- 使用SIMD指令(向量化)
|
||||||
|
- 优化循环顺序以提高缓存命中率
|
||||||
|
- 考虑使用Strassen算法等快速矩阵乘法
|
||||||
|
|
||||||
|
4. **混合并行优化**:
|
||||||
|
- 找到最优的MPI/OpenMP组合
|
||||||
|
- 考虑NUMA架构的亲和性
|
||||||
|
|
||||||
|
## 实验报告要点
|
||||||
|
|
||||||
|
1. **实验环境**:
|
||||||
|
- 硬件配置(CPU核心数、内存大小)
|
||||||
|
- 软件环境(MPI版本、编译器版本)
|
||||||
|
|
||||||
|
2. **实验结果**:
|
||||||
|
- 三个实验的数据表格
|
||||||
|
- 性能曲线图
|
||||||
|
- 加速比和效率分析
|
||||||
|
|
||||||
|
3. **结果分析**:
|
||||||
|
- 不同并行策略的性能比较
|
||||||
|
- MPI进程数和OpenMP线程数的最优组合
|
||||||
|
- 矩阵规模对并行效率的影响
|
||||||
|
|
||||||
|
4. **优化方案**:
|
||||||
|
- 识别性能瓶颈
|
||||||
|
- 提出优化策略
|
||||||
|
- 实施优化并对比效果
|
||||||
|
|
||||||
|
5. **结论**:
|
||||||
|
- MPI-OpenMP混合并行的优势
|
||||||
|
- 最佳实践建议
|
||||||
|
- 进一步改进方向
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 编译错误
|
||||||
|
|
||||||
|
如果遇到MPI相关错误:
|
||||||
|
```bash
|
||||||
|
# 检查MPI是否安装
|
||||||
|
which mpic++
|
||||||
|
mpic++ --version
|
||||||
|
|
||||||
|
# 检查OpenMP支持
|
||||||
|
echo | clang++ -x c++ - -fopenmp -E - > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行时错误
|
||||||
|
|
||||||
|
如果遇到MPI运行错误:
|
||||||
|
```bash
|
||||||
|
# 检查MPI进程数是否合理
|
||||||
|
# 确保系统有足够的资源
|
||||||
|
|
||||||
|
# 检查OpenMP线程数设置
|
||||||
|
echo $OMP_NUM_THREADS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能异常
|
||||||
|
|
||||||
|
如果性能不如预期:
|
||||||
|
1. 检查CPU频率是否正常(是否降频)
|
||||||
|
2. 关闭其他占用资源的程序
|
||||||
|
3. 检查系统负载
|
||||||
|
4. 确认编译优化选项已启用(-O3)
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- MPI教程:https://mpitutorial.com/
|
||||||
|
- OpenMP官方文档:https://www.openmp.org/
|
||||||
|
- 并行编程模式:https://patterns.eecs.berkeley.edu/
|
||||||
280
work/analyze_results.py
Executable file
280
work/analyze_results.py
Executable file
@ -0,0 +1,280 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
MPI-OpenMP矩阵乘法实验数据分析脚本
|
||||||
|
用于读取实验数据并生成性能分析图表
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import seaborn as sns
|
||||||
|
|
||||||
|
# 设置中文字体和样式
|
||||||
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
|
||||||
|
plt.rcParams['axes.unicode_minus'] = False
|
||||||
|
sns.set_style("whitegrid")
|
||||||
|
|
||||||
|
def load_data(filename='experiment_results.csv'):
|
||||||
|
"""加载实验数据"""
|
||||||
|
df = pd.read_csv(filename)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def load_serial_data(filename='serial_results.csv'):
|
||||||
|
"""加载串行基准数据"""
|
||||||
|
df = pd.read_csv(filename)
|
||||||
|
return df
|
||||||
|
|
||||||
|
def plot_experiment1(df):
|
||||||
|
"""绘制实验一:MPI进程数扩展性"""
|
||||||
|
exp1 = df[df['Experiment'] == 'Exp1'].copy()
|
||||||
|
|
||||||
|
if exp1.empty:
|
||||||
|
print("警告:没有找到实验一的数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
|
||||||
|
|
||||||
|
# 绘制加速比
|
||||||
|
for size in exp1['M'].unique():
|
||||||
|
data = exp1[exp1['M'] == size].sort_values('MPI_Processes')
|
||||||
|
ax1.plot(data['MPI_Processes'], data['Speedup'],
|
||||||
|
marker='o', label=f'{size}×{size}', linewidth=2)
|
||||||
|
|
||||||
|
ax1.set_xlabel('MPI进程数', fontsize=12)
|
||||||
|
ax1.set_ylabel('加速比', fontsize=12)
|
||||||
|
ax1.set_title('实验一:MPI进程数扩展性(OpenMP=1)', fontsize=14)
|
||||||
|
ax1.legend(fontsize=10)
|
||||||
|
ax1.grid(True, alpha=0.3)
|
||||||
|
ax1.plot([1, 16], [1, 16], 'k--', alpha=0.3, label='理想线性加速比')
|
||||||
|
|
||||||
|
# 绘制效率
|
||||||
|
for size in exp1['M'].unique():
|
||||||
|
data = exp1[exp1['M'] == size].sort_values('MPI_Processes')
|
||||||
|
ax2.plot(data['MPI_Processes'], data['Efficiency'] * 100,
|
||||||
|
marker='s', label=f'{size}×{size}', linewidth=2)
|
||||||
|
|
||||||
|
ax2.set_xlabel('MPI进程数', fontsize=12)
|
||||||
|
ax2.set_ylabel('效率 (%)', fontsize=12)
|
||||||
|
ax2.set_title('实验一:并行效率', fontsize=14)
|
||||||
|
ax2.legend(fontsize=10)
|
||||||
|
ax2.grid(True, alpha=0.3)
|
||||||
|
ax2.axhline(y=100, color='k', linestyle='--', alpha=0.3, label='理想效率100%')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('exp1_mpi_scaling.png', dpi=300, bbox_inches='tight')
|
||||||
|
print("已保存: exp1_mpi_scaling.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
def plot_experiment2(df):
|
||||||
|
"""绘制实验二:MPI-OpenMP混合并行扩展性"""
|
||||||
|
exp2 = df[df['Experiment'] == 'Exp2'].copy()
|
||||||
|
|
||||||
|
if exp2.empty:
|
||||||
|
print("警告:没有找到实验二的数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
exp2['Total_Processors'] = exp2['MPI_Processes'] * exp2['OpenMP_Threads']
|
||||||
|
|
||||||
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
|
||||||
|
|
||||||
|
# 绘制加速比(按OpenMP线程数分组)
|
||||||
|
for nthreads in exp2['OpenMP_Threads'].unique():
|
||||||
|
data = exp2[exp2['OpenMP_Threads'] == nthreads].copy()
|
||||||
|
# 对相同总处理器数的数据取平均
|
||||||
|
avg_data = data.groupby('Total_Processors').agg({
|
||||||
|
'Speedup': 'mean',
|
||||||
|
'Efficiency': 'mean'
|
||||||
|
}).reset_index()
|
||||||
|
|
||||||
|
ax1.plot(avg_data['Total_Processors'], avg_data['Speedup'],
|
||||||
|
marker='o', label=f'OpenMP={nthreads}', linewidth=2)
|
||||||
|
|
||||||
|
ax1.set_xlabel('总处理器数', fontsize=12)
|
||||||
|
ax1.set_ylabel('加速比', fontsize=12)
|
||||||
|
ax1.set_title('实验二:混合并行扩展性', fontsize=14)
|
||||||
|
ax1.legend(fontsize=10)
|
||||||
|
ax1.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
# 绘制效率
|
||||||
|
for nthreads in exp2['OpenMP_Threads'].unique():
|
||||||
|
data = exp2[exp2['OpenMP_Threads'] == nthreads].copy()
|
||||||
|
avg_data = data.groupby('Total_Processors').agg({
|
||||||
|
'Speedup': 'mean',
|
||||||
|
'Efficiency': 'mean'
|
||||||
|
}).reset_index()
|
||||||
|
|
||||||
|
ax2.plot(avg_data['Total_Processors'], avg_data['Efficiency'] * 100,
|
||||||
|
marker='s', label=f'OpenMP={nthreads}', linewidth=2)
|
||||||
|
|
||||||
|
ax2.set_xlabel('总处理器数', fontsize=12)
|
||||||
|
ax2.set_ylabel('效率 (%)', fontsize=12)
|
||||||
|
ax2.set_title('实验二:并行效率', fontsize=14)
|
||||||
|
ax2.legend(fontsize=10)
|
||||||
|
ax2.grid(True, alpha=0.3)
|
||||||
|
ax2.axhline(y=100, color='k', linestyle='--', alpha=0.3)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('exp2_hybrid_scaling.png', dpi=300, bbox_inches='tight')
|
||||||
|
print("已保存: exp2_hybrid_scaling.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
def plot_experiment3(df):
|
||||||
|
"""绘制实验三:MPI/OpenMP组合优化"""
|
||||||
|
exp3 = df[df['Experiment'] == 'Exp3'].copy()
|
||||||
|
|
||||||
|
if exp3.empty:
|
||||||
|
print("警告:没有找到实验三的数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
exp3['Total_Processors'] = exp3['MPI_Processes'] * exp3['OpenMP_Threads']
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(12, 6))
|
||||||
|
|
||||||
|
# 绘制效率热图
|
||||||
|
for size in exp3['M'].unique():
|
||||||
|
data = exp3[exp3['M'] == size]
|
||||||
|
ax.plot(data['MPI_Processes'], data['Efficiency'] * 100,
|
||||||
|
marker='o', label=f'{size}×{size}', linewidth=2, markersize=8)
|
||||||
|
|
||||||
|
ax.set_xlabel('MPI进程数', fontsize=12)
|
||||||
|
ax.set_ylabel('效率 (%)', fontsize=12)
|
||||||
|
ax.set_title('实验三:不同MPI/OpenMP组合的效率(总处理器数=16)', fontsize=14)
|
||||||
|
ax.legend(fontsize=10)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
ax.axhline(y=100, color='k', linestyle='--', alpha=0.3)
|
||||||
|
|
||||||
|
# 添加x轴标签显示OpenMP线程数
|
||||||
|
ax2 = ax.twiny()
|
||||||
|
ax2.set_xlim(ax.get_xlim())
|
||||||
|
ax2.set_xlabel('OpenMP线程数', fontsize=12)
|
||||||
|
ax2.set_xticks([1, 2, 4, 8, 16])
|
||||||
|
ax2.set_xticklabels([16, 8, 4, 2, 1])
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('exp3_mpi_openmp_combo.png', dpi=300, bbox_inches='tight')
|
||||||
|
print("已保存: exp3_mpi_openmp_combo.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
def plot_efficiency_heatmap(df):
|
||||||
|
"""绘制效率热图"""
|
||||||
|
exp2 = df[df['Experiment'] == 'Exp2'].copy()
|
||||||
|
|
||||||
|
if exp2.empty:
|
||||||
|
print("警告:没有找到实验二的数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 选择一个中等规模的矩阵尺寸
|
||||||
|
sizes = sorted(exp2['M'].unique())
|
||||||
|
if len(sizes) > 2:
|
||||||
|
target_size = sizes[len(sizes)//2]
|
||||||
|
else:
|
||||||
|
target_size = sizes[0] if sizes else 1024
|
||||||
|
|
||||||
|
data = exp2[exp2['M'] == target_size].copy()
|
||||||
|
|
||||||
|
if data.empty:
|
||||||
|
print("警告:没有足够的数据绘制热图")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建数据透视表
|
||||||
|
pivot_data = data.pivot_table(
|
||||||
|
values='Efficiency',
|
||||||
|
index='MPI_Processes',
|
||||||
|
columns='OpenMP_Threads',
|
||||||
|
aggfunc='mean'
|
||||||
|
) * 100
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(10, 8))
|
||||||
|
sns.heatmap(pivot_data, annot=True, fmt='.1f', cmap='YlOrRd',
|
||||||
|
cbar_kws={'label': '效率 (%)'}, ax=ax)
|
||||||
|
ax.set_title(f'并行效率热图(矩阵尺寸: {target_size}×{target_size})', fontsize=14)
|
||||||
|
ax.set_xlabel('OpenMP线程数', fontsize=12)
|
||||||
|
ax.set_ylabel('MPI进程数', fontsize=12)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('efficiency_heatmap.png', dpi=300, bbox_inches='tight')
|
||||||
|
print("已保存: efficiency_heatmap.png")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
def print_summary(df):
|
||||||
|
"""打印实验结果摘要"""
|
||||||
|
print("\n" + "="*80)
|
||||||
|
print("实验结果摘要")
|
||||||
|
print("="*80)
|
||||||
|
|
||||||
|
# 实验一摘要
|
||||||
|
exp1 = df[df['Experiment'] == 'Exp1']
|
||||||
|
if not exp1.empty:
|
||||||
|
print("\n实验一:MPI进程数扩展性(OpenMP=1)")
|
||||||
|
print("-" * 80)
|
||||||
|
for size in sorted(exp1['M'].unique()):
|
||||||
|
data = exp1[exp1['M'] == size]
|
||||||
|
max_speedup = data['Speedup'].max()
|
||||||
|
max_eff = data['Efficiency'].max()
|
||||||
|
best_np = data.loc[data['Speedup'].idxmax(), 'MPI_Processes']
|
||||||
|
print(f"矩阵 {size}×{size}: 最大加速比={max_speedup:.2f} (NP={best_np}), "
|
||||||
|
f"最高效率={max_eff*100:.1f}%")
|
||||||
|
|
||||||
|
# 实验二摘要
|
||||||
|
exp2 = df[df['Experiment'] == 'Exp2']
|
||||||
|
if not exp2.empty:
|
||||||
|
exp2['Total_Processors'] = exp2['MPI_Processes'] * exp2['OpenMP_Threads']
|
||||||
|
print("\n实验二:混合并行扩展性")
|
||||||
|
print("-" * 80)
|
||||||
|
for nthreads in sorted(exp2['OpenMP_Threads'].unique()):
|
||||||
|
data = exp2[exp2['OpenMP_Threads'] == nthreads]
|
||||||
|
max_speedup = data['Speedup'].max()
|
||||||
|
max_eff = data['Efficiency'].max()
|
||||||
|
best_total = data.loc[data['Speedup'].idxmax(), 'Total_Processors']
|
||||||
|
print(f"OpenMP={nthreads}: 最大加速比={max_speedup:.2f} "
|
||||||
|
f"(总处理器={best_total}), 最高效率={max_eff*100:.1f}%")
|
||||||
|
|
||||||
|
# 实验三摘要
|
||||||
|
exp3 = df[df['Experiment'] == 'Exp3']
|
||||||
|
if not exp3.empty:
|
||||||
|
print("\n实验三:MPI/OpenMP组合优化(总处理器=16)")
|
||||||
|
print("-" * 80)
|
||||||
|
for size in sorted(exp3['M'].unique()):
|
||||||
|
data = exp3[exp3['M'] == size]
|
||||||
|
max_eff = data['Efficiency'].max()
|
||||||
|
best_config = data.loc[data['Efficiency'].idxmax()]
|
||||||
|
print(f"矩阵 {size}×{size}: 最高效率={max_eff*100:.1f}% "
|
||||||
|
f"(MPI={best_config['MPI_Processes']}, "
|
||||||
|
f"OpenMP={best_config['OpenMP_Threads']})")
|
||||||
|
|
||||||
|
print("\n" + "="*80)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
filename = sys.argv[1] if len(sys.argv) > 1 else 'experiment_results.csv'
|
||||||
|
|
||||||
|
print(f"加载数据文件: {filename}")
|
||||||
|
try:
|
||||||
|
df = load_data(filename)
|
||||||
|
print(f"数据加载成功,共 {len(df)} 条记录")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"错误:找不到文件 {filename}")
|
||||||
|
print("请先运行 ./run_experiments.sh 生成实验数据")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 打印摘要
|
||||||
|
print_summary(df)
|
||||||
|
|
||||||
|
# 生成图表
|
||||||
|
print("\n生成性能分析图表...")
|
||||||
|
plot_experiment1(df)
|
||||||
|
plot_experiment2(df)
|
||||||
|
plot_experiment3(df)
|
||||||
|
plot_efficiency_heatmap(df)
|
||||||
|
|
||||||
|
print("\n所有图表已生成完成!")
|
||||||
|
print("\n建议:")
|
||||||
|
print("1. 查看 exp1_mpi_scaling.png 了解MPI扩展性")
|
||||||
|
print("2. 查看 exp2_hybrid_scaling.png 了解混合并行性能")
|
||||||
|
print("3. 查看 exp3_mpi_openmp_combo.png 了解MPI/OpenMP组合优化")
|
||||||
|
print("4. 查看 efficiency_heatmap.png 了解不同配置的效率分布")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
39
work/build.sh
Executable file
39
work/build.sh
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 编译脚本 - 使用mpic++直接编译
|
||||||
|
|
||||||
|
echo "编译MPI-OpenMP矩阵乘法程序..."
|
||||||
|
|
||||||
|
# 编译串行版本
|
||||||
|
echo "编译串行版本..."
|
||||||
|
g++ -O3 -march=native gemm_serial.cpp -o gemm_serial
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " 串行版本编译成功: gemm_serial"
|
||||||
|
else
|
||||||
|
echo " 串行版本编译失败!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 编译并行版本
|
||||||
|
echo "编译并行版本..."
|
||||||
|
mpic++ -O3 -march=native -fopenmp gemm_parallel.cpp -o gemm_parallel
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " 并行版本编译成功: gemm_parallel"
|
||||||
|
else
|
||||||
|
echo " 并行版本编译失败!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 编译优化版本
|
||||||
|
echo "编译优化版本..."
|
||||||
|
mpic++ -O3 -march=native -fopenmp gemm_optimized.cpp -o gemm_optimized
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " 优化版本编译成功: gemm_optimized"
|
||||||
|
else
|
||||||
|
echo " 优化版本编译失败!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "所有版本编译完成!"
|
||||||
|
echo "可执行文件: gemm_serial, gemm_parallel, gemm_optimized"
|
||||||
41
work/experiment_results.csv
Normal file
41
work/experiment_results.csv
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Experiment,M,N,K,MPI_Processes,OpenMP_Threads,Time_ms,Speedup,Efficiency
|
||||||
|
Exp1,512,512,512,1,1,256.697,.9890,.9890
|
||||||
|
Exp1,512,512,512,2,1,132.547,1.9153,.9576
|
||||||
|
Exp1,512,512,512,4,1,76.225,3.3305,.8326
|
||||||
|
Exp1,512,512,512,9,1,43.584,5.8249,.6472
|
||||||
|
Exp1,512,512,512,16,1,50.423,5.0348,.3146
|
||||||
|
Exp1,1024,1024,1024,1,1,1867.22,.9643,.9643
|
||||||
|
Exp1,1024,1024,1024,2,1,969.653,1.8570,.9285
|
||||||
|
Exp1,1024,1024,1024,4,1,519.796,3.4642,.8660
|
||||||
|
Exp1,1024,1024,1024,9,1,301.516,5.9721,.6635
|
||||||
|
Exp1,1024,1024,1024,16,1,302.173,5.9591,.3724
|
||||||
|
Exp1,2048,2048,2048,1,1,14317,1.0010,1.0010
|
||||||
|
Exp1,2048,2048,2048,2,1,7480.42,1.9160,.9580
|
||||||
|
Exp1,2048,2048,2048,4,1,3835.64,3.7366,.9341
|
||||||
|
Exp1,2048,2048,2048,9,1,1990.38,7.2008,.8000
|
||||||
|
Exp1,2048,2048,2048,16,1,2726.76,5.2562,.3285
|
||||||
|
Exp1,4096,4096,4096,1,1,115264,1.0014,1.0014
|
||||||
|
Exp1,4096,4096,4096,2,1,59895.9,1.9272,.9636
|
||||||
|
Exp1,4096,4096,4096,4,1,30193.8,3.8230,.9557
|
||||||
|
Exp1,4096,4096,4096,9,1,17926,6.4393,.7154
|
||||||
|
Exp1,4096,4096,4096,16,1,20160.1,5.7257,.3578
|
||||||
|
Exp3-opt,512,512,512,1,16,73.444,3.4567,.2160
|
||||||
|
Exp3-opt,512,512,512,2,8,48.487,5.2359,.3272
|
||||||
|
Exp3-opt,512,512,512,4,4,24.81,10.2327,.6395
|
||||||
|
Exp3-opt,512,512,512,8,2,26.739,9.4945,.5934
|
||||||
|
Exp3-opt,512,512,512,16,1,44.175,5.7470,.3591
|
||||||
|
Exp3-opt,1024,1024,1024,1,16,711.848,2.5296,.1581
|
||||||
|
Exp3-opt,1024,1024,1024,2,8,397.291,4.5324,.2832
|
||||||
|
Exp3-opt,1024,1024,1024,4,4,126.462,14.2390,.8899
|
||||||
|
Exp3-opt,1024,1024,1024,8,2,158.872,11.3342,.7083
|
||||||
|
Exp3-opt,1024,1024,1024,16,1,290.578,6.1969,.3873
|
||||||
|
Exp3-opt,2048,2048,2048,1,16,5834.11,2.4566,.1535
|
||||||
|
Exp3-opt,2048,2048,2048,2,8,2957.71,4.8458,.3028
|
||||||
|
Exp3-opt,2048,2048,2048,4,4,933.626,15.3514,.9594
|
||||||
|
Exp3-opt,2048,2048,2048,8,2,980.117,14.6232,.9139
|
||||||
|
Exp3-opt,2048,2048,2048,16,1,1446.78,9.9064,.6191
|
||||||
|
Exp3-opt,4096,4096,4096,1,16,49018.6,2.3548,.1471
|
||||||
|
Exp3-opt,4096,4096,4096,2,8,25955.3,4.4473,.2779
|
||||||
|
Exp3-opt,4096,4096,4096,4,4,6514.2,17.7200,1.1075
|
||||||
|
Exp3-opt,4096,4096,4096,8,2,6978.85,16.5402,1.0337
|
||||||
|
Exp3-opt,4096,4096,4096,16,1,8275.21,13.9491,.8718
|
||||||
|
BIN
work/gemm_optimized
Executable file
BIN
work/gemm_optimized
Executable file
Binary file not shown.
BIN
work/gemm_parallel
Executable file
BIN
work/gemm_parallel
Executable file
Binary file not shown.
BIN
work/gemm_serial
Executable file
BIN
work/gemm_serial
Executable file
Binary file not shown.
49
work/quick_test.sh
Executable file
49
work/quick_test.sh
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 快速测试脚本 - 验证程序功能
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "MPI-OpenMP矩阵乘法快速测试"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# 编译程序
|
||||||
|
echo "1. 编译程序..."
|
||||||
|
./build.sh
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "编译失败!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 测试串行版本
|
||||||
|
echo "2. 测试串行版本 (512x512x512)..."
|
||||||
|
./gemm_serial 512 512 512 0 | grep -E "(matmul|verification)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 测试并行版本 - 单进程
|
||||||
|
echo "3. 测试并行版本 (1 MPI进程, 512x512x512)..."
|
||||||
|
mpirun -np 1 ./gemm_parallel 512 512 512 | grep -E "(mpi matmul|verification)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 测试并行版本 - 多进程
|
||||||
|
echo "4. 测试并行版本 (4 MPI进程, 512x512x512)..."
|
||||||
|
mpirun -np 4 ./gemm_parallel 512 512 512 | grep -E "(mpi matmul|verification)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 测试并行版本 - 混合并行
|
||||||
|
echo "5. 测试并行版本 (2 MPI进程, 2 OpenMP线程, 512x512x512)..."
|
||||||
|
export OMP_NUM_THREADS=2
|
||||||
|
mpirun -np 2 ./gemm_parallel 512 512 512 | grep -E "(mpi matmul|verification)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 测试优化版本
|
||||||
|
echo "6. 测试优化版本 (4 MPI进程, 512x512x512)..."
|
||||||
|
mpirun -np 4 ./gemm_optimized 512 512 512 | grep -E "(optimized mpi matmul|verification)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "快速测试完成!"
|
||||||
|
echo ""
|
||||||
|
echo "如果所有测试都显示 'PASSED',说明程序工作正常。"
|
||||||
|
echo "接下来可以运行 ./run_experiments.sh 进行完整实验。"
|
||||||
|
echo "=========================================="
|
||||||
@ -9,16 +9,7 @@ export OMP_NUM_THREADS=${OMP_NUM_THREADS:-1}
|
|||||||
# 输出文件
|
# 输出文件
|
||||||
OUTPUT_FILE="experiment_results.csv"
|
OUTPUT_FILE="experiment_results.csv"
|
||||||
SERIAL_OUTPUT="serial_results.csv"
|
SERIAL_OUTPUT="serial_results.csv"
|
||||||
# get arch using uname -m
|
|
||||||
# if aarch64 then use arm64-v8a else use x86_64
|
|
||||||
ARCH=$(uname -m)
|
|
||||||
if [ "$ARCH" == "aarch64" ]; then
|
|
||||||
BUILD_ARCH="arm64-v8a"
|
|
||||||
else
|
|
||||||
BUILD_ARCH="x86_64"
|
|
||||||
fi
|
|
||||||
# Build directory
|
|
||||||
BUILD_DIR="./build/linux/$BUILD_ARCH/release"
|
|
||||||
# 创建输出文件并写入表头
|
# 创建输出文件并写入表头
|
||||||
echo "Experiment,M,N,K,MPI_Processes,OpenMP_Threads,Time_ms,Speedup,Efficiency" > $OUTPUT_FILE
|
echo "Experiment,M,N,K,MPI_Processes,OpenMP_Threads,Time_ms,Speedup,Efficiency" > $OUTPUT_FILE
|
||||||
echo "M,N,K,Time_ms" > $SERIAL_OUTPUT
|
echo "M,N,K,Time_ms" > $SERIAL_OUTPUT
|
||||||
@ -27,7 +18,7 @@ echo "M,N,K,Time_ms" > $SERIAL_OUTPUT
|
|||||||
MATRIX_SIZES="512 1024 2048 4096"
|
MATRIX_SIZES="512 1024 2048 4096"
|
||||||
|
|
||||||
# MPI进程数配置
|
# MPI进程数配置
|
||||||
MPI_PROCESSES="1 2 3 6 9 12"
|
MPI_PROCESSES="1 2 4 9 16"
|
||||||
|
|
||||||
# OpenMP线程数配置
|
# OpenMP线程数配置
|
||||||
OPENMP_THREADS="1 2 4 8"
|
OPENMP_THREADS="1 2 4 8"
|
||||||
@ -38,7 +29,7 @@ echo "=========================================="
|
|||||||
|
|
||||||
# 编译程序
|
# 编译程序
|
||||||
echo "编译程序..."
|
echo "编译程序..."
|
||||||
xmake
|
./build.sh
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "编译失败!"
|
echo "编译失败!"
|
||||||
exit 1
|
exit 1
|
||||||
@ -53,7 +44,7 @@ echo "=========================================="
|
|||||||
|
|
||||||
for SIZE in $MATRIX_SIZES; do
|
for SIZE in $MATRIX_SIZES; do
|
||||||
echo "测试矩阵尺寸: ${SIZE}x${SIZE}x${SIZE}"
|
echo "测试矩阵尺寸: ${SIZE}x${SIZE}x${SIZE}"
|
||||||
TIME=$($BUILD_DIR/gemm_serial $SIZE $SIZE $SIZE 0 | grep "matmul:" | awk '{print $2}')
|
TIME=$(./gemm_serial $SIZE $SIZE $SIZE 0 | grep "matmul:" | awk '{print $2}')
|
||||||
echo " 时间: ${TIME} ms"
|
echo " 时间: ${TIME} ms"
|
||||||
echo "$SIZE,$SIZE,$SIZE,$TIME" >> $SERIAL_OUTPUT
|
echo "$SIZE,$SIZE,$SIZE,$TIME" >> $SERIAL_OUTPUT
|
||||||
done
|
done
|
||||||
@ -75,7 +66,7 @@ for SIZE in $MATRIX_SIZES; do
|
|||||||
|
|
||||||
for NP in $MPI_PROCESSES; do
|
for NP in $MPI_PROCESSES; do
|
||||||
echo " MPI进程数: $NP"
|
echo " MPI进程数: $NP"
|
||||||
TIME=$(mpirun --hostfile ~/mpi_hosts --oversubscribe -np $NP $BUILD_DIR/gemm_parallel $SIZE $SIZE $SIZE | grep "mpi matmul:" | awk '{print $3}')
|
TIME=$(mpirun --oversubscribe -np $NP ./gemm_parallel $SIZE $SIZE $SIZE | grep "mpi matmul:" | awk '{print $3}')
|
||||||
|
|
||||||
if [ ! -z "$TIME" ]; then
|
if [ ! -z "$TIME" ]; then
|
||||||
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
||||||
@ -106,7 +97,7 @@ for SIZE in $MATRIX_SIZES; do
|
|||||||
TOTAL_PROCS=$((NP * NTHREADS))
|
TOTAL_PROCS=$((NP * NTHREADS))
|
||||||
echo " MPI进程数: $NP (总处理器数: $TOTAL_PROCS)"
|
echo " MPI进程数: $NP (总处理器数: $TOTAL_PROCS)"
|
||||||
|
|
||||||
TIME=$(mpirun --hostfile ~/mpi_hosts --oversubscribe -np $NP $BUILD_DIR/gemm_parallel $SIZE $SIZE $SIZE | grep "mpi matmul:" | awk '{print $3}')
|
TIME=$(mpirun --oversubscribe -np $NP ./gemm_parallel $SIZE $SIZE $SIZE | grep "mpi matmul:" | awk '{print $3}')
|
||||||
|
|
||||||
if [ ! -z "$TIME" ]; then
|
if [ ! -z "$TIME" ]; then
|
||||||
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
||||||
@ -144,7 +135,7 @@ for SIZE in $MATRIX_SIZES; do
|
|||||||
export OMP_NUM_THREADS=$NTHREADS
|
export OMP_NUM_THREADS=$NTHREADS
|
||||||
echo " MPI: $NP, OpenMP: $NTHREADS (总处理器: $TOTAL_PROCS)"
|
echo " MPI: $NP, OpenMP: $NTHREADS (总处理器: $TOTAL_PROCS)"
|
||||||
|
|
||||||
TIME=$(mpirun --hostfile ~/mpi_hosts --oversubscribe -np $NP $BUILD_DIR/gemm_parallel $SIZE $SIZE $SIZE | grep "mpi matmul:" | awk '{print $3}')
|
TIME=$(mpirun --oversubscribe -np $NP ./gemm_parallel $SIZE $SIZE $SIZE | grep "mpi matmul:" | awk '{print $3}')
|
||||||
|
|
||||||
if [ ! -z "$TIME" ]; then
|
if [ ! -z "$TIME" ]; then
|
||||||
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
||||||
@ -178,7 +169,7 @@ for SIZE in $MATRIX_SIZES; do
|
|||||||
export OMP_NUM_THREADS=$NTHREADS
|
export OMP_NUM_THREADS=$NTHREADS
|
||||||
echo " MPI: $NP, OpenMP: $NTHREADS (总处理器: $TOTAL_PROCS)"
|
echo " MPI: $NP, OpenMP: $NTHREADS (总处理器: $TOTAL_PROCS)"
|
||||||
|
|
||||||
TIME=$(mpirun --hostfile ~/mpi_hosts --oversubscribe -np $NP $BUILD_DIR/gemm_optimized $SIZE $SIZE $SIZE | grep "optimized mpi matmul:" | awk '{print $4}')
|
TIME=$(mpirun --oversubscribe -np $NP ./gemm_optimized $SIZE $SIZE $SIZE | grep "optimized mpi matmul:" | awk '{print $4}')
|
||||||
|
|
||||||
if [ ! -z "$TIME" ]; then
|
if [ ! -z "$TIME" ]; then
|
||||||
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
||||||
|
|||||||
5
work/serial_results.csv
Normal file
5
work/serial_results.csv
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
M,N,K,Time_ms
|
||||||
|
512,512,512,253.874
|
||||||
|
1024,1024,1024,1800.7
|
||||||
|
2048,2048,2048,14332.5
|
||||||
|
4096,4096,4096,115432
|
||||||
|
58
work/test_experiments.sh
Executable file
58
work/test_experiments.sh
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 快速测试脚本 - 验证run_experiments.sh的基本功能
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "快速实验测试"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# 设置较小的测试参数
|
||||||
|
MATRIX_SIZES="512 1024"
|
||||||
|
MPI_PROCESSES="1 2"
|
||||||
|
OPENMP_THREADS="1 2"
|
||||||
|
|
||||||
|
OUTPUT_FILE="test_experiment_results.csv"
|
||||||
|
SERIAL_OUTPUT="test_serial_results.csv"
|
||||||
|
|
||||||
|
# 创建输出文件
|
||||||
|
echo "Experiment,M,N,K,MPI_Processes,OpenMP_Threads,Time_ms,Speedup,Efficiency" > $OUTPUT_FILE
|
||||||
|
echo "M,N,K,Time_ms" > $SERIAL_OUTPUT
|
||||||
|
|
||||||
|
echo "1. 串行基准测试..."
|
||||||
|
for SIZE in $MATRIX_SIZES; do
|
||||||
|
echo " 测试 ${SIZE}x${SIZE}x${SIZE}"
|
||||||
|
TIME=$(./gemm_serial $SIZE $SIZE $SIZE 0 | grep "matmul:" | awk '{print $2}')
|
||||||
|
echo "$SIZE,$SIZE,$SIZE,$TIME" >> $SERIAL_OUTPUT
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "2. MPI并行测试..."
|
||||||
|
export OMP_NUM_THREADS=1
|
||||||
|
for SIZE in $MATRIX_SIZES; do
|
||||||
|
SERIAL_TIME=$(grep "^$SIZE," $SERIAL_OUTPUT | cut -d',' -f4)
|
||||||
|
echo " 矩阵 ${SIZE}x${SIZE}x${SIZE}, 串行时间: ${SERIAL_TIME} ms"
|
||||||
|
|
||||||
|
for NP in $MPI_PROCESSES; do
|
||||||
|
echo " 测试 $NP 个MPI进程..."
|
||||||
|
TIME=$(mpirun --oversubscribe -np $NP ./gemm_parallel $SIZE $SIZE $SIZE | grep "mpi matmul:" | awk '{print $3}')
|
||||||
|
|
||||||
|
if [ ! -z "$TIME" ]; then
|
||||||
|
SPEEDUP=$(echo "scale=4; $SERIAL_TIME / $TIME" | bc)
|
||||||
|
EFFICIENCY=$(echo "scale=4; $SPEEDUP / $NP" | bc)
|
||||||
|
echo " 时间: ${TIME} ms, 加速比: $SPEEDUP, 效率: $EFFICIENCY"
|
||||||
|
echo "Exp1,$SIZE,$SIZE,$SIZE,$NP,1,$TIME,$SPEEDUP,$EFFICIENCY" >> $OUTPUT_FILE
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "快速测试完成!"
|
||||||
|
echo "结果文件: $OUTPUT_FILE"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "查看结果:"
|
||||||
|
cat $OUTPUT_FILE
|
||||||
|
echo ""
|
||||||
|
echo "如果以上测试正常,则可以运行完整的实验:"
|
||||||
|
echo " ./run_experiments.sh"
|
||||||
@ -1,6 +1,5 @@
|
|||||||
set_project("gemm")
|
set_project("gemm")
|
||||||
set_version("1.0")
|
set_version("1.0")
|
||||||
add_requires("openmp")
|
|
||||||
|
|
||||||
add_rules("mode.debug", "mode.release")
|
add_rules("mode.debug", "mode.release")
|
||||||
-- Find MPI package
|
-- Find MPI package
|
||||||
@ -16,8 +15,8 @@ target("gemm_serial")
|
|||||||
target("gemm_parallel")
|
target("gemm_parallel")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("gemm_parallel.cpp")
|
add_files("gemm_parallel.cpp")
|
||||||
add_cxxflags("-O3", "-march=native")
|
add_cxxflags("-O3", "-march=native", "-fopenmp")
|
||||||
add_packages("openmp")
|
add_ldflags("-fopenmp")
|
||||||
-- 使用mpic++作为编译器
|
-- 使用mpic++作为编译器
|
||||||
add_packages("mpi")
|
add_packages("mpi")
|
||||||
add_packages("mpi_cxx")
|
add_packages("mpi_cxx")
|
||||||
@ -26,8 +25,8 @@ target("gemm_parallel")
|
|||||||
target("gemm_optimized")
|
target("gemm_optimized")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("gemm_optimized.cpp")
|
add_files("gemm_optimized.cpp")
|
||||||
add_cxxflags("-O3", "-march=native")
|
add_cxxflags("-O3", "-march=native", "-fopenmp")
|
||||||
add_packages("openmp")
|
add_ldflags("-fopenmp")
|
||||||
-- 使用mpic++作为编译器
|
-- 使用mpic++作为编译器
|
||||||
add_packages("mpi")
|
add_packages("mpi")
|
||||||
add_packages("mpi_cxx")
|
add_packages("mpi_cxx")
|
||||||
|
|||||||
326
work/实验报告模板.md
Normal file
326
work/实验报告模板.md
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
# MPI-OpenMP混合并行矩阵乘法实验报告
|
||||||
|
|
||||||
|
**姓名**:__________
|
||||||
|
**学号**:__________
|
||||||
|
**日期**:__________
|
||||||
|
|
||||||
|
## 1. 实验目的
|
||||||
|
|
||||||
|
1. 掌握MPI、OpenMP程序设计的基本编写、编译与运行方法
|
||||||
|
2. 了解集群环境下矩阵乘法的并行程序设计方法
|
||||||
|
3. 掌握利用(强/弱)加速比、运行时间、效率等测度分析并行程序性能
|
||||||
|
|
||||||
|
## 2. 实验环境
|
||||||
|
|
||||||
|
### 2.1 硬件环境
|
||||||
|
- CPU型号:__________
|
||||||
|
- 核心数:__________
|
||||||
|
- 内存大小:__________
|
||||||
|
|
||||||
|
### 2.2 软件环境
|
||||||
|
- 操作系统:__________
|
||||||
|
- MPI版本:__________
|
||||||
|
- 编译器版本:__________
|
||||||
|
- OpenMP版本:__________
|
||||||
|
|
||||||
|
## 3. 实验原理
|
||||||
|
|
||||||
|
### 3.1 矩阵乘法串行算法
|
||||||
|
|
||||||
|
矩阵乘法C = A × B,其中A为m×n矩阵,B为n×k矩阵,C为m×k矩阵。
|
||||||
|
|
||||||
|
串行算法复杂度:O(m×n×k)
|
||||||
|
|
||||||
|
### 3.2 并行算法设计
|
||||||
|
|
||||||
|
#### 3.2.1 MPI并行策略
|
||||||
|
- 采用主从(Master-Slave)模型
|
||||||
|
- 使用带状分块方式分配矩阵
|
||||||
|
- Rank 0负责数据分发和结果收集
|
||||||
|
|
||||||
|
#### 3.2.2 OpenMP并行策略
|
||||||
|
- 在每个MPI进程内部使用OpenMP并行化本地矩阵乘法
|
||||||
|
- 使用`#pragma omp parallel for`并行化外层循环
|
||||||
|
- 支持collapse子句增加并行度
|
||||||
|
|
||||||
|
#### 3.2.3 混合并行策略
|
||||||
|
- MPI用于节点间并行
|
||||||
|
- OpenMP用于节点内并行
|
||||||
|
- 通过调整MPI进程数和OpenMP线程数优化性能
|
||||||
|
|
||||||
|
## 4. 实验步骤与结果
|
||||||
|
|
||||||
|
### 4.1 编译与测试
|
||||||
|
|
||||||
|
#### 编译命令
|
||||||
|
```bash
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 快速测试结果
|
||||||
|
| 测试项 | 配置 | 运行时间(ms) | 验证结果 |
|
||||||
|
|--------|------|--------------|----------|
|
||||||
|
| 串行版本 | 512×512×512 | ______ | PASSED |
|
||||||
|
| MPI并行(1进程) | 512×512×512 | ______ | PASSED |
|
||||||
|
| MPI并行(4进程) | 512×512×512 | ______ | PASSED |
|
||||||
|
| 混合并行(2×2) | 512×512×512 | ______ | PASSED |
|
||||||
|
| 优化版本(4进程) | 512×512×512 | ______ | PASSED |
|
||||||
|
|
||||||
|
### 4.2 实验一:MPI进程数扩展性
|
||||||
|
|
||||||
|
**实验条件**:OpenMP线程数固定为1
|
||||||
|
|
||||||
|
#### 4.2.1 运行时间数据
|
||||||
|
|
||||||
|
| 矩阵尺寸 | 1进程 | 2进程 | 4进程 | 9进程 | 16进程 |
|
||||||
|
|----------|-------|-------|-------|-------|--------|
|
||||||
|
| 512×512×512 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 1024×1024×1024 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 2048×2048×2048 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 4096×4096×4096 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
|
||||||
|
#### 4.2.2 加速比数据
|
||||||
|
|
||||||
|
| 矩阵尺寸 | 1进程 | 2进程 | 4进程 | 9进程 | 16进程 |
|
||||||
|
|----------|-------|-------|-------|-------|--------|
|
||||||
|
| 512×512×512 | 1.00 | ______ | ______ | ______ | ______ |
|
||||||
|
| 1024×1024×1024 | 1.00 | ______ | ______ | ______ | ______ |
|
||||||
|
| 2048×2048×2048 | 1.00 | ______ | ______ | ______ | ______ |
|
||||||
|
| 4096×4096×4096 | 1.00 | ______ | ______ | ______ | ______ |
|
||||||
|
|
||||||
|
#### 4.2.3 效率数据
|
||||||
|
|
||||||
|
| 矩阵尺寸 | 1进程 | 2进程 | 4进程 | 9进程 | 16进程 |
|
||||||
|
|----------|-------|-------|-------|-------|--------|
|
||||||
|
| 512×512×512 | 100% | ______ | ______ | ______ | ______ |
|
||||||
|
| 1024×1024×1024 | 100% | ______ | ______ | ______ | ______ |
|
||||||
|
| 2048×2048×2048 | 100% | ______ | ______ | ______ | ______ |
|
||||||
|
| 4096×4096×4096 | 100% | ______ | ______ | ______ | ______ |
|
||||||
|
|
||||||
|
#### 4.2.4 性能曲线图
|
||||||
|
|
||||||
|
(在此处插入 exp1_mpi_scaling.png)
|
||||||
|
|
||||||
|
#### 4.2.5 结果分析
|
||||||
|
|
||||||
|
1. **加速比分析**:
|
||||||
|
- 随着MPI进程数增加,加速比______(上升/下降)
|
||||||
|
- 在______进程时达到最大加速比
|
||||||
|
- 大规模矩阵的加速比______(优于/差于)小规模矩阵
|
||||||
|
|
||||||
|
2. **效率分析**:
|
||||||
|
- 并行效率随进程数增加而______(上升/下降)
|
||||||
|
- 效率下降的主要原因是______
|
||||||
|
- ______矩阵尺寸的效率最高
|
||||||
|
|
||||||
|
### 4.3 实验二:MPI-OpenMP混合并行扩展性
|
||||||
|
|
||||||
|
#### 4.3.1 运行时间数据(部分示例)
|
||||||
|
|
||||||
|
**OpenMP线程数 = 1**:
|
||||||
|
|
||||||
|
| 矩阵尺寸 | 总处理器=1 | 总处理器=2 | 总处理器=4 | 总处理器=8 | 总处理器=16 |
|
||||||
|
|----------|-----------|-----------|-----------|-----------|------------|
|
||||||
|
| 512×512×512 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 1024×1024×1024 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 2048×2048×2048 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 4096×4096×4096 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
|
||||||
|
**OpenMP线程数 = 4**:
|
||||||
|
|
||||||
|
| 矩阵尺寸 | 总处理器=1 | 总处理器=2 | 总处理器=4 | 总处理器=8 | 总处理器=16 |
|
||||||
|
|----------|-----------|-----------|-----------|-----------|------------|
|
||||||
|
| 512×512×512 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 1024×1024×1024 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 2048×2048×2048 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 4096×4096×4096 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
|
||||||
|
#### 4.3.2 最大加速比对比
|
||||||
|
|
||||||
|
| OpenMP线程数 | 最大加速比 | 对应总处理器数 |
|
||||||
|
|--------------|-----------|---------------|
|
||||||
|
| 1 | ______ | ______ |
|
||||||
|
| 2 | ______ | ______ |
|
||||||
|
| 4 | ______ | ______ |
|
||||||
|
| 8 | ______ | ______ |
|
||||||
|
|
||||||
|
#### 4.3.3 性能曲线图
|
||||||
|
|
||||||
|
(在此处插入 exp2_hybrid_scaling.png)
|
||||||
|
|
||||||
|
#### 4.3.4 结果分析
|
||||||
|
|
||||||
|
1. **混合并行效果**:
|
||||||
|
- 混合并行相比纯MPI______(更优/更差)
|
||||||
|
- 最优的OpenMP线程数是______
|
||||||
|
- 原因是______
|
||||||
|
|
||||||
|
2. **扩展性分析**:
|
||||||
|
- 总处理器数增加时,加速比______
|
||||||
|
- 效率随总处理器数______
|
||||||
|
- ______(大/小)规模矩阵的扩展性更好
|
||||||
|
|
||||||
|
### 4.4 实验三:MPI/OpenMP组合优化
|
||||||
|
|
||||||
|
**实验条件**:总处理器数固定为16
|
||||||
|
|
||||||
|
#### 4.4.1 效率数据
|
||||||
|
|
||||||
|
| 矩阵尺寸 | 1×16 | 2×8 | 4×4 | 8×2 | 16×1 |
|
||||||
|
|----------|------|-----|-----|-----|------|
|
||||||
|
| 512×512×512 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 1024×1024×1024 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 2048×2048×2048 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
| 4096×4096×4096 | ______ | ______ | ______ | ______ | ______ |
|
||||||
|
|
||||||
|
*注:M×N表示M个MPI进程,每个进程N个OpenMP线程*
|
||||||
|
|
||||||
|
#### 4.4.2 性能曲线图
|
||||||
|
|
||||||
|
(在此处插入 exp3_mpi_openmp_combo.png)
|
||||||
|
|
||||||
|
#### 4.4.3 结果分析
|
||||||
|
|
||||||
|
1. **最优配置**:
|
||||||
|
- 对于512×512矩阵,最优配置是______
|
||||||
|
- 对于1024×1024矩阵,最优配置是______
|
||||||
|
- 对于2048×2048矩阵,最优配置是______
|
||||||
|
- 对于4096×4096矩阵,最优配置是______
|
||||||
|
|
||||||
|
2. **配置影响分析**:
|
||||||
|
- MPI进程数较多时,效率______(高/低),原因是______
|
||||||
|
- OpenMP线程数较多时,效率______(高/低),原因是______
|
||||||
|
- ______配置的通信开销最小
|
||||||
|
- ______配置的负载均衡最好
|
||||||
|
|
||||||
|
## 5. 性能瓶颈分析与优化
|
||||||
|
|
||||||
|
### 5.1 性能瓶颈识别
|
||||||
|
|
||||||
|
通过实验分析,发现以下性能瓶颈:
|
||||||
|
|
||||||
|
1. **通信瓶颈**:
|
||||||
|
- MPI通信占用时间比例:______
|
||||||
|
- 主要通信操作:______
|
||||||
|
- 通信热点:______
|
||||||
|
|
||||||
|
2. **计算瓶颈**:
|
||||||
|
- 计算密集型操作:______
|
||||||
|
- 缓存命中率:______
|
||||||
|
- 内存带宽利用率:______
|
||||||
|
|
||||||
|
3. **负载不均衡**:
|
||||||
|
- 负载不均衡程度:______
|
||||||
|
- 原因:______
|
||||||
|
|
||||||
|
### 5.2 优化方案
|
||||||
|
|
||||||
|
#### 5.2.1 优化方案一:改进分块策略
|
||||||
|
|
||||||
|
**问题描述**:带状分块导致负载不均衡
|
||||||
|
|
||||||
|
**优化方法**:
|
||||||
|
- 采用二维块循环分块
|
||||||
|
- 优化分块大小以适应缓存
|
||||||
|
- 考虑NUMA架构
|
||||||
|
|
||||||
|
**预期效果**:效率提升______%
|
||||||
|
|
||||||
|
#### 5.2.2 优化方案二:通信优化
|
||||||
|
|
||||||
|
**问题描述**:通信开销过大
|
||||||
|
|
||||||
|
**优化方法**:
|
||||||
|
- 使用非阻塞通信重叠计算和通信
|
||||||
|
- 减少通信次数,增加每次通信的数据量
|
||||||
|
- 优化通信模式
|
||||||
|
|
||||||
|
**预期效果**:通信时间减少______%
|
||||||
|
|
||||||
|
#### 5.2.3 优化方案三:计算优化
|
||||||
|
|
||||||
|
**问题描述**:计算效率不高
|
||||||
|
|
||||||
|
**优化方法**:
|
||||||
|
- 使用分块矩阵乘法提高缓存利用率
|
||||||
|
- 使用SIMD指令向量化
|
||||||
|
- 循环展开和优化
|
||||||
|
|
||||||
|
**预期效果**:计算时间减少______%
|
||||||
|
|
||||||
|
### 5.3 优化实现与效果对比
|
||||||
|
|
||||||
|
#### 5.3.1 优化前后性能对比
|
||||||
|
|
||||||
|
| 配置 | 优化前时间(ms) | 优化后时间(ms) | 加速比 |
|
||||||
|
|------|---------------|---------------|--------|
|
||||||
|
| 4 MPI进程, 512×512 | ______ | ______ | ______ |
|
||||||
|
| 9 MPI进程, 1024×1024 | ______ | ______ | ______ |
|
||||||
|
| 16 MPI进程, 2048×2048 | ______ | ______ | ______ |
|
||||||
|
|
||||||
|
#### 5.3.2 优化效果分析
|
||||||
|
|
||||||
|
1. **分块优化**:
|
||||||
|
- 效果:______
|
||||||
|
- 原因:______
|
||||||
|
|
||||||
|
2. **通信优化**:
|
||||||
|
- 效果:______
|
||||||
|
- 原因:______
|
||||||
|
|
||||||
|
3. **计算优化**:
|
||||||
|
- 效果:______
|
||||||
|
- 原因:______
|
||||||
|
|
||||||
|
## 6. 实验总结
|
||||||
|
|
||||||
|
### 6.1 实验结论
|
||||||
|
|
||||||
|
1. **MPI并行效果**:
|
||||||
|
- MPI并行能够有效加速矩阵乘法
|
||||||
|
- 最优MPI进程数与______相关
|
||||||
|
- 加速比受______限制
|
||||||
|
|
||||||
|
2. **OpenMP并行效果**:
|
||||||
|
- OpenMP适合______并行
|
||||||
|
- 最优线程数与______相关
|
||||||
|
- 与MPI结合可以______
|
||||||
|
|
||||||
|
3. **混合并行优势**:
|
||||||
|
- 混合并行能够______
|
||||||
|
- 适合______场景
|
||||||
|
- 需要仔细调优______
|
||||||
|
|
||||||
|
### 6.2 心得体会
|
||||||
|
|
||||||
|
1. 通过本次实验,我掌握了______
|
||||||
|
2. 在并行程序设计中,______很重要
|
||||||
|
3. 性能优化需要考虑______
|
||||||
|
4. 遇到的主要困难是______,解决方法是______
|
||||||
|
|
||||||
|
### 6.3 改进建议
|
||||||
|
|
||||||
|
1. 程序可以进一步优化______
|
||||||
|
2. 实验设计可以改进______
|
||||||
|
3. 性能分析可以更深入______
|
||||||
|
|
||||||
|
## 7. 参考文献
|
||||||
|
|
||||||
|
1. MPI论坛. MPI: A Message-Passing Interface Standard.
|
||||||
|
2. OpenMP Architecture Review Board. OpenMP Specifications.
|
||||||
|
3. Grama, A., et al. Introduction to Parallel Computing.
|
||||||
|
4. 其他相关资料
|
||||||
|
|
||||||
|
## 附录
|
||||||
|
|
||||||
|
### 附录A:完整源代码
|
||||||
|
|
||||||
|
(在此处附上关键代码片段或完整代码)
|
||||||
|
|
||||||
|
### 附录B:实验原始数据
|
||||||
|
|
||||||
|
(在此处附上CSV格式的原始数据)
|
||||||
|
|
||||||
|
### 附录C:性能分析图表
|
||||||
|
|
||||||
|
(在此处附上所有生成的图表)
|
||||||
Loading…
x
Reference in New Issue
Block a user