在华为云服务器上测试GCC for OpenEuler的特性

前言

操作系统课程任务 探讨 GCC for openEuler 的特性和优势

什么是 GCC for openEuler?

GCC for openEuler 基于开源 GCC-10.3 版本(GCC 官网,2021年4月发行)开发,并进行了优化和改进,实现软硬件深度协同优化,挖掘 OpenMP、SVE 向量化、数学库等领域极致性能。它是一种 Linux 下针对鲲鹏920处理器的高性能编译器。GCC for openEuler 默认使用场景为 TaiShan 服务器、鲲鹏920 处理器、Arm 架构,操作系统为 CentOS 7.6、openEuler 22.03、openEuler 20.03 等。

实验步骤

步骤1:购买并配置华为云服务器

1.1 注册华为云账号

  1. 访问华为云官网:打开浏览器,访问 华为云官网
  2. 注册账号
    • 点击页面右上角的“注册”按钮。
    • 按照提示填写必要的信息(邮箱、密码、验证码等)完成注册。
    • 可能需要验证邮箱,请按照邮件中的指示完成验证。

1.2 登录华为云控制台

  1. 登录账号:使用刚注册的账号和密码登录华为云控制台。
  2. 实名认证
    • 如果是首次登录,可能需要进行实名认证。按照提示提交相关身份证明文件,完成实名认证。

1.3 创建虚拟私有云 (VPC)

创建云服务器需要先创建虚拟私有云。

VPC 创建

注意:区域选择华北北京四,下一步也是。

1.4 创建弹性云服务器(ECS)

  1. 进入 ECS 控制台

    • 登录后,点击页面顶部导航栏中的“产品”。
    • 在下拉菜单中选择“弹性云服务器”。
  2. 创建服务器

    • 点击“创建弹性云服务器”按钮。
  3. 选择区域和可用区

    • 区域:选择华北-北京四。
    • 可用区:选择可用区1。

    img

  4. 选择规格

    • 根据需求选择合适的实例规格。

    • 推荐配置

      • CPU:至少4核。
      • 内存:8GB及以上。
    • 示例配置

      规格选择

  5. 选择镜像

    • 推荐选择市场镜像,选择 openEuler 22.03-LTS-SP2,点击选择。

      img
      img
      img

  6. 存储配置

    • 根据需求选择系统盘和数据盘的大小。

    • 默认配置通常已足够,或者设置为50G。

      img

  7. 选择网络

    • 选择已有的网络或创建新的 VPC(虚拟私有云)。

      网络选择

    • 虚拟私有云是前面一步创建的。

    • 确保服务器具有公网 IP 地址,以便从 Windows 远程连接。

  8. 安全组配置

    • 安全组:选择默认安全组或创建新的安全组。

    • 选择通用的安全组即可。

    • 开放端口:确保至少开放 22 端口(SSH)。

      img

  9. 公网访问

    如图配置

    img

  10. 云服务器管理配置密码

    • 登录方式:选择“密码登录”。

    • 设置密码:输入并确认 root 用户的密码。请记住此密码。

    • 名称自己取

      img

  11. 确认订单并创建

    • 检查所有配置是否正确。
    • 点击“立即购买”完成服务器创建。
  12. 获取服务器信息

    • 创建完成后,在 ECS 管理列表中找到新创建的服务器。

    • 记录下服务器的 公网 IP 地址

      img
      img


步骤2:连接到服务器

你可以使用 PuTTYWindows 自带的 SSH 客户端 连接到你的服务器。以下以 PuTTY 为例进行说明。

2.1 下载并安装 PuTTY

img

  1. 下载 PuTTY

    • 访问 PuTTY 官网
    • 下载适用于 Windows 的最新版本 PuTTY 安装包。
  2. 安装 PuTTY

    • 双击下载的安装包,按照提示完成安装。

2.2 使用 PuTTY 连接服务器

  1. 打开 PuTTY

    • 安装完成后,双击桌面上的 PuTTY 图标打开程序。
  2. 配置连接

    • Host Name (or IP address):输入你的服务器公网 IP 地址(如 123.45.67.89)。
    • Port:默认是 22
    • Connection type:选择 SSH
  3. 保存会话(可选):

    • 在“Saved Sessions”字段输入一个名称,如 euler

    • 点击“Save”按钮,方便下次快速连接。

      img

  4. 连接

    • 点击“Open”按钮。
  5. 接受安全警告

    • 第一次连接时,PuTTY 会弹出一个安全警告窗口,点击“Yes”继续。
  6. 登录

    • Login as:输入 root,按 Enter

    • Password:输入你在创建服务器时设置的密码(输入时不会显示),按 Enter

      登录

    注意:如果密码输入错误,请重新尝试或检查服务器设置。

    可能会遇到的问题

    • 没有密码登录方式

      img

      这是因为 sshd_config 文件中的 PasswordAuthentication 设置为 no,需要修改为 yes。在华为云控制台中找到服务器,点击右侧的管理,然后点击远程登录。

      img

      随后可以在这个页面通过密码登录:

      img

      然后打开 /etc/ssh/sshd_config 文件,找到 PasswordAuthentication no,修改为 yes

      vi /etc/ssh/sshd_config
      

      img
      img

      然后按 i 进入编辑模式,找到 PasswordAuthentication no,修改为 yes,同时 PermitRootLogin 也修改为 yes,注意这里一共修改三个位置。

      然后重启配置文件服务:

      sudo systemctl restart sshd
      service sshd restart
      

      之后就可以通过密码登录了。


