【GPU】Nvidia CUDA 编程高级教程——利用蒙特卡罗法求解 的近似值

2024-01-20 15:10

本文主要是介绍【GPU】Nvidia CUDA 编程高级教程——利用蒙特卡罗法求解 的近似值,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接

本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。


博客内容主要围绕:
       5G/6G协议讲解
       算力网络讲解(云计算,边缘计算,端计算)
       高级C语言讲解
       Rust语言讲解



利用蒙特卡罗法求解 𝜋 的近似值

在这里插入图片描述

算法简介

估算 𝜋 有一个著名的技巧,那就是在单位面积内随机选择大量点,并计算落在单位圆内的点数。因为正方形的面积是 1,圆的面积是 𝜋/4 ,所以落在圆上的点的点数乘以 4,就是一个 𝜋 的良好近似值。

高度可并行

从并行编程的角度来看,该算法的良好特征之一是每个随机点都可以独立计算。我们只需要知道一个点的坐标,即可评估其是否落在圆内,对于点坐标 (𝑥,𝑦) 而言,如果 𝑥 2 + 𝑦 2 < = 1 𝑥^{2}+𝑦^{2}<=1 x2+y2<=1,那么点落在圆内,只要我们能处理好与计数器相关的任何竞态条件,表示圆内点数的计数器就可以递增。

单一 GPU 实现

我们来看看在单 GPU 上的 CUDA 中的实现情况。我们已提供实现情况的示例,如下所示:

#include <iostream>
#include <curand_kernel.h>#define N 1024*1024__global__ void calculate_pi(int* hits) {int idx = threadIdx.x + blockIdx.x * blockDim.x;// 初始化随机数状态(网格中的每个线程不得重复)int seed = 0;int offset = 0;curandState_t curand_state;curand_init(seed, idx, offset, &curand_state);// 在 (0.0, 1.0] 内生成随机坐标float x = curand_uniform(&curand_state);float y = curand_uniform(&curand_state);// 如果这一点在圈内,增加点击计数器if (x * x + y * y <= 1.0f) {atomicAdd(hits, 1);}
}int main(int argc, char** argv) {// 分配主机和设备值int* hits;hits = (int*) malloc(sizeof(int));int* d_hits;cudaMalloc((void**) &d_hits, sizeof(int));// 初始化点击次数并复制到设备*hits = 0;cudaMemcpy(d_hits, hits, sizeof(int), cudaMemcpyHostToDevice);// 启动核函数进行计算int threads_per_block = 256;int blocks = (N + threads_per_block - 1) / threads_per_block;calculate_pi<<<blocks, threads_per_block>>>(d_hits);cudaDeviceSynchronize();// 将最终结果复制回主机cudaMemcpy(hits, d_hits, sizeof(int), cudaMemcpyDeviceToHost);// 计算 pi 的最终值float pi_est = (float) *hits / (float) (N) * 4.0f;// 打印结果std::cout << "Estimated value of pi = " << pi_est << std::endl;std::cout << "Error = " << std::abs((M_PI - pi_est) / pi_est) << std::endl;// 清理free(hits);cudaFree(d_hits);return 0;
}

请注意,此代码仅用于指导目的,并不代表具有特别高的性能。具体原因如下:

  • 我们将使用设备侧 API(属于cuRAND),直接在核函数中生成随机数。即使您不熟悉 cuRAND 也无妨,只需知道每个 CUDA 线程都有各自唯一的随机数即可。
  • 我们让每个线程只计算一个值,所以计算强度很低。
  • 在更改hits(“命中”)计数器时,我们将遇到许多原子操作的冲突。

即便如此,我们仍可用 100 万个样本点快速估算 𝜋 。与正确值相比,我们的计算误差应该仅约为 0.05%。

运行结果如下:

Estimated value of pi = 3.14319
Error = 0.000507708
CPU times: user 51.4 ms, sys: 16.5 ms, total: 67.9 ms
Wall time: 3.17 s

扩展到多个 GPU

有一个简单的方法可以将我们的示例扩展到多个 GPU,那就是使用管理多个 GPU 的单一主机进程。如果我们利用 M 个 GPU 对 N 个采样点进行计算,则可以将N/M采样点分配给每个 GPU,原则上可以M 倍地加快计算。

为了实施这一方法,我们要:

  • 使用cudaGetDeviceCount确定可用 GPU 的数量。
  • 以GPU数量为循环次数,在每次循环中使用cudaSetDevice指定执行代码的是哪个GPU。
  • 在指定的 GPU 上执行分配给它的那部分工作。
    int device_count;
    cudaGetDeviceCount(&device_count);for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);# Do single GPU worth of work.
    }
    

代码实现

请注意,在此示例中,我们会给每个 GPU 一个不同的随机数生成器种子,以便每个 GPU 进行不同的工作。因此,我们的答案会有所不同。

