SRQ – 24 解题过程
本文最后更新于24 天前,其中的信息可能已经过时,如有错误请发送邮件到zhangweihao22@outlook.com

-1 注意事项

【这些是我配置的问题?】

真相!(9.28)
所有问题都归结为虚拟机的ip地址发生改变
解决方案:
重新在终端使用命令:ip a(还是ip_a?)

  1. 远程链接虚拟机的时候,本地的ip不要改变(比如更改代理【还有更改wifi?】
    不然就会出现无法连接到虚拟机的报错
  2. 远程连接虚拟机的时候,虚拟机不能处于休眠状态
  3. Rocky Linux是CentOS的版本,安装open mpi 的时候有命令字符的差别

0 程序的运行

建立在环境以及虚拟机已经配置好的基础上

首先链接虚拟机,注意,一定要在连接网络的基础上进行,否则可能会报错无法连接虚拟机(好奇怪?)
然后打开文件夹,注意,直接点最上面的…,然后加载计算机(还是虚拟机)上面的所有文件
然后打开terminal
然后最重要的是,在终端先输入
# cd home
然后输入
# ls
接下来输入
# cd SRQ
最后输入运行指令
# sh run.sh

1 阅读解释文档

1.1 用GPT询问solve.cpp里面程序的含义

这个代码实现了一个矩阵乘法的简单实现,下面是代码的逐步解释:

[!note]

  1. 宏定义?
  2. 计算的过程似乎没问题,不需要去优化?
  3. 偏移量?(不管,是矩阵计算中涉及到的,和优化应该没关系【但是后面学到矩阵计算的代码实现的时候可以留一下这个】)
  4. GPT总结中的“传统的三重循环矩阵乘法”,是不是意味着这个矩阵计算的方法也可以优化?
  5. 在学长的直播演示中引用了“一坨”代码,然后运行就快了很多,后面可以往这方面想,接下来可以去了解下那一坨东西是什么

基本概念:

矩阵乘法的基本原理是:

  • 如果有矩阵 ( A )(尺寸 ( M \times K ))和矩阵 ( B )(尺寸 ( K \times N )),矩阵乘法的结果 ( C ) 会是一个尺寸 ( M \times N ) 的矩阵。
  • 对于 ( C ) 矩阵中的每个元素 ( C(i,j) ),它的值是 ( A ) 的第 ( i ) 行和 ( B ) 的第 ( j ) 列对应元素的点积。

代码详细解析:

#define A(i,j) A[(i)*lda+(j)]
#define B(i,j) B[(i)*ldb+(j)]
#define C(i,j) C[(i)*ldc+(j)]
  • 这三行宏定义用于方便矩阵元素的访问。假设矩阵 ( A ), ( B ), 和 ( C ) 是存储在一维数组中的,那么通过这些宏定义可以像访问二维数组一样访问矩阵元素。
  • A(i,j) 表示访问矩阵 ( A ) 中第 ( i ) 行、第 ( j ) 列的元素。实际上是通过一维数组计算位置:A[i * lda + j]。其中 lda 是矩阵 ( A ) 的列数,即列的步长。
  • 同理,B(i,j)C(i,j) 用于访问矩阵 ( B ) 和 ( C ) 中的元素。

矩阵乘法的主循环:

void User_matMult(float* A, float* B, float* C, int M, int K, int N, int lda, int ldb, int ldc){
    for (int i = 0; i < M; i++)
    {
        for (int j = 0; j < N; j++)
        {
            for (int k = 0; k < K; k++)
            {
                C(i,j) += A(i,k) * B(k,j);
            }
        }
    }
}
  • float* A, float* B, float* C:指向矩阵 ( A ), ( B ), 和 ( C ) 的指针。它们实际上是通过一维数组存储的矩阵。
  • int M, int K, int N:代表矩阵的维度:
  • ( M ):矩阵 ( A ) 和 ( C ) 的行数。
  • ( K ):矩阵 ( A ) 的列数和矩阵 ( B ) 的行数。
  • ( N ):矩阵 ( B ) 和 ( C ) 的列数。
  • int lda, ldb, ldc:分别代表矩阵 ( A ), ( B ), 和 ( C ) 的列步长(列的宽度)。这是矩阵在内存中的存储方式,每行的偏移量是通过 i * lda 来计算的。

核心计算部分:

  1. 外层循环 (for (int i = 0; i < M; i++)):
  • 这表示遍历矩阵 ( C ) 的每一行。
  1. 中间层循环 (for (int j = 0; j < N; j++)):
  • 这表示遍历矩阵 ( C ) 的每一列。
  • 对于每个元素 ( C(i, j) ),它需要计算 ( A ) 的第 ( i ) 行和 ( B ) 的第 ( j ) 列的点积。
  1. 内层循环 (for (int k = 0; k < K; k++)):
  • 这是矩阵乘法的核心步骤,用于计算 ( A ) 的第 ( i ) 行和 ( B ) 的第 ( j ) 列的点积。具体是将 ( A(i,k) ) 和 ( B(k,j) ) 的乘积累加到 ( C(i,j) ) 中。
  • 具体来说,( C(i,j) += A(i,k) * B(k,j) ) 表示不断累加 ( A(i,k) ) 和 ( B(k,j) ) 的乘积。

矩阵乘法流程:

  1. 初始化:矩阵 ( C ) 中的所有元素初始为 0。
  2. 遍历矩阵 ( A ) 和矩阵 ( B )
  • 对于 ( C(i,j) ) 来说,它是通过遍历 ( A(i,k) ) 和 ( B(k,j) ) 的乘积累加得到的。
  1. 存储结果:最终计算的结果会存储在矩阵 ( C ) 的相应位置。

总结:

  • 该代码实现了传统的三重循环矩阵乘法。对于每个结果矩阵 ( C(i,j) ),计算时会累加矩阵 ( A ) 的第 ( i ) 行与矩阵 ( B ) 的第 ( j ) 列的点积。
  • 代码使用了一维数组来表示矩阵,并通过 lda, ldb, ldc 参数控制步长,适应了实际应用中常见的矩阵存储方式。

1.2 对比bese与sovle里面的代码差异

1.2.1#include “solve.hpp”

[!note]
内容:
copy另一个文件内的代码
copy的文件和此文件都必须在同一个项目内

#include "solve.hpp" 是一个预处理指令,用于在编译前将 solve.hpp 文件的内容包含到当前的源代码文件中。

具体解释:

  1. #include 预处理指令
  • 这是 C/C++ 中的一个指令,告诉编译器在编译当前文件之前,把指定的头文件(header file)的内容插入到这个位置。
  • 它相当于把 solve.hpp 文件中的代码复制到 #include "solve.hpp" 的位置上。
  1. "solve.hpp":
  • 引号 " 表示引用当前项目或自定义的头文件,而不是标准库中的头文件。编译器会在当前项目的目录中查找这个文件。
  • .hpp 是 C++ 头文件的常见扩展名,通常用于声明函数、类和变量的接口(但不包含它们的具体实现)。在这个文件中,通常会有函数的声明,而实际的实现代码可能在 solve.cpp 中。

头文件的作用:

  • 头文件的主要作用是声明函数、类、结构等,这些声明允许多个源文件共享同样的接口,而具体的实现则通常位于 .cpp.c 文件中。
  • 例如,solve.hpp 中可能包含一些函数的声明,例如 User_matMult,而这些函数的具体实现是在 solve.cpp 文件中。

在这个代码中,solve.hpp 可能包含了与矩阵乘法相关的函数声明、常量、宏定义等,供 main.cpp 文件使用。

例子:

假设 solve.hpp 文件中包含以下内容:

// solve.hpp
#ifndef SOLVE_HPP
#define SOLVE_HPP

void User_matMult(float* A, float* B, float* C, int M, int K, int N, int lda, int ldb, int ldc);

#endif

这只是声明了 User_matMult 函数,而它的具体实现可能是在 solve.cpp 文件中。

1.2.1多的两个函数声明与中间变量tem

但是为什么没有对应的函数的定义?
void random_matrix(int m, int n, float *C, int ldc);
void compare_matrices(int m, int n, float *a, int lda, float *b, int ldb);

float temp;
temp = A(i,j);

1.3 用GPT询问baseline里面的代码含义

这段代码定义了一个矩阵乘法的实现,并且还使用了预处理宏来简化矩阵的索引方式。它主要包括了三个部分:宏定义、函数声明、以及矩阵乘法的实现。

代码解释

1. 宏定义部分

#define A(i,j) A[(i)*lda+(j)]
#define B(i,j) B[(i)*ldb+(j)]
#define C(i,j) C[(i)*ldc+(j)]

这三行宏定义的作用是简化矩阵的索引方式。通常在矩阵运算中,二维矩阵保存在一维数组中(行优先或列优先),访问元素时需要手动计算每个元素的线性偏移。宏定义将这种繁琐的操作抽象化,使代码更易读。

  • A(i,j) 实际上是访问矩阵 A 中第 i 行、第 j 列的元素,它通过表达式 A[(i)*lda + (j)] 计算出一维数组中对应的线性位置。
  • lda 是 leading dimension(主维度),即矩阵 A 的列数或步长(表示每一行在内存中的偏移量)。
  • 类似的,B(i,j)C(i,j) 分别表示访问矩阵 BC 中的对应位置。

2. 函数声明部分

void random_matrix(int m, int n, float *C, int ldc);
void compare_matrices(int m, int n, float *a, int lda, float *b, int ldb);
  • random_matrix 函数声明:
  • 用于生成一个 m x n 的随机矩阵,并将结果存储在 C 指向的内存中,ldcC 的列步长。
  • compare_matrices 函数声明:
  • 用于比较两个矩阵 ab 的元素是否一致,mn 是矩阵的行数和列数,ldaldb 分别是两个矩阵的列步长。

3. 矩阵乘法的实现 Base_matMult

void Base_matMult(float* A, float* B, float* C, int M, int K, int N, int lda, int ldb, int ldc) {
    float temp;
    for (int i = 0; i < M; i++) {
        for (int j = 0; j < K; j++) {
            temp = A(i,j);
            for (int k = 0; k < N; k++) {
                C(i,k) += temp * B(j,k);
            }
        }
    }
}

这个函数实现了矩阵 AB 的乘法,并将结果存储在矩阵 C 中。参数解释如下:

  • float* A, float* B, float* C: 这三个参数分别是矩阵 ABC 的指针,用于传递矩阵的内存地址。
  • int M, K, N: 矩阵的维度。AM x KBK x NCM x N 的矩阵。
  • int lda, ldb, ldc: 这些参数是矩阵 ABC 的列步长(leading dimension),即它们在内存中每行的跨度。
矩阵乘法过程:
  • 外层循环 for (int i = 0; i < M; i++) 遍历矩阵 A 的行数。
  • 中间循环 for (int j = 0; j < K; j++) 遍历矩阵 A 的列数和矩阵 B 的行数(AB 的共同维度 K)。
  • 在这个循环中,temp = A(i,j) 取出 A 矩阵中第 i 行、第 j 列的值。
  • 内层循环 for (int k = 0; k < N; k++) 遍历矩阵 B 的列数,将 A(i,j)B(j,k) 相乘,并将结果累加到 C(i,k) 中。

最终实现的效果是,矩阵 C 中的元素 C(i,k) 被累加了所有对应的 A(i,j) * B(j,k) 的乘积,即完成了矩阵乘法。

矩阵乘法的数学表示:

C = A * B,其中:

  • AM x K 矩阵
  • BK x N 矩阵
  • CM x N 矩阵

C(i,k) 的值是矩阵 A 的第 i 行与矩阵 B 的第 k 列相乘的结果:

[
C(i,k) = \sum_{j=0}^{K-1} A(i,j) * B(j,k)
]

总结

这段代码实现了一个常规的矩阵乘法,通过使用宏简化矩阵元素的访问,并且利用三重循环完成了 A * B = C 的计算。

1.4 omp 与 mpi以及并行化

[!note] 要点
并行就有点像多线程操作

并行化是指将一个计算任务分解为多个小任务,交给多个处理器或处理单元同时执行,从而加速任务的完成。并行计算可以显著提高大规模任务(如矩阵运算、数据处理等)的效率,尤其在科学计算、模拟和大数据处理领域具有广泛的应用。

在并行计算中,OpenMP (OMP)MPI 是两种常见的并行编程模型,它们分别适用于不同类型的计算环境。

1. OpenMP (OMP)

OpenMP 是一种用于在共享内存系统中实现并行编程的编程接口。它通过简化的方式让开发者在多核 CPU 上进行并行计算。OpenMP 主要通过在代码中添加编译指令(pragma 指令)来实现多线程并行化。

OpenMP 的特点

  • 共享内存:OpenMP 适用于共享内存架构,即所有线程共享同一块内存区域。
  • 多线程并行:程序的某些部分可以通过指令并行执行(如 #pragma omp parallel for 让循环并行运行)。
  • 易于使用:OpenMP 使用编译器指令(pragma)来指示并行操作,开发者不需要管理线程的创建和销毁。
  • 常用于:多核 CPU,适合那些能够在单台机器上并行执行的应用。

OpenMP 示例

#pragma omp parallel for
for (int i = 0; i < n; i++) {
    // 这个循环会被多个线程并行执行
}

上面的代码在循环体中插入了 #pragma omp parallel for 指令,表示该循环可以被多个线程同时执行,每个线程会处理部分循环体。

2. MPI (Message Passing Interface)

MPI 是一种用于分布式内存系统的并行编程模型,通常在多台机器(如计算集群)之间进行并行计算。它通过消息传递的方式让多个进程之间进行通信与协作。与 OpenMP 不同,MPI 适用于分布式内存架构,即每个处理器都有自己的独立内存。

MPI 的特点

  • 分布式内存:MPI 适用于每个处理器或计算节点都有自己独立内存的环境,处理器之间通过网络进行通信。
  • 消息传递:进程之间通过显式的消息传递进行数据交换(如发送和接收)。
  • 更灵活:MPI 可以运行在不同节点之间,无需共享内存的假设。
  • 常用于:高性能计算集群(如超级计算机、云计算集群),尤其适合那些需要跨多台计算机执行的大规模任务。

MPI 示例

MPI_Init(NULL, NULL);    // 初始化 MPI 环境
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);  // 获取进程数
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);  // 获取当前进程 ID