步骤3:获取 GCC for openEuler 软件包到服务器

3.1 从本机上传

华为云镜像下载对应镜像到本地。

wget 下载

为了将本地的 gcc-12.3.1-2024.09-aarch64-linux.tar.gz 上传到服务器,可以使用 WinSCP 工具。

3.1.1 下载并安装 WinSCP

  1. 下载 WinSCP

    • 访问 WinSCP 官网
    • 下载最新版本的 WinSCP 安装包。
  2. 安装 WinSCP

    • 双击下载的安装包,按照提示完成安装。

3.1.2 使用 WinSCP 上传文件

  1. 打开 WinSCP

    • 安装完成后,启动 WinSCP。
  2. 配置连接

    • File protocol:选择 SFTP
    • Hostname:输入你的服务器公网 IP 地址(如 123.45.67.89)。
    • Port number:默认是 22
    • Username:输入 root
    • Password:输入你在创建服务器时设置的密码。
  3. 连接服务器

    • 点击“Login”按钮连接服务器。
    • 如果是第一次连接,可能会提示确认服务器的主机密钥,点击“是”继续。
  4. 上传文件

    • 左侧窗口:显示你本地的文件系统。

    • 右侧窗口:显示服务器的文件系统。

    • 在左侧导航到你下载的 gcc-12.3.1-2024.09-aarch64-linux.tar.gz 文件所在的本地目录。

    • 在右侧服务器端,导航到 /opt/aarch64/compiler 目录。如果该目录不存在,可以先在服务器端创建。

      创建目录

      • 在服务器端窗口中,右键点击空白区域,选择 “New” > “Directory”。
      • 输入目录名 compiler,点击 “OK”。
      • 重复上述操作在 /opt/aarch64 下创建 compiler 目录(如果尚未存在)。
    • gcc-12.3.1-2024.09-aarch64-linux.tar.gz 文件从左侧窗口拖拽到右侧窗口的 /opt/aarch64/compiler 目录中。

  5. 确认上传

    • 上传完成后,在服务器端的 /opt/aarch64/compiler 目录下应该能看到 gcc-12.3.1-2024.09-aarch64-linux.tar.gz 文件。

3.2 使用 wget 下载

3.2.1 创建目录

mkdir -p /opt/aarch64/compiler
cd /opt/aarch64/compiler

然后使用 wget 下载,所需网址来自 华为云镜像

wget 下载

按日期排序,选中最新的版本,然后复制链接地址,使用 wget 下载:

# 下载 gcc-12.3.1-2024.09-aarch64-linux.tar.gz,当前目录为 /opt/aarch64/compiler
wget https://mirrors.huaweicloud.com/gcc/releases/gcc-12.3.1-2024.09/gcc-12.3.1-2024.09-aarch64-linux.tar.gz -P /opt/aarch64/compiler

3.2.2 完整性校验

下载 sha256sum 文件:

下载 sha256sum

复制链接地址,使用 wget 下载:

# 下载 sha256sum 文件
wget https://mirrors.huaweicloud.com/kunpeng/archive/compiler/kunpeng_gcc/gcc-12.3.1-2024.09-aarch64-linux.tar.gz.sha256

然后进行校验:

sha256sum -c gcc-12.3.1-2024.09-aarch64-linux.tar.gz.sha256

如果文件完整,会显示如下信息:

gcc-12.3.1-2024.09-aarch64-linux.tar.gz: OK

注意:如果下载的版本与示例不同,相应的文件名也要改变,这里只是一个示例。


步骤4:安装 GCC for openEuler

4.1 创建安装目录

  1. 登录到服务器

    • 使用 PuTTY 登录到你的服务器(参考步骤2)。
  2. 创建安装目录
    如果未创建安装目录,执行以下命令:

    mkdir -p /opt/aarch64/compiler
    
  3. 进入安装目录

    cd /opt/aarch64/compiler
    

4.2 将软件包上传到服务器

