在华为云服务器上测试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.3 创建虚拟私有云 (VPC)
创建云服务器需要先创建虚拟私有云。
注意:区域选择华北北京四,下一步也是。
1.4 创建弹性云服务器(ECS)
-
进入 ECS 控制台:
- 登录后,点击页面顶部导航栏中的“产品”。
- 在下拉菜单中选择“弹性云服务器”。
-
创建服务器:
- 点击“创建弹性云服务器”按钮。
-
选择区域和可用区:
- 区域:选择华北-北京四。
- 可用区:选择可用区1。
-
选择规格:
-
根据需求选择合适的实例规格。
-
推荐配置:
- CPU:至少4核。
- 内存:8GB及以上。
-
示例配置:
-
-
选择镜像:
-
推荐选择市场镜像,选择
openEuler 22.03-LTS-SP2
,点击选择。
-
-
存储配置:
-
根据需求选择系统盘和数据盘的大小。
-
默认配置通常已足够,或者设置为50G。
-
-
选择网络:
-
选择已有的网络或创建新的 VPC(虚拟私有云)。
-
虚拟私有云是前面一步创建的。
-
确保服务器具有公网 IP 地址,以便从 Windows 远程连接。
-
-
安全组配置:
-
安全组:选择默认安全组或创建新的安全组。
-
选择通用的安全组即可。
-
开放端口:确保至少开放 22 端口(SSH)。
-
-
公网访问
如图配置
-
云服务器管理配置密码
-
登录方式:选择“密码登录”。
-
设置密码:输入并确认 root 用户的密码。请记住此密码。
-
名称自己取
-
-
确认订单并创建:
- 检查所有配置是否正确。
- 点击“立即购买”完成服务器创建。
-
获取服务器信息:
-
创建完成后,在 ECS 管理列表中找到新创建的服务器。
-
记录下服务器的 公网 IP 地址。
-
步骤2:连接到服务器
你可以使用 PuTTY 或 Windows 自带的 SSH 客户端 连接到你的服务器。以下以 PuTTY 为例进行说明。
2.1 下载并安装 PuTTY
-
下载 PuTTY:
- 访问 PuTTY 官网。
- 下载适用于 Windows 的最新版本 PuTTY 安装包。
-
安装 PuTTY:
- 双击下载的安装包,按照提示完成安装。
2.2 使用 PuTTY 连接服务器
-
打开 PuTTY:
- 安装完成后,双击桌面上的 PuTTY 图标打开程序。
-
配置连接:
- Host Name (or IP address):输入你的服务器公网 IP 地址(如
123.45.67.89
)。 - Port:默认是
22
。 - Connection type:选择
SSH
。
- Host Name (or IP address):输入你的服务器公网 IP 地址(如
-
保存会话(可选):
-
在“Saved Sessions”字段输入一个名称,如
euler
。 -
点击“Save”按钮,方便下次快速连接。
-
-
连接:
- 点击“Open”按钮。
-
接受安全警告:
- 第一次连接时,PuTTY 会弹出一个安全警告窗口,点击“Yes”继续。
-
登录:
-
Login as:输入
root
,按Enter
。 -
Password:输入你在创建服务器时设置的密码(输入时不会显示),按
Enter
。
注意:如果密码输入错误,请重新尝试或检查服务器设置。
可能会遇到的问题:
-
没有密码登录方式:
这是因为
sshd_config
文件中的PasswordAuthentication
设置为no
,需要修改为yes
。在华为云控制台中找到服务器,点击右侧的管理,然后点击远程登录。随后可以在这个页面通过密码登录:
然后打开
/etc/ssh/sshd_config
文件,找到PasswordAuthentication no
,修改为yes
:vi /etc/ssh/sshd_config
然后按
i
进入编辑模式,找到PasswordAuthentication no
,修改为yes
,同时PermitRootLogin
也修改为yes
,注意这里一共修改三个位置。然后重启配置文件服务:
sudo systemctl restart sshd service sshd restart
之后就可以通过密码登录了。
-
步骤3:获取 GCC for openEuler 软件包到服务器
3.1 从本机上传
在华为云镜像下载对应镜像到本地。
为了将本地的 gcc-12.3.1-2024.09-aarch64-linux.tar.gz
上传到服务器,可以使用 WinSCP 工具。
3.1.1 下载并安装 WinSCP
-
下载 WinSCP:
- 访问 WinSCP 官网。
- 下载最新版本的 WinSCP 安装包。
-
安装 WinSCP:
- 双击下载的安装包,按照提示完成安装。
3.1.2 使用 WinSCP 上传文件
-
打开 WinSCP:
- 安装完成后,启动 WinSCP。
-
配置连接:
- File protocol:选择
SFTP
。 - Hostname:输入你的服务器公网 IP 地址(如
123.45.67.89
)。 - Port number:默认是
22
。 - Username:输入
root
。 - Password:输入你在创建服务器时设置的密码。
- File protocol:选择
-
连接服务器:
- 点击“Login”按钮连接服务器。
- 如果是第一次连接,可能会提示确认服务器的主机密钥,点击“是”继续。
-
上传文件:
-
左侧窗口:显示你本地的文件系统。
-
右侧窗口:显示服务器的文件系统。
-
在左侧导航到你下载的
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
目录中。
-
-
确认上传:
- 上传完成后,在服务器端的
/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
下载:
# 下载 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
文件:
复制链接地址,使用 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 创建安装目录
-
登录到服务器:
- 使用 PuTTY 登录到你的服务器(参考步骤2)。
-
创建安装目录:
如果未创建安装目录,执行以下命令:mkdir -p /opt/aarch64/compiler
-
进入安装目录:
cd /opt/aarch64/compiler
4.2 将软件包上传到服务器
注意:如果已经通过 WinSCP 上传文件到 /opt/aarch64/compiler
或通过 wget
下载到该目录,可以跳过此步骤。
4.3 解压缩软件包
-
查看当前目录内容:
ls
- 确认
gcc-12.3.1-2024.09-aarch64-linux.tar.gz
文件存在。
- 确认
-
解压缩软件包:
tar -xf gcc-12.3.1-2024.09-aarch64-linux.tar.gz
tar
:Unix 下常用的打包工具。-x
:解包。-f
:指定文件名。
-
查看解压后的内容:
ls
- 应该看到一个名为
gcc-12.3.1-2024.09-aarch64-linux
的目录。
- 应该看到一个名为
步骤5:配置环境变量
配置环境变量可以让系统识别新安装的 GCC 编译器。推荐使用编辑 /etc/profile
文件的方法,因为它更简单。
5.1 编辑 /etc/profile
文件
-
打开
/etc/profile
文件:vi /etc/profile
vi
:一种文本编辑器。/etc/profile
:系统级环境变量配置文件。
-
进入插入模式:
- 按下
i
键,进入插入模式。
- 按下
-
添加环境变量配置:
在文件末尾添加以下内容:# 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
:指定系统在运行程序时查找共享库的路径。- 如果下载的版本不同,相应的路径也要改变。
-
保存并退出:
- 按下
Esc
键退出插入模式。 - 输入
:wq
,然后按Enter
键保存并退出vi
编辑器。
- 按下
5.2 使环境变量生效
-
执行以下命令:
# 使配置生效,这个命令无需考虑当前目录在哪 source /etc/profile
source
:在当前 shell 中读取并执行指定文件中的命令。- 这样,无需重新登录,环境变量的更改立即生效。
5.3 验证环境变量配置
-
查看
PATH
环境变量:echo $PATH
- 确认输出中包含
/opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/bin
。
- 确认输出中包含
-
查看
INCLUDE
环境变量:echo $INCLUDE
- 确认输出中包含
/opt/aarch64/compiler/gcc-12.3.1-2024.09-aarch64-linux/include
。
- 确认输出中包含
-
查看
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 版本
-
执行以下命令:
# 当前目录还是 /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 是什么:
which 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 编译器。
-
系统标准 GCC(版本 10.3.1):
-
系统 GCC 通常安装在
/usr/bin/gcc
,可以通过直接调用完整路径来使用:/usr/bin/gcc --version
这将显示系统标准 GCC 的版本。
-
-
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
-
具体实验步骤
分别使用 系统标准 GCC 和 GCC for openEuler 来编译和运行程序,并进行对比。
1. 创建并编辑矩阵乘法程序
-
创建或进入工作目录:
-
路径:
/home/gcc_experiment
mkdir -p /home/gcc_experiment cd /home/gcc_experiment
-
-
创建 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)进行编译和运行
-
编译程序:
-
路径:仍然在
/home/gcc_experiment
/usr/bin/gcc matrix_mul.c -o matrix_mul_default -fopenmp
-
这个命令使用系统默认的 GCC(10.3.1)进行编译,生成可执行文件
matrix_mul_default
。
-
-
运行程序并记录结果:
./matrix_mul_default > default_results.txt
- 该命令将运行结果保存到文件
default_results.txt
中。
- 该命令将运行结果保存到文件
3. 使用 GCC for openEuler(12.3.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
进行优化。
-
-
运行程序并记录结果:
./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
数据导出和分析
-
使用 WinSCP 将结果文件下载到本地:
-
本地绘图示例:
可以看出,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)
- GCC for openEuler:安装路径
- 工具:
-
Python:用于绘图,建议安装
matplotlib
和pandas
库。pip install matplotlib pandas
-
文件传输工具:如 WinSCP,用于下载结果文件到本地计算机。
-
实验概述
本部分包含三个部分,每部分针对不同的性能优化场景:
- 动态寻址(Dynamic Addressing)
- 浮点优化(Float Optimization)
- 内存优化(Memory Optimization)
每个实验包括以下步骤:
- 编写测试程序
- 使用两种编译器编译程序,应用相应的优化选项
- 运行程序,记录执行时间
- 收集结果并进行对比分析
1. 动态寻址
场景:
适用于需要处理大规模数组或数据结构的应用,如科学计算、数据分析和机器学习等领域。
优化选项:
-mcmodel=large
:允许程序访问更大的数据段,适合处理超过默认模型限制的大数组。
目标:
评估使用 -mcmodel=large
选项后,程序在处理大数组时的性能改进,具体体现在执行时间的减少上。
代码说明:
该程序动态分配一个大数组,初始化数组元素后计算数组元素的和,并记录执行时间。
实验步骤:
-
创建测试程序
创建文件
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; }
-
创建 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
后,程序在浮点运算中的性能提升,通过记录执行时间变化来反映优化效果。
代码说明:
该程序初始化两个浮点数组,计算它们的和并累加,记录执行时间。
实验步骤:
-
创建测试程序
创建文件
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; }
-
创建 CSV 文件头
echo "N,Execution Time (seconds)" > float_optimization_default.csv echo "N,Execution Time (seconds)" > float_optimization_openEuler.csv
3. 内存优化
场景:
适用于对内存使用敏感的应用,如嵌入式系统、低功耗设备或需要在有限资源上运行的程序,评估编译器在内存管理方面的优化效果。
优化选项:
-fomit-frame-pointer
:指示编译器在生成代码时省略帧指针,从而减少内存占用并可能提升执行速度。
目标:
评估该优化选项对程序性能的影响,特别是在需要频繁分配和释放内存的场景中,通过记录执行时间来判断优化效果。
代码说明:
该程序动态分配一个整型数组,初始化数组元素后进行简单的冒泡排序,并记录执行时间。
实验步骤:
-
创建测试程序
创建文件
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; }
-
创建 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
,自动完成编译、运行以及结果记录的步骤。
创建脚本文件
-
创建脚本文件
vi run_all_tests.sh
-
粘贴以下内容
#!/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."
-
保存并退出
在
vi
编辑器中,按Esc
键,输入:wq
,然后按Enter
键。 -
赋予脚本执行权限
chmod +x run_all_tests.sh
-
运行脚本
# 路径:/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
示例图表:
-
动态寻址性能对比
-
浮点优化性能对比
-
内存优化性能对比
本部分结论
通过上述三个实验,可以评估 GCC for openEuler 在三个优化场景下相较于系统默认 GCC 的性能提升:
-
动态寻址:
- 场景:处理大规模数据集,如科学计算和数据分析。
- 优化指令:
-mcmodel=large
。 - 结果:GCC for openEuler 在处理更大规模的数据段时表现更高效,执行时间更短。
-
浮点优化:
- 场景:需要大量浮点运算的应用,如图形处理和物理模拟。
- 优化指令:
-ffast-math
。 - 结果:启用浮点优化后,GCC for openEuler 显著提高浮点运算速度,执行时间明显减少。
-
内存优化:
- 场景:内存使用敏感的应用,如嵌入式系统和低功耗设备。
- 优化指令:
-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 优化会出奇地快,有待进一步研究。
参考资料:
- Hikunpeng 技术文章
- 华为云社区博客
- CSDN 博客
- Openeuler文档
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示