// 各进程可以通过 MPI_Send 和 MPI_Recv 函数进行通信
MPI_Finalize();  // 结束 MPI 环境

在 MPI 中,多个进程会同时执行同一份代码,每个进程都有一个唯一的编号(rank),用于区分它们。进程之间可以通过 MPI_SendMPI_Recv 等函数交换数据。

3. 并行化的概念

并行化是通过将计算任务分解成独立的部分,交给多个计算单元(如 CPU 核心、GPU 核心、集群中的节点等)同时执行,从而减少总的执行时间。主要的目标是提高计算性能处理大数据以及解决复杂问题

并行化的类型

  1. 数据并行:数据被分成多个子集,每个子集由一个处理单元来处理。例如,一个大的数组可以分给多个线程分别计算。
  2. 任务并行:不同的任务或函数被分配给不同的处理单元并行执行。
  3. 管道并行:任务分为多个阶段,不同阶段同时在不同的处理单元上运行。

4. OpenMP 与 MPI 的对比

特性OpenMPMPI
内存模型共享内存(单机多核)分布式内存(多机多节点)
适用环境多核 CPU 系统高性能计算集群或多台机器
并行单元线程进程
通信机制线程间共享内存直接访问进程间通过消息传递通信
编程复杂度简单,易于调试和管理较复杂,需管理通信与同步
适用场景单台机器上的并行计算,如图像处理、数值计算大规模集群计算,如气候建模、大数据分析