注意:如果已经通过 WinSCP 上传文件到 /opt/aarch64/compiler 或通过 wget 下载到该目录,可以跳过此步骤。

4.3 解压缩软件包

  1. 查看当前目录内容

    ls
    
    • 确认 gcc-12.3.1-2024.09-aarch64-linux.tar.gz 文件存在。
  2. 解压缩软件包

    tar -xf gcc-12.3.1-2024.09-aarch64-linux.tar.gz
    
    • tar:Unix 下常用的打包工具。
    • -x:解包。
    • -f:指定文件名。
  3. 查看解压后的内容

    ls
    
    • 应该看到一个名为 gcc-12.3.1-2024.09-aarch64-linux 的目录。

步骤5:配置环境变量

配置环境变量可以让系统识别新安装的 GCC 编译器。推荐使用编辑 /etc/profile 文件的方法,因为它更简单。

5.1 编辑 /etc/profile 文件

  1. 打开 /etc/profile 文件

    vi /etc/profile
    
    • vi:一种文本编辑器。
    • /etc/profile:系统级环境变量配置文件。
  2. 进入插入模式

    • 按下 i 键,进入插入模式。
  3. 添加环境变量配置
    在文件末尾添加以下内容:

    # GCC for openEuler 环境变量配置
    export PATH=/opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin:$PATH
    export INCLUDE=/opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/include:$INCLUDE
    export LD_LIBRARY_PATH=/opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/lib64:$LD_LIBRARY_PATH
    

    解释

    • PATH:告诉系统在执行命令时在哪些目录查找可执行文件。
    • INCLUDE:指定编译器的头文件目录。
    • LD_LIBRARY_PATH:指定系统在运行程序时查找共享库的路径。
    • 如果下载的版本不同,相应的路径也要改变。
  4. 保存并退出

    • 按下 Esc 键退出插入模式。
    • 输入 :wq,然后按 Enter 键保存并退出 vi 编辑器。

5.2 使环境变量生效

  1. 执行以下命令

    # 使配置生效,这个命令无需考虑当前目录在哪
    source /etc/profile
    
    • source:在当前 shell 中读取并执行指定文件中的命令。
    • 这样,无需重新登录,环境变量的更改立即生效。

5.3 验证环境变量配置

  1. 查看 PATH 环境变量

    echo $PATH
    
    • 确认输出中包含 /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin
  2. 查看 INCLUDE 环境变量

    echo $INCLUDE
    
    • 确认输出中包含 /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/include
  3. 查看 LD_LIBRARY_PATH 环境变量

    echo $LD_LIBRARY_PATH
    
    • 确认输出中包含 /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/lib64

5.4 注意事项

  • 安装路径不同:如果安装路径与上述不同,请相应修改环境变量中的路径。

    例如,如果安装在 /usr/local/gcc-12.3.1,则配置应为:

    export PATH=/usr/local/gcc-12.3.1/bin:$PATH
    export INCLUDE=/usr/local/gcc-12.3.1/include:$INCLUDE
    export LD_LIBRARY_PATH=/usr/local/gcc-12.3.1/lib64:$LD_LIBRARY_PATH
    
  • 顺序重要:确保新添加的路径在 $PATH 等变量的前面,否则系统会优先使用默认路径下的 GCC 版本。


步骤6:验证安装

6.1 检查 GCC 版本

  1. 执行以下命令

    # 当前目录还是 /opt/aarch64/compiler
    gcc -v
    
    • 应该看到类似如下的输出:

      Using built-in specs.
      COLLECT_GCC=gcc
      COLLECT_LTO_WRAPPER=/opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/libexec/gcc/aarch64-linux-gnu/12.3.1/lto-wrapper
      Target: aarch64-linux-gnu
      Configured with: ... (相关配置)
      Thread model: posix
      Supported LTO compression algorithms: zlib zstd
      gcc version 12.3.1 (gcc for openEuler 2.3.8)
      

    注意

    • gcc version 行应显示你安装的 GCC for openEuler 版本信息,如 gcc for openEuler 2.3.8
    • 如果显示的是其他版本,如系统默认的 GCC,请检查环境变量配置是否正确。

6.2 解决可能的问题

  • 命令未找到

    • 确认环境变量 PATH 是否正确配置,且包含了 GCC 的 bin 目录。
    • 确认 GCC 软件包是否正确上传并解压。
  • 版本不正确

    • 确认解压的目录是否正确,且环境变量指向正确的 GCC 安装路径。
    • 重新执行 source /etc/profile,确保环境变量已生效。

目前用到的命令简要说明