#include <iostream>
#include <curand_kernel.h>#define N 1024*1024__global__ void calculate_pi(int* hits, int device) {int idx = threadIdx.x + blockIdx.x * blockDim.x;// 初始化随机数状态(网格中的每个线程不得重复)int seed = device;int offset = 0;curandState_t curand_state;curand_init(seed, idx, offset, &curand_state);// 在 (0.0, 1.0] 内生成随机坐标float x = curand_uniform(&curand_state);float y = curand_uniform(&curand_state);// 如果这一点在圈内,增加点击计数器if (x * x + y * y <= 1.0f) {atomicAdd(hits, 1);}
}int main(int argc, char** argv) {// 确定 GPU 数量int device_count;cudaGetDeviceCount(&device_count);std::cout << "Using " << device_count << " GPUs" << std::endl;// 分配主机和设备值(每个 GPU 一个)int** hits = (int**) malloc(device_count * sizeof(int*));for (int i = 0; i < device_count; ++i) {hits[i] = (int*) malloc(sizeof(int));}int** d_hits = (int**) malloc(device_count * sizeof(int*));for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);cudaMalloc((void**) &d_hits[i], sizeof(int));}// 初始化点击次数并复制到设备for (int i = 0; i < device_count; ++i) {*hits[i] = 0;cudaSetDevice(i);cudaMemcpy(d_hits[i], hits[i], sizeof(int), cudaMemcpyHostToDevice);}// 启动核函数进行计算int threads_per_block = 256;int blocks = (N / device_count + threads_per_block - 1) / threads_per_block;// 先启动所有核函数,以支持异步执行// 然后在所有设备上同步。for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);calculate_pi<<<blocks, threads_per_block>>>(d_hits[i], i);}for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);cudaDeviceSynchronize();}// 将最终结果复制回主机for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);cudaMemcpy(hits[i], d_hits[i], sizeof(int), cudaMemcpyDeviceToHost);}// 计算所有设备的点击总数int hits_total = 0;for (int i = 0; i < device_count; ++i) {hits_total += *hits[i];}// 计算 pi 的最终值float pi_est = (float) hits_total / (float) (N) * 4.0f;// 打印结果std::cout << "Estimated value of pi = " << pi_est << std::endl;std::cout << "Error = " << std::abs((M_PI - pi_est) / pi_est) << std::endl;// 清理for (int i = 0; i < device_count; ++i) {free(hits[i]);cudaFree(d_hits[i]);}free(hits);free(d_hits);return 0;
}

运行结果

我这边使用了4个GPU,根据测试环境不同,我们显示的结果也是不同的。

Using 4 GPUs
Estimated value of pi = 3.14072
Error = 0.000277734
CPU times: user 27.2 ms, sys: 16.1 ms, total: 43.3 ms
Wall time: 2.46 s


在这里插入图片描述

这篇关于【GPU】Nvidia CUDA 编程高级教程——利用蒙特卡罗法求解 的近似值的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/626420

相关文章

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

全网最全Tomcat完全卸载重装教程小结

《全网最全Tomcat完全卸载重装教程小结》windows系统卸载Tomcat重新通过ZIP方式安装Tomcat,优点是灵活可控,适合开发者自定义配置,手动配置环境变量后,可通过命令行快速启动和管理... 目录一、完全卸载Tomcat1. 停止Tomcat服务2. 通过控制面板卸载3. 手动删除残留文件4.

Python的pandas库基础知识超详细教程

《Python的pandas库基础知识超详细教程》Pandas是Python数据处理核心库,提供Series和DataFrame结构,支持CSV/Excel/SQL等数据源导入及清洗、合并、统计等功能... 目录一、配置环境二、序列和数据表2.1 初始化2.2  获取数值2.3 获取索引2.4 索引取内容2

python依赖管理工具UV的安装和使用教程

《python依赖管理工具UV的安装和使用教程》UV是一个用Rust编写的Python包安装和依赖管理工具,比传统工具(如pip)有着更快、更高效的体验,:本文主要介绍python依赖管理工具UV... 目录前言一、命令安装uv二、手动编译安装2.1在archlinux安装uv的依赖工具2.2从github

Python绘制TSP、VRP问题求解结果图全过程

《Python绘制TSP、VRP问题求解结果图全过程》本文介绍用Python绘制TSP和VRP问题的静态与动态结果图,静态图展示路径,动态图通过matplotlib.animation模块实现动画效果... 目录一、静态图二、动态图总结【代码】python绘制TSP、VRP问题求解结果图(包含静态图与动态图

C#实现SHP文件读取与地图显示的完整教程

《C#实现SHP文件读取与地图显示的完整教程》在地理信息系统(GIS)开发中,SHP文件是一种常见的矢量数据格式,本文将详细介绍如何使用C#读取SHP文件并实现地图显示功能,包括坐标转换、图形渲染、平... 目录概述功能特点核心代码解析1. 文件读取与初始化2. 坐标转换3. 图形绘制4. 地图交互功能缩放

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分