5. 并行化的好处

  • 加速计算:多个任务并行执行可以大幅缩短计算时间。
  • 资源利用最大化:有效利用多核、多节点计算资源。
  • 处理大规模问题:并行化能处理原本单线程无法处理的大规模问题。

总结来说,OpenMP 适用于共享内存环境(如多核 CPU),而MPI 适用于分布式内存环境(如计算集群)。两者的结合可以在不同硬件环境下实现高效的并行计算。

1.5 linux虚拟机怎么使用omp 与 mpi

[!note] 总结
omp(单机多核【多线程】)相对而言比较简单,可以直接使用。已经使用
mpi要进行相应的配置(分布式内存系统【也就是多机连用】)

Linux 虚拟机能够使用 OpenMP 和 MPI 进行并行计算。你可以在 Linux 虚拟机上配置相关的开发环境,并运行基于 OpenMP 和 MPI 的并行程序。以下是它们的基本配置和使用方法:

1. 使用 OpenMP 在 Linux 虚拟机上进行并行计算

OpenMP 安装与配置

OpenMP 是基于编译器的编程模型,大多数现代编译器(如 GCC)都支持 OpenMP。一般情况下,你不需要额外安装 OpenMP,只要使用支持 OpenMP 的编译器即可。

GCC 编译支持 OpenMP