以下是安装过程中用到的一些常用命令及其说明,供参考:

  • mkdir -p /path/to/directory

    • 创建一个目录,包括必要的父目录。
    • 例如:mkdir -p /opt/aarch64/compiler
  • cd /path/to/directory

    • 切换当前工作目录到指定路径。
    • 例如:cd /opt/aarch64/compiler
  • ls

    • 列出当前目录下的文件和子目录。
    • 常用选项
      • -l:长格式显示,包含详细信息。
      • -a:显示所有文件,包括隐藏文件。
  • tar -xf file.tar.gz

    • 解压缩 .tar.gz 文件。
    • -x:解包。
    • -f:指定文件名。
  • vi /etc/profile

    • 使用 vi 编辑器打开 /etc/profile 文件。
    • 基本操作
      • i:进入插入模式。
      • Esc:退出插入模式。
      • :wq:保存并退出。
      • :q!:不保存强制退出。
  • echo $VARIABLE

    • 显示环境变量 VARIABLE 的值。
    • 例如:echo $PATH
  • source /etc/profile

    • 重新加载 /etc/profile 文件,使环境变量更改立即生效。
  • gcc -v

    • 显示 GCC 的版本信息。

步骤7:测试向量化

我们需要测试由系统默认 GCC 和 GCC for openEuler 编译的程序的性能差异。这需要分别使用两个版本的 GCC 编译相同的程序,并对比运行结果。

确认是否有默认的 GCC

# 执行下载 gcc
yum install gcc -y

发现结果如下:

安装 GCC

然后查看默认 GCC 是什么:

which gcc

发现结果如下:

查看默认 GCC

这说明系统中已经安装了一个较为标准的 GCC(版本为 10.3.1),但是目前默认 GCC 版本是 GCC for openEuler(12.3.1),并且这个编译器路径指向了 /opt/aarch64/compiler/...

目标:我们需要将两个版本的 GCC 区分开,以便在实验中分别使用它们。可以通过指定不同的路径来调用系统的 GCC 10.3.1 和 GCC for openEuler 12.3.1 进行对比实验。

区分两个版本的 GCC

目前 which gcc 返回的是 /opt/aarch64/compiler/...,这意味着系统路径被 GCC for openEuler 覆盖了。需要区分两个版本的 GCC。

直接调用 GCC 的完整路径

直接使用 GCC 编译器的完整路径来调用不同的版本,确保使用的是期望的 GCC 编译器。

  1. 系统标准 GCC(版本 10.3.1):

    • 系统 GCC 通常安装在 /usr/bin/gcc,可以通过直接调用完整路径来使用:

      /usr/bin/gcc --version
      

      这将显示系统标准 GCC 的版本。

  2. GCC for openEuler(版本 12.3.1):

    • 已知 GCC for openEuler 的路径是 /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc

    • 通过调用完整路径来使用这个版本:

      /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc --version
      

具体实验步骤

分别使用 系统标准 GCCGCC for openEuler 来编译和运行程序,并进行对比。

1. 创建并编辑矩阵乘法程序

  1. 创建或进入工作目录

    • 路径/home/gcc_experiment

      mkdir -p /home/gcc_experiment
      cd /home/gcc_experiment
      
  2. 创建 C 文件

    • 文件路径/home/gcc_experiment/matrix_mul.c

      vi matrix_mul.c
      
    • 在编辑器中粘贴以下代码:

      #include <stdio.h>
      #include <stdlib.h>
      #include <time.h>
      #include <omp.h>
      
      void multiply(int n, double **A, double **B, double **C) {
          int i, j, k;
          #pragma omp parallel for private(i, j, k) shared(A, B, C)
          for (i = 0; i < n; i++) {
              for (j = 0; j < n; j++) {
                  C[i][j] = 0.0;
                  for (k = 0; k < n; k++) {
                      C[i][j] += A[i][k] * B[k][j];
                  }
              }
          }
      }
      
      int main() {
          int i, j, n;
          double **A, **B, **C;
          clock_t start, end;
      
          for (n = 100; n <= 1500; n += 100) {
              // 动态分配内存
              A = (double **)malloc(n * sizeof(double *));
              B = (double **)malloc(n * sizeof(double *));
              C = (double **)malloc(n * sizeof(double *));
              for (i = 0; i < n; i++) {
                  A[i] = (double *)malloc(n * sizeof(double));
                  B[i] = (double *)malloc(n * sizeof(double));
                  C[i] = (double *)malloc(n * sizeof(double));
              }
      
              // 初始化矩阵
              for (i = 0; i < n; i++) {
                  for (j = 0; j < n; j++) {
                      A[i][j] = rand() / (double)RAND_MAX;
                      B[i][j] = rand() / (double)RAND_MAX;
                  }
              }
      
              // 矩阵乘法
              start = clock();
              multiply(n, A, B, C);
              end = clock();
      
              printf("Matrix size %d: %f seconds\n", n, ((double)(end - start)) / CLOCKS_PER_SEC);
      
              // 释放内存
              for (i = 0; i < n; i++) {
                  free(A[i]);
                  free(B[i]);
                  free(C[i]);
              }
              free(A);
              free(B);
              free(C);
          }
      
          return 0;
      }
      
    • 保存并退出(Esc -> :wq)。

