该课设主题是多类积分函数华为鲲鹏CPU 与 CPU + GPU 对比。
多类积分函数指的是在数学中,涉及到多重积分、固定常数、复杂的函数表达式等特点的一类积分函数。它们通常需要进行大量的数值计算和积分求解,以得出精确的结果。这些函数通常用于计算、模拟和数据分析等领域,并具有广泛的应用。在工程中,多类积分函数常用于电子设备设计、信号处理、通信系统优化、图像识别等方面,其中涉及到众多的复杂计算过程。
对于华为鲲鹏 CPU,它拥有强大的计算能力和超强的多线程性能,可以处理大量的计算任务。同时,鲲鹏 CPU 还具有高速的内存访问能力和大容量的内存,可以存储大量的数据。在处理多类积分函数时,鲲鹏 CPU 可以提供高效的计算能力,可以满足高性能计算的需求。
与此同时,华为鲲鹏 CPU 还可以与 GPU 联合使用,以进一步提高计算效率。GPU 具有高度并行的计算能力,可以同时处理多个任务,加速计算速度。在使用 GPU 计算时,可以采用并行化算法,充分发挥 GPU 的计算能力。此外,GPU 还可用于加速数据传输和存储,提高算法的整体效率。
因此,在处理多类积分函数时,华为鲲鹏 CPU与 CPU + GPU 的对比,取决于具体的算法和实现方式。对于一些计算密集型的算法,通过华为鲲鹏 CPU 的多线程计算加速可能会比 CPU + GPU 效果更好;但对于一些具有高并行性质的算法,CPU + GPU 的联合计算会提供更高的计算效率。另外,也需要考虑到算法的需求和计算平台的成本等因素。
在我的课设中,我选取了计算这个多类积分函数中的二重积分。在鲲鹏服务器上用直接计算和OMP技术来并行计算;在PC机上用CUDA(CPU + GPU)来计算,并比较了这些方法的性能,得出一系列结论。
华为鲲鹏(Kunpeng)服务器是华为基于 ARM 架构体系设计的服务器,旨在为云计算、大数据、虚拟化等业务提供高性能、高可靠性的硬件平台。它采用了充分优化的 CPU 和操作系统软件,提供超强的性能和可扩展性,同时还支持多种高速网络接口和存储设备,可以满足各种企业和机构的计算需求。
鲲鹏服务器的硬件部分主要具有以下特点:
·可扩展、高密度的服务器架构
鲲鹏服务器支持双路主板设计,可扩展高达 64 个处理器核心。它还具有高密度的服务器设计,可在标准机架尺寸下实现高达 128 个核心和 2TB 内存。
·领先的处理器性能
鲲鹏服务器采用最新一代 ARMv8-A 架构中的 APM 扩展,可实现每核心 2.6GHz 的峰值处理器性能。此外,其加速器支持 SIMD 指令集,提供更高效的向量运算支持。
·多种高速接口
鲲鹏服务器支持多种高速网络接口,包括 40G/100G 以太网、InfiniBand 和 RoCE 等。它还支持多种存储设备,包括高速硬盘、SSD 和 NVMe 设备等。
·高可靠性设计
鲲鹏服务器采用了多种高可靠性设计,包括 ECC 内存、RAID 存储、双路热备份等。此外,它还支持预测性故障诊断和动态电源管理等功能,提供更高的可靠性和稳定性。
鲲鹏服务器的软件部分也是其优势之一,其操作系统基于 ARM 架构,并专为企业级应用定制。这个操作系统称为 EulerOS,其特点如下:
·自适应架构
EulerOS 具有自适应架构,可以运行于 ARM 和 x86 架构的服务器上,为用户提供无缝的跨平台支持。
·安全性和可靠性
EulerOS 支持基于 TPM、SELinux、AppArmor 等多种安全技术,并经过了多层认证和安全审计,可以确保每个用户和每台服务器的安全和可靠性。
·模块化设计
EulerOS 采用模块化设计,可以根据用户需求进行轻松部署和管理,提高了用户的效率。
总体来说,华为鲲鹏服务器提供了高性能、高可靠性、高扩展性和高安全性的硬件和软件平台,可以为用户提供更高效、更可靠的计算服务。
图3-1
图3-2
图3-3
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <random>
#include <chrono>
#include <omp.h>
#define ull unsigned long long
double monteCarloIntegrate(double r, ull N) {
double _sum = 0.0;
//修改随机数生成器
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::mt19937 gen(seed);
std::uniform_real_distribution<> dis(0, r);
double x, y;
for (ull i = 0; i < N; ++i) {
x = dis(gen); // 随机生成 x
y = dis(gen);
if (x * x + y * y <= r) {
_sum += 1.0f;
}
}
return _sum;
}
int main() {
double r;
printf("请输入积分上下限!\n");
scanf("%lf", &r);
const ull n = 1e8; // 投点个数
clock_t before, after;
before = clock();
double res = monteCarloIntegrate(r, n);
double realres = (res / n) * 4 * r * r;
after = clock();
printf("The value of integration is %20.18f\n", realres);
printf("The time to calculate integration was %f seconds\n", ((double)(after - before) / CLOCKS_PER_SEC));
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <random>
#include <chrono>
#include <omp.h>
#define ull unsigned long long
double monteCarloIntegrate(double r, const ull N) {
double _sum = 0.0;
//修改随机数生成器
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::mt19937 gen(seed);
std::uniform_real_distribution<> dis(0, r);
//使用循环分块技术
#pragma omp parallel
{
double sum = 0.0; //在每个线程内部定义一个sum用来累计该线程计算的击中的次数
#pragma omp for schedule(dynamic) nowait
for (ull i = 0; i < N; ++i) {
double x = dis(gen);
double y = dis(gen);
if (x * x + y * y <= r * r) {
sum += 1.0;
}
}
#pragma omp critical
//将每个线程的结果累加给_sum
_sum += sum;
}
return _sum;
}
int main() {
double r;
printf("请输入积分上下限:\n");
scanf("%lf", &r);
const ull n = 1e8;
clock_t before, after;
before = clock();
double res = monteCarloIntegrate(r, n);
double realres = (res / n) * 4 * r * r;
after = clock();
printf("Integration value is %20.18lf\n", realres);
printf("Running time was %lf seconds\n", ((double)(after - before) / CLOCKS_PER_SEC));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <random>
#include <cuda.h>
#include <curand_kernel.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#define NUM_THREAD 1024
#define NUM_BLOCK 32 // 修改块数以适应更大的计算量
#define ull unsigned long long
using namespace std;
// CUDA 核函数,计算蒙特卡洛积分
__global__ void monteCarlo(curandState* state, double* _sum, ull n, float r, ull nthreads, ull nblocks) {
ull tid = threadIdx.x + blockDim.x * blockIdx.x; //线程唯一的标识符
curandState localState = state[tid]; //每个线程都有一个随机数状态号
float x, y;
for (ull i = tid; i < n; i += nthreads * nblocks) {
x = curand_uniform(&localState) * r; //给x分配一个从0-1的随机数
y = curand_uniform(&localState) * r; //给y分配一个从0-1的随机数
if (x * x + y * y <= r * r) {
_sum[tid] += 1.0f; //如果在圆内,就+1
}
}
state[tid] = localState; //线程结束的时候需要收回这个状态号,方便下一个线程分配随机数
//最终_sum累积的是,是在四分之一圆的次数
}
__global__ void init_states(unsigned int seed, curandState* states) {
int tid = threadIdx.x + blockIdx.x * blockDim.x;
curand_init(seed + tid, tid, 0, &states[tid]);
}
int main() {
// 设置CUDA相关变量
float r, res = 0, realres = 0;
printf("请输入积分上限!\n");
scanf("%f", &r);
const ull n = 1e11; // 增加采样点数量,提高计算精度
// 为每个线程初始化 curand 状态
curandState* devStates;
cudaMalloc((void**)&devStates, NUM_THREAD * NUM_BLOCK * sizeof(curandState));
//线程总数
ull size = NUM_THREAD * NUM_BLOCK * sizeof(double);
//sumHost是host端的总和,sumDev是device端的总和
double* sumHost, * sumDev;
sumHost = (double*)malloc(size);
memset(sumHost, 0, size);
cudaMalloc((void**)&sumDev, size);
// 不需要再次将 sumDev 初始化为 0,因为 CUDA 分配出来的内存本身已经被初始化为 0。
clock_t before, after;
before = clock();
dim3 numBlocks(NUM_BLOCK, 1, 1);
dim3 threadsPerBlock(NUM_THREAD, 1, 1);
//初始化device端状态号
init_states << <numBlocks, threadsPerBlock >> > (time(nullptr), devStates);
monteCarlo << <numBlocks, threadsPerBlock >> > (devStates, sumDev, n, r, NUM_THREAD, NUM_BLOCK);
cudaMemcpy(sumHost, sumDev, size, cudaMemcpyDeviceToHost);
for (ull tid = 0; tid < NUM_THREAD * NUM_BLOCK; tid++) {
res += sumHost[tid];
}
//次数除以总数 * 4,得到二重积分值
realres = (res / n) * 4 * r * r;
after = clock();
printf("The value of integration is %20.18lf\n", realres);
printf("The time to calculate integration was %lf seconds\n", ((double)(after - before) / CLOCKS_PER_SEC));
free(sumHost);
cudaFree(sumDev);
cudaFree(devStates);
return 0;
}
图6-1 取样1e7,上限1
图6-2 取样1e8,上限1
图6-3 取样1e8,上限2
图6-4 取样1e7,上限1
图6-5 取样1e8,上限1
图6-6 取样1e8,上限2
图6-7 取样1e10,上限1
图6-8 取样1e11,上限1
图6-9 取样1e11,上限2
首先,上限为1时,结果为pi,值为3.1415926……;上限为2时,结果为4*pi,值为12.5663704……。不论是直接计算,OpenMP计算还是CUDA计算,误差都在正常范围内,正确性满足。
直接计算和OpenMP计算准确率比较。两者在准确率方面都差不多,基本上精确到第三位小数。这也因为使用了1e8的取样量,对于1e7的取样量,肯定会导致只精确到第二位小数。
两者在性能上来讲,在OpenMP作为支持并行计算的库,并没有比直接计算快。一方面因为华为鲲鹏虽然在计算上是非常快,性能极高的。但是它终究还是CPU,发挥不出很多OpenMP的实力。另一方面,可能在线程建立与销毁上面也会花费一定时间。OpenMP计算内容只有一个加法,不是很复杂,所以也导致了时间上差不多。
CPU+GPU与CPU准确率比较。因为CUDA选取的是1e10,1e11取样,这让CUDA计算能够精确到小数点后四位。
性能上因为CUDA本身使用的GPU+CPU,充分利用的大批线程并行计算的优势,将单线程计算的复杂度降低了很多,所以在速度上,即使CPU样本量1e7,CUDA样本量1e11,也丝毫不逊色于CPU。性能上远超CPU。
在这个项目中,我使用了蒙特卡罗积分算法来计算二重积分,并通过并行化技术来提高程序的计算效率。我分别使用了 OpenMP 和 CUDA 工具库来并行化代码,并对两种并行化技术进行了比较。
在实现中,我遇到了一些报错的问题,这让我感到非常沮丧和困惑。我花费了很长时间仔细检查代码,包括变量名、数据类型和代码结构等,最终找到了一些不当之处,并成功解决了错误。最令我头疼的是下面这段代码:
void GenRandomX(float* X, float a, float b, int N) {
for (int i = 0; i < N; i++) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> distrib(a, b);
//其中,rd() 创建一个 random_device 对象,gen(rd()) 创建一个名为 gen 的随机数引擎,并以 rd() 为构造函数的参数初始化。distrib(a, b) 创建一个在 a 至 b 之间均匀分布的随机数分布器。distrib(gen) 调用生成器 gen,返回一个随机数,该随机数在 distrib 指定的范围内。
X[i] = distrib(gen);
}
}
这段代码我其实是想做一个随机化的计算,这刚好满足蒙特卡罗的方式,但是在CUDA下,这个X随机数数组导入到核函数计算之后,不论怎么样,计算结果都是0;经过两天的调查,我才发现,原来要用到CUDA中自带的随机生成器!!!真的又兴奋又难过的感觉。
通过本项目,我学到了很多关于并行化和优化算法的知识。蒙特卡罗积分算法本身是一种简单而有效的积分算法,它可以用于计算各种类型的积分。将其与并行化技术相结合,可以更有效地加速程序,并提高准确性。使用 OpenMP 和 CUDA 实现代码的并行化可以极大地加快程序的运行速度,但是需要注意算法并行化的正确性和数据竞争的问题。不过因为CUDA是基于我PC机上的GPU,通过分配线程块来并行计算,极大地提高的性能。OpenMP 可能因为我的理解不够深刻,它的性能没有想象中的那么理想。也证明了CUDA的强大之处。
总的来说,这个项目让我更深入地了解了并行编程、数值方法和优化算法等方面的知识,也提升了我的代码调试能力。虽然我遇到了一些困难,但是通过不断的尝试和调整,最终还是得以成功实现了代码的修改和优化。在今后的学习和工作中,我将继续努力深入理解并行计算的相关知识,以便更好地实现高效的算法。