GCC 默认支持 OpenMP,编译时只需要加上 -fopenmp 选项即可。例如:

gcc -fopenmp -o my_program my_program.c
  • 代码示例
#include <omp.h>
#include <stdio.h>

int main() {
    #pragma omp parallel
    {
        printf("Hello from thread %d\n", omp_get_thread_num());
    }
    return 0;
}

编译并运行:

gcc -fopenmp -o hello hello.c
./hello

OpenMP 常见功能

  • omp_set_num_threads(int):设置使用多少个线程。
  • #pragma omp parallel:并行化区域。
  • omp_get_thread_num():获取当前线程的 ID。

2. 使用 MPI 在 Linux 虚拟机上进行并行计算

MPI 安装与配置

MPI 在 Linux 系统上通过一些常用的 MPI 实现工具来安装,如 MPICHOpenMPI。这两个库都广泛应用于高性能计算环境。

安装 OpenMPI

在 Ubuntu 或其他基于 Debian 的 Linux 发行版上,您可以通过包管理器安装 OpenMPI:

sudo apt-get update
sudo apt-get install openmpi-bin openmpi-common libopenmpi-dev

MPI 程序编写与运行

编写一个简单的 MPI 程序,例如:

#include <mpi.h>
#include <stdio.h>

int main(int argc, char** argv) {
    MPI_Init(&argc, &argv);

    int world_size;
    MPI_Comm_size(MPI_COMM_WORLD, &world_size);  // 获取进程数

    int world_rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);  // 获取当前进程 ID

    printf("Hello from process %d out of %d processes\n", world_rank, world_size);

    MPI_Finalize();
    return 0;
}