2. 使用系统 GCC(10.3.1)进行编译和运行

  1. 编译程序

    • 路径:仍然在 /home/gcc_experiment

      /usr/bin/gcc matrix_mul.c -o matrix_mul_default -fopenmp
      
    • 这个命令使用系统默认的 GCC(10.3.1)进行编译,生成可执行文件 matrix_mul_default

  2. 运行程序并记录结果

    ./matrix_mul_default > default_results.txt
    
    • 该命令将运行结果保存到文件 default_results.txt 中。

3. 使用 GCC for openEuler(12.3.1)进行编译和运行

  1. 编译程序

    • 路径:继续在 /home/gcc_experiment

      /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc matrix_mul.c -o matrix_mul_optimized -fopenmp -O3
      
    • 使用 GCC for openEuler(12.3.1)进行编译,并且使用 -O3 进行优化。

  2. 运行程序并记录结果

    ./matrix_mul_optimized > optimized_results.txt
    
    • 运行结果保存到 optimized_results.txt 中。

多次测试取平均值

# euler 开启向量化:-ftree-vectorize
/root@ecs-euler gcc_experiment# /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc matrix_mul.c -o matrix_mul_euler_ftree_vectorize -fopenmp -O3 -ftree-vectorize
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_ftree_vectorize > matrix_mul_euler_ftree_vectorize_1.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_ftree_vectorize > matrix_mul_euler_ftree_vectorize_2.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_ftree_vectorize > matrix_mul_euler_ftree_vectorize_3.txt

# euler 不开启向量化
/root@ecs-euler gcc_experiment# /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc matrix_mul.c -o matrix_mul_euler_no_ftree_vectorize -fopenmp -O3
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_no_ftree_vectorize > matrix_mul_euler_no_ftree_vectorize_1.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_no_ftree_vectorize > matrix_mul_euler_no_ftree_vectorize_2.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_no_ftree_vectorize > matrix_mul_euler_no_ftree_vectorize_3.txt

# 默认 gcc
/root@ecs-euler gcc_experiment# /usr/bin/gcc matrix_mul.c -o matrix_mul_default -fopenmp
/root@ecs-euler gcc_experiment# ./matrix_mul_default > matrix_mul_default_1.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_default > matrix_mul_default_2.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_default > matrix_mul_default_3.txt

# euler 针对鲲鹏芯片调整,开启向量化
/root@ecs-euler gcc_experiment# /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc matrix_mul.c -o matrix_mul_euler_kunpeng_ftree_vectorize -fopenmp -O3 -ftree-vectorize -mcpu=thunderx2t99
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_kunpeng_ftree_vectorize > matrix_mul_euler_kunpeng_ftree_vectorize_1.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_kunpeng_ftree_vectorize > matrix_mul_euler_kunpeng_ftree_vectorize_2.txt
/root@ecs-euler gcc_experiment# ./matrix_mul_euler_kunpeng_ftree_vectorize > matrix_mul_euler_kunpeng_ftree_vectorize_3.txt

数据导出和分析

  1. 使用 WinSCP 将结果文件下载到本地

    下载结果文件

  2. 本地绘图示例

    可以看出,GCC for openEuler 在针对鲲鹏芯片的优化上,以及在向量化优化上的效果,进而为后续的性能优化提供参考。

总结

通过实验,我们可以验证 GCC for openEuler 在针对鲲鹏芯片的优化上,以及在向量化优化上的效果,进而为后续的性能优化提供参考。

接下来,我们将继续测试其他三个编译优化指令,以及评估 GCC for openEuler 在特定优化场景下的性能提升。


步骤8:测试其他三个编译优化指令

有了前面测试向量化效果的基础,这部分详细介绍如何通过另外三个不同的实验,评估 GCC for openEuler 相较于系统默认 GCC 在特定优化场景下的性能提升。每个实验针对不同的应用场景,使用相应的编译优化选项,通过编写测试程序、编译运行、记录执行时间,并最终绘制对比图,直观展示优化效果。


测试环境

在开始实验前,请确保以下环境配置正确,这些配置由前面的步骤提供:

  • 硬件:配备 Kunpeng 920 处理器的服务器或工作站。
  • 操作系统:最新版本的 openEuler
  • 编译器
    • GCC for openEuler:安装路径 /opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc
    • 系统默认 GCC:安装路径 /usr/bin/gcc(版本 10.3.1)
  • 工具
    • Python:用于绘图,建议安装 matplotlibpandas 库。

      pip install matplotlib pandas
      
    • 文件传输工具:如 WinSCP,用于下载结果文件到本地计算机。