编译并运行:

mpicc -o mpi_hello mpi_hello.c
mpirun -np 4 ./mpi_hello

这将使用 4 个进程运行该程序,每个进程会输出它的进程 ID 和总进程数。

3. 虚拟机使用 OMP 和 MPI 的要求

  • 多核支持:OpenMP 依赖多核系统,因此确保虚拟机配置了多核 CPU。虚拟机管理器(如 VirtualBox 或 VMware)中可以设置虚拟机的处理器核数。
  • 网络支持:MPI 在多节点通信中发挥作用时需要网络配置。如果要在不同虚拟机间运行 MPI 程序,需要配置它们的网络连接(例如同一局域网中)。但是,在单机上运行 MPI 也无需网络,只需配置好 MPI 环境。

4. 混合使用 OpenMP 和 MPI

在一些高性能计算环境中,OpenMP 和 MPI 可以混合使用:在单个节点上使用 OpenMP 来实现多线程,并在多个节点之间使用 MPI 来实现进程间通信。这种方法能够充分利用多节点集群的资源和多核处理器的性能。

结论:

  • OpenMPMPI 都可以在 Linux 虚拟机中运行。对于 OpenMP,只需安装 GCC 或其他支持 OpenMP 的编译器;对于 MPI,可以使用 MPICH 或 OpenMPI 这样的实现工具。