实验概述

本部分包含三个部分,每部分针对不同的性能优化场景:

  1. 动态寻址(Dynamic Addressing)
  2. 浮点优化(Float Optimization)
  3. 内存优化(Memory Optimization)

每个实验包括以下步骤:

  • 编写测试程序
  • 使用两种编译器编译程序,应用相应的优化选项
  • 运行程序,记录执行时间
  • 收集结果并进行对比分析

1. 动态寻址

场景
适用于需要处理大规模数组或数据结构的应用,如科学计算、数据分析和机器学习等领域。

优化选项

  • -mcmodel=large:允许程序访问更大的数据段,适合处理超过默认模型限制的大数组。

目标
评估使用 -mcmodel=large 选项后,程序在处理大数组时的性能改进,具体体现在执行时间的减少上。

代码说明
该程序动态分配一个大数组,初始化数组元素后计算数组元素的和,并记录执行时间。

实验步骤

  1. 创建测试程序

    创建文件 dynamic_addressing.c

    vi dynamic_addressing.c
    

    粘贴以下代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main(int argc, char *argv[]) {
        if(argc != 2) {
            printf("用法: %s <N>\n", argv[0]);
            return 1;
        }
    
        long N = atol(argv[1]);
        double *array = malloc(N * sizeof(double));
        if(!array) {
            perror("内存分配失败");
            return 1;
        }
    
        // 初始化数组
        for(long i = 0; i < N; i++) {
            array[i] = (double)i * 0.1;
        }
    
        clock_t start = clock();
        // 简单数组求和
        double sum = 0.0;
        for(long i = 0; i < N; i++) {
            sum += array[i];
        }
        clock_t end = clock();
    
        double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
        printf("%ld,%f\n", N, time_spent);
    
        free(array);
        return 0;
    }
    
  2. 创建 CSV 文件头

    # 路径:/home/gcc_experiment
    echo "N,Execution Time (seconds)" > dynamic_addressing_default.csv
    echo "N,Execution Time (seconds)" > dynamic_addressing_openEuler.csv
    

2. 浮点优化

场景
适用于需要进行大量浮点运算的应用,如图形处理、物理模拟和机器学习算法,尤其在速度优先而非精度优先的情况下。

优化选项

  • -ffast-math:启用激进的浮点优化,允许编译器重新排序浮点运算、忽略某些精度保证等,以提高计算速度。

目标
评估启用 -ffast-math 后,程序在浮点运算中的性能提升,通过记录执行时间变化来反映优化效果。

代码说明
该程序初始化两个浮点数组,计算它们的和并累加,记录执行时间。

实验步骤

  1. 创建测试程序

    创建文件 float_optimization.c

    vi float_optimization.c
    

    粘贴以下代码:

    #include <stdio.h>
    #include <math.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main(int argc, char *argv[]) {
        if(argc != 2) {
            printf("用法: %s <N>\n", argv[0]);
            return 1;
        }
    
        long N = atol(argv[1]);
        double *a = malloc(N * sizeof(double));
        double *b = malloc(N * sizeof(double));
        double *c = malloc(N * sizeof(double));
        if(!a || !b || !c) {
            perror("内存分配失败");
            return 1;
        }
    
        // 初始化数组
        for(long i = 0; i < N; i++) {
            a[i] = sin((double)i) * sin((double)i);
            b[i] = cos((double)i) * cos((double)i);
        }
    
        clock_t start = clock();
        // 点积运算
        double sum = 0.0;
        for(long i = 0; i < N; i++) {
            c[i] = a[i] + b[i];
            sum += c[i];
        }
        clock_t end = clock();
    
        double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
        printf("%ld,%f\n", N, time_spent);
    
        free(a);
        free(b);
        free(c);
        return 0;
    }
    
  2. 创建 CSV 文件头

    echo "N,Execution Time (seconds)" > float_optimization_default.csv
    echo "N,Execution Time (seconds)" > float_optimization_openEuler.csv
    

3. 内存优化

场景
适用于对内存使用敏感的应用,如嵌入式系统、低功耗设备或需要在有限资源上运行的程序,评估编译器在内存管理方面的优化效果。

优化选项

  • -fomit-frame-pointer:指示编译器在生成代码时省略帧指针,从而减少内存占用并可能提升执行速度。

目标
评估该优化选项对程序性能的影响,特别是在需要频繁分配和释放内存的场景中,通过记录执行时间来判断优化效果。

代码说明
该程序动态分配一个整型数组,初始化数组元素后进行简单的冒泡排序,并记录执行时间。