2 阅读教学文档

2.1 初略观看

  1. 分布优化之前的“建立”部分好像是教授性能绘图软件的配置,和优化无关,大概率可以不看

3 了解基本并行内容

3.1 MIP

优点:

  1. 能够精确分配内存,最大限度发觉处理器性能(?)
  2. 可以多节点运行(可以集成算力?)
    缺点:
  3. 对小白很不友好,需要配置环境,还需要使用一大堆函数返回、传递信息

3.2 OMP

仅适用于公用内存的….
优点:

  1. 对小白非常友好,只需要引用头文件,再在需要优化的部分使用一段语句+指令限制
    缺点:
  2. 暂时没看到

4 安装open mpi

在 Linux 系统下配置 MPI(Message Passing Interface)环境,通常以 OpenMPI 或 MPICH 作为实现方式。以下是以 OpenMPI 为例的配置步骤:

1. 安装 OpenMPI

使用包管理器安装

根据你的 Linux 发行版,使用合适的包管理器安装 OpenMPI。

  • Ubuntu/Debian:
  sudo apt update
  sudo apt install openmpi-bin openmpi-common libopenmpi-dev
  • CentOS/Fedora:
  sudo yum install openmpi openmpi-devel

使用源码编译安装

如果你需要安装特定版本的 OpenMPI,可以通过源码编译:

  1. 下载 OpenMPI 源码:
   wget https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-4.1.2.tar.gz
  1. 解压并进入源码目录:
   tar -xvf openmpi-4.1.2.tar.gz
   cd openmpi-4.1.2
  1. 配置、编译和安装:
   ./configure --prefix=/usr/local/openmpi
   make -j4
   sudo make install
  1. 更新环境变量:
    编辑 ~/.bashrc~/.bash_profile 文件,加入以下内容:
   export PATH=/usr/local/openmpi/bin:$PATH
   export LD_LIBRARY_PATH=/usr/local/openmpi/lib:$LD_LIBRARY_PATH

然后执行以下命令使修改生效:

   source ~/.bashrc

2. 验证 MPI 安装

安装完成后,可以通过以下命令验证 MPI 是否正确安装:

mpicc --version
mpirun --version

如果这些命令输出版本信息,说明安装成功。

3. 编译 MPI 程序

使用 mpicc 编译你的 MPI 程序。例如,假设你有一个 hello_mpi.c 程序:

mpicc -o hello_mpi hello_mpi.c

4. 运行 MPI 程序

使用 mpirunmpiexec 运行编译好的程序。例如,使用 4 个进程运行:

mpirun -np 4 ./hello_mpi

5. 配置主机文件(可选,适用于多节点运行)

如果需要在多节点上运行 MPI 程序,需要配置一个主机文件(例如 hosts 文件),包含所有节点的 IP 地址或主机名:

node1 slots=4
node2 slots=4

然后通过以下命令运行:

mpirun -np 8 --hostfile hosts ./hello_mpi

6. 其他

  • 如果有防火墙配置,确保开放相关的端口。
  • 确保各节点之间可以通过 SSH 无密码登录。

这样,你就可以在 Linux 系统上配置和运行 MPI 环境了。

2024/10/7 创建
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇
Copyright 2025-2025 @ Ziyang
Running Time days H M S