实验步骤

  1. 创建测试程序

    创建文件 memory_optimization.c

    vi memory_optimization.c
    

    粘贴以下代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int main(int argc, char *argv[]) {
        if(argc != 2) {
            printf("用法: %s <N>\n", argv[0]);
            return 1;
        }
    
        long N = atol(argv[1]);
        int *array = malloc(N * sizeof(int));
        if(!array) {
            perror("内存分配失败");
            return 1;
        }
    
        // 初始化数组
        for(long i = 0; i < N; i++) {
            array[i] = rand();
        }
    
        clock_t start = clock();
        // 简单排序(冒泡排序,适用于较小的 N)
        for(long i = 0; i < N; i++) {
            for(long j = 0; j < N - i - 1; j++) {
                if(array[j] > array[j + 1]) {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
        clock_t end = clock();
    
        double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
        printf("%ld,%f\n", N, time_spent);
    
        free(array);
        return 0;
    }
    
  2. 创建 CSV 文件头

    echo "N,Execution Time (seconds)" > memory_optimization_default.csv
    echo "N,Execution Time (seconds)" > memory_optimization_openEuler.csv
    

    注意
    由于冒泡排序在大数据集上的效率极低,建议将 N 的最大值限制在 15000 以内,以确保合理的执行时间。


自动化编译与运行脚本

测试过程与测试向量化没有太大差别,为了简化实验过程,创建一个脚本 run_all_tests.sh,自动完成编译、运行以及结果记录的步骤。

创建脚本文件

  1. 创建脚本文件

    vi run_all_tests.sh
    
  2. 粘贴以下内容

    #!/bin/bash
    
    # 定义编译器路径
    GCC_DEFAULT="/usr/bin/gcc"
    GCC_OPTIMIZED="/opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin/gcc"
    
    # 定义测试文件和对应的 CSV 文件
    declare -A TESTS
    TESTS=(
        ["dynamic_addressing"]="dynamic_addressing"
        ["float_optimization"]="float_optimization"
        ["memory_optimization"]="memory_optimization"
    )
    
    # 定义 N 值的范围和步长
    declare -A N_VALUES
    N_VALUES=(
        ["dynamic_addressing"]="1000000 3000000 5000000 7500000 10000000 12000000 15000000 200000000"
        ["float_optimization"]="1000000 1500000 2000000 3000000 5000000 7500000 10000000"
        ["memory_optimization"]="10000 15000 20000 35000 50000 75000 100000"
    )
    
    # 清理旧结果并添加表头
    for test in "${!TESTS[@]}"; do
        default_csv="${TESTS[$test]}_default.csv"
        optimized_csv="${TESTS[$test]}_openEuler.csv"
        echo "N,Execution Time (seconds)" > "$default_csv"
        echo "N,Execution Time (seconds)" > "$optimized_csv"
    done
    
    # 运行每个测试
    for test in "${!TESTS[@]}"; do
        default_csv="${TESTS[$test]}_default.csv"
        optimized_csv="${TESTS[$test]}_openEuler.csv"
        echo "Running test: $test"
    
        for N in ${N_VALUES[$test]}; do
            echo "Testing N = $N"
    
            # 编译并运行 Default GCC
            if [ "$test" == "dynamic_addressing" ]; then
                $GCC_DEFAULT ${test}.c -o ${test}_default -O3 -mcmodel=large
            elif [ "$test" == "float_optimization" ]; then
                $GCC_DEFAULT ${test}.c -o ${test}_default -O3 -ffast-math -lm
            elif [ "$test" == "memory_optimization" ]; then
                $GCC_DEFAULT ${test}.c -o ${test}_default -O3 -fomit-frame-pointer
            fi
    
            if [ $? -ne 0 ]; then
                echo "Compilation failed for $test with Default GCC on N=$N"
                continue
            fi
    
            # 运行 Default GCC 编译的程序并记录结果
            ./$(basename ${test}_default) $N >> "$default_csv"
    
            # 编译并运行 GCC for openEuler
            if [ "$test" == "dynamic_addressing" ]; then
                $GCC_OPTIMIZED ${test}.c -o ${test}_optimized -O3 -mcmodel=large
            elif [ "$test" == "float_optimization" ]; then
                $GCC_OPTIMIZED ${test}.c -o ${test}_optimized -O3 -ffast-math -lm
            elif [ "$test" == "memory_optimization" ]; then
                $GCC_OPTIMIZED ${test}.c -o ${test}_optimized -O3 -fomit-frame-pointer
            fi
    
            if [ $? -ne 0 ]; then
                echo "Compilation failed for $test with GCC for openEuler on N=$N"
                continue
            fi
    
            # 运行 GCC for openEuler 编译的程序并记录结果
            ./$(basename ${test}_optimized) $N >> "$optimized_csv"
        done
    done
    
    echo "All tests completed."
    
  3. 保存并退出

    vi 编辑器中,按 Esc 键,输入 :wq,然后按 Enter 键。

  4. 赋予脚本执行权限

    chmod +x run_all_tests.sh
    
  5. 运行脚本

    # 路径:/home/gcc_experiment
    ./run_all_tests.sh
    

    脚本说明

    • 编译器路径:指定系统默认 GCC 和 GCC for openEuler 的路径。
    • 测试定义:使用关联数组定义每个测试的名称和对应的 C 文件。
    • N 值定义:为每个测试定义适当的输入规模范围。
    • CSV 初始化:为每个测试和编译器组合创建 CSV 文件,并添加表头。
    • 编译与运行
      • 根据测试类型,应用相应的编译选项。
      • 编译失败时,输出错误信息并跳过当前测试。
      • 运行编译后的程序,将输出结果(N 值和执行时间)追加到对应的 CSV 文件中。
    • 完成提示:测试全部完成后,输出提示信息。

结果收集与绘图

完成实验后,将生成六个 CSV 文件,分别对应三个测试在系统默认 GCC 和 GCC for openEuler 下的执行时间。接下来,通过 Python 脚本将结果可视化。

使用 WinSCP 或其他文件传输工具,将以下 CSV 文件下载到本地计算机:

  • dynamic_addressing_default.csv
  • dynamic_addressing_openEuler.csv
  • float_optimization_default.csv
  • float_optimization_openEuler.csv
  • memory_optimization_default.csv
  • memory_optimization_openEuler.csv

下载结果

示例图表

  1. 动态寻址性能对比

  2. 浮点优化性能对比

  3. 内存优化性能对比


本部分结论

通过上述三个实验,可以评估 GCC for openEuler 在三个优化场景下相较于系统默认 GCC 的性能提升:

  1. 动态寻址

    • 场景:处理大规模数据集,如科学计算和数据分析。
    • 优化指令-mcmodel=large
    • 结果GCC for openEuler 在处理更大规模的数据段时表现更高效,执行时间更短。
  2. 浮点优化

    • 场景:需要大量浮点运算的应用,如图形处理和物理模拟。
    • 优化指令-ffast-math
    • 结果:启用浮点优化后,GCC for openEuler 显著提高浮点运算速度,执行时间明显减少。
  3. 内存优化

    • 场景:内存使用敏感的应用,如嵌入式系统和低功耗设备。
    • 优化指令-fomit-frame-pointer
    • 结果GCC for openEuler 通过省略帧指针减少内存占用,提高程序整体执行速度。

综合结论
GCC for openEuler 在多种优化选项下展现出显著的性能优势,特别是在大规模数据处理、浮点运算优化和内存管理方面。这使其成为在特定硬件和操作系统环境下,追求高性能应用的理想编译器选择。


步骤8 故障排除

在实验过程中,可能会遇到以下常见问题及其解决方法:

1. 编译错误:undefined reference to 'sincos'

问题描述
编译浮点优化实验程序时,出现链接错误,提示 sincos 函数未定义。

解决方法
确保在编译时链接数学库,使用 -lm 选项。例如:

gcc float_optimization.c -o float_optimization_default -O3 -ffast-math -lm

2. 编译错误:gcc: error: unrecognized argument ‘-mcmodel=large’

问题描述
编译动态寻址实验程序时,出现错误提示 -mcmodel=large 选项未被识别。

解决方法

  • 确认编译器版本:确保使用的是支持 -mcmodel=large 选项的 GCC 版本。
  • 检查编译器路径:使用正确路径下的 GCC for openEuler
  • 查看 GCC 文档:参考当前 GCC 版本的文档,确认是否支持该选项。

3. 执行错误:./dynamic_addressing_openEuler: No such file or directory

问题描述
运行编译后的程序时,提示找不到可执行文件。

解决方法

  • 确认编译成功:检查编译过程是否有错误,确保生成了可执行文件。

  • 检查文件权限:确保可执行文件具有执行权限。

    chmod +x dynamic_addressing_openEuler
    
  • 确认文件路径:在正确的目录下运行程序。

4. 程序运行时间异常长

问题描述
内存优化实验中的冒泡排序在大数据集上运行时间极长。

解决方法

  • 限制 N 值:将 N 的最大值设置在合理范围内(如15000)。
  • 优化算法:考虑使用更高效的排序算法(如快速排序),以减少执行时间。

整个实验的总结与不足

通过实验,测试了 GCC for openEuler 的特性和优势,但是有一点不足是在测试向量化时,默认的 GCC -O3 优化会出奇地快,有待进一步研究。


参考资料


posted @   NOTHINGBUTNOTHING  阅读(224)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示