CUDA 7 Stream流简化并发性
CUDA 7 Stream流简化并发性
异构计算是指高效地使用系统中的所有处理器,包括 CPU 和 GPU 。为此,应用程序必须在多个处理器上并发执行函数。 CUDA 应用程序通过在 streams 中执行异步命令来管理并发性,这些命令是按顺序执行的。不同的流可以并发地执行它们的命令,也可以彼此无序地执行它们的命令。
在不指定流的情况下执行异步 CUDA 命令时,运行时使用默认流。在 CUDA 7 之前,默认流是一个特殊流,它隐式地与设备上的所有其他流同步。
CUDA 7 引入了大量强大的新功能 ,包括一个新的选项,可以为每个主机线程使用独立的默认流,这避免了传统默认流的序列化。本文将展示如何在 CUDA 程序中简化实现内核和数据副本之间的并发。
CUDA 中的异步命令
如 CUDA C 编程指南所述,异步命令在设备完成请求的任务之前将控制权返回给调用主机线程(它们是非阻塞的)。这些命令是:
- 内核启动;
- 存储器在两个地址之间复制到同一设备存储器;
- 从主机到设备的 64kb 或更少内存块的内存拷贝;
- 由后缀为
Async
的函数执行的内存复制; - 内存设置函数调用。
为内核启动或主机设备内存复制指定流是可选的;可以调用 CUDA 命令而不指定流(或通过将 stream 参数设置为零)。下面两行代码都在默认流上启动内核。
kernel<<< blocks, threads, bytes >>>(); // default stream
kernel<<< blocks, threads, bytes, 0 >>>(); // stream 0
默认流
在并发性对性能不重要的情况下,默认流很有用。在 CUDA 7 之前,每个设备都有一个用于所有主机线程的默认流,这会导致隐式同步。正如 CUDA C 编程指南中的“隐式同步”一节所述,如果主机线程向它们之间的默认流发出任何 CUDA 命令,来自不同流的两个命令就不能并发运行。
CUDA 7 引入了一个新选项, 每线程默认流 ,它有两个效果。首先,它为每个主机线程提供自己的默认流。这意味着不同主机线程向默认流发出的命令可以并发运行。其次,这些默认流是常规流。这意味着默认流中的命令可以与非默认流中的命令同时运行。
要在 nvcc
7 及更高版本中启用每线程默认流,可以在包含 CUDA 头( cuda.h
或 cuda_runtime.h
)之前,使用 nvcc
命令行选项 CUDA 或 #define
编译 CUDA_API_PER_THREAD_DEFAULT_STREAM
预处理器宏。需要注意的是:当代码由 nvcc
编译时,不能使用 #define CUDA_API_PER_THREAD_DEFAULT_STREAM
在. cu 文件中启用此行为,因为 nvcc
在翻译单元的顶部隐式包含了 cuda_runtime.h
。
Multistream多流示例
看一个小例子。下面的代码简单地在八个流上启动一个简单内核的八个副本。只为每个网格启动一个线程块,这样就有足够的资源同时运行多个线程块。作为遗留默认流如何导致序列化的示例,在默认流上添加不起作用的虚拟内核启动。这是密码。
const int N = 1 << 20;
__global__ void kernel(float *x, int n)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
for (int i = tid; i < n; i += blockDim.x * gridDim.x) {
x[i] = sqrt(pow(3.14159,i));
}
}
int main()
{
const int num_streams = 8;
cudaStream_t streams[num_streams];
float *data[num_streams];
for (int i = 0; i < num_streams; i++) {
cudaStreamCreate(&streams[i]);
cudaMalloc(&data[i], N * sizeof(float));
// launch one worker kernel per stream
kernel<<<1, 64, 0, streams[i]>>>(data[i], N);
// launch a dummy kernel on the default stream
kernel<<<1, 1>>>(0, 0);
}
cudaDeviceReset();
return 0;
}
首先让检查遗留行为,通过不带选项的编译。
nvcc ./stream_test.cu -o stream_legacy
可以在 NVIDIA visualprofiler ( nvvp
)中运行该程序,以获得显示所有流和内核启动的时间轴。图 1 显示了 Macbook Pro 上生成的内核时间线,该 Macbook Pro 带有 NVIDIA GeForce GT 750M (一台开普勒 GPU )。可以看到默认流上虚拟内核的非常小的条,以及它们如何导致所有其他流序列化。
一个简单的多流示例在将任何交错内核发送到默认流时不会实现并发
现在尝试新的每线程默认流。
nvcc --default-stream per-thread ./stream_test.cu -o stream_per-thread
图 2 显示了来自 nvvp
的结果。在这里可以看到九个流之间的完全并发:默认流(在本例中映射到流 14 )和创建的其他八个流。请注意,虚拟内核运行得如此之快,以至于很难看到在这个图像中默认流上有八个调用。
图 2 :使用新的每线程默认流选项的多流示例,它支持完全并发执行。
MultiThread多线程示例
看另一个例子,该示例旨在演示新的默认流行为如何使多线程应用程序更容易实现执行并发。下面的例子创建了八个 POSIX 线程,每个线程在默认流上调用的内核,然后同步默认流。(需要在本例中进行同步,以确保探查器在程序退出之前获得内核开始和结束时间戳。)
#include <pthread.h>
#include <stdio.h>
const int N = 1 << 20;
__global__ void kernel(float *x, int n)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
for (int i = tid; i < n; i += blockDim.x * gridDim.x) {
x[i] = sqrt(pow(3.14159,i));
}
}
void *launch_kernel(void *dummy)
{
float *data;
cudaMalloc(&data, N * sizeof(float));
kernel<<<1, 64>>>(data, N);
cudaStreamSynchronize(0);
return NULL;
}
int main()
{
const int num_threads = 8;
pthread_t threads[num_threads];
for (int i = 0; i < num_threads; i++) {
if (pthread_create(&threads[i], NULL, launch_kernel, 0)) {
fprintf(stderr, "Error creating threadn");
return 1;
}
}
for (int i = 0; i < num_threads; i++) {
if(pthread_join(threads[i], NULL)) {
fprintf(stderr, "Error joining threadn");
return 2;
}
}
cudaDeviceReset();
return 0;
}
首先,编译时不使用任何选项来测试遗留的默认流行为。
nvcc ./pthread_test.cu -o pthreads_legacy
在 nvvp
中运行它时,看到一个流,默认流,所有内核启动都序列化,如图 3 所示。
图 3 :一个具有遗留默认流行为的多线程示例:所有八个线程都被序列化。
让用新的 per-thread default stream 选项编译它。
nvcc --default-stream per-thread ./pthread_test.cu -o pthreads_per_thread
图 4 显示,对于每个线程的默认流,每个线程都会自动创建一个新的流,它们不会同步,因此所有八个线程的内核都会并发运行。
图 4 :每个线程默认流的多线程示例:所有八个线程的内核同时运行。
更多提示
在为并发进行编程时,还需要记住以下几点。
- 记住:对于每线程的默认流,每个线程中的默认流的行为与常规流相同,只要同步和并发就可以了。对于传统的默认流,这是不正确的。
--default-stream
选项是按编译单元应用的,确保将其应用于所有需要它的nvcc
命令行。cudaDeviceSynchronize()
继续同步设备上的所有内容,甚至使用新的每线程默认流选项。如果只想同步单个流,请使用cudaStreamSynchronize(cudaStream_t stream)
,如的第二个示例所示。- 从 CUDA 7 开始,还可以使用句柄
cudaStreamPerThread
显式地访问每线程的默认流,也可以使用句柄cudaStreamLegacy
访问旧的默认流。请注意,cudaStreamLegacy
仍然隐式地与每个线程的默认流同步,如果碰巧在一个程序中混合使用它们。 - 可以通过将
cudaStreamCreate()
标志传递给cudaStreamCreate()
来创建不与传统默认流同步的 非阻塞流 。
立即下载 CUDA 7rc
CUDA toolkitversion7 . 0 的发布候选者今天可以向 NVIDIA 注册的开发人员提供。如果不是注册开发者, 在 NVIDIA 开发区注册免费访问 。了解 这里是 CUDA 7 的特点。
想了解更多关于 Tesla 平台上的加速计算以及使用 CUDA 进行 GPU 计算的信息吗?参加 3 月 17 日至 20 日在圣何塞会议中心举行的 GPU 技术会议 ,这是世界上规模最大、最重要的 GPU 开发者大会。 Parallel Forall 的读者可以使用折扣代码 GM15PFAB 获得任何会议通行证 20% 的折扣!`
CUDA 7 Stream流简化并发性的更多相关文章
- CUDA 7流简化并发
CUDA 7流简化并发 异构计算是指有效使用系统中的所有处理器,包括CPU和GPU.为此,应用程序必须在多个处理器上同时执行功能.CUDA应用程序通过在流(按顺序执行的命令序列)中,执行异步命令来管理 ...
- CUDA 7 流并发性优化
异构计算是指高效地使用系统中的所有处理器,包括 CPU 和 GPU .为此,应用程序必须在多个处理器上并发执行函数. CUDA 应用程序通过在 streams 中执行异步命令来管理并发性,这些命令是按 ...
- Cuda Stream流分析
Cuda Stream流分析 Stream 一般来说,cuda c并行性表现在下面两个层面上: Kernel level Grid level Stream和event简介 Cuda stream是指 ...
- 【CUDA 基础】6.0 流和并发
title: [CUDA 基础]6.0 流和并发 categories: - CUDA - Freshman tags: - 流 - 事件 - 网格级并行 - 同步机制 - NVVP toc: tru ...
- 这可能是史上最好的 Java8 新特性 Stream 流教程
本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...
- Java8系列 (二) Stream流
概述 Stream流是Java8新引入的一个特性, 它允许你以声明性方式处理数据集合, 而不是像以前的指令式编程那样需要编写具体怎么实现. 比如炒菜, 用指令式编程需要编写具体的实现 配菜(); 热锅 ...
- 【CUDA 基础】6.2 并发内核执行
title: [CUDA 基础]6.2 并发内核执行 categories: - CUDA - Freshman tags: - 流 - 事件 - 深度优先 - 广度优先 - 硬件工作队列 - 默认流 ...
- 【转】Java8 Stream 流详解
当我第一次阅读 Java8 中的 Stream API 时,说实话,我非常困惑,因为它的名字听起来与 Java I0 框架中的 InputStream 和 OutputStream 非常类似.但是 ...
- 深度分析:java8的新特性lambda和stream流,看完你学会了吗?
1. lambda表达式 1.1 什么是lambda 以java为例,可以对一个java变量赋一个值,比如int a = 1,而对于一个方法,一块代码也是赋予给一个变量的,对于这块代码,或者说被赋给变 ...
随机推荐
- UVA11462年龄排序
题意: 给你200w个人的年龄,年龄的范围是1-100,然后让你从小到大排序输出所有人的年龄,题目还特意强调输入文件限制25MB,题目内存限制2MB. 思路: 比较经典又简单的一 ...
- Python练习1-文档格式化成html
文档格式化成HTML 把文档格式化成了THML,并没有处理所有thml规则,只是处理了一部分,功能不重要,重要的是复习熟悉下Python对文档的处理细节.毕竟Python大多数给我的印象都是处理文档. ...
- Portswigger web security academy:Reflected XSS
Portswigger web security academy:Reflected XSS 目录 Portswigger web security academy:Reflected XSS Ref ...
- 2020年电子设计大赛F题
挺简单前一百分得了九十多 当然主要是队友很给力 1 温度判别 MLX90614DCC,然后测温拟合吧...从五十度到三十度平均一次要测一个半小时...这是真的痛苦...然后虽然文件里面说自带测温工具, ...
- 记录数据库被攻击.md
昨天的数据库还是正常的,早上想连接mysql,一直报错1045,最后才发现数据库被攻击了 navicat连接mysql疯狂报错1045 因为1045的报错,一般都是密码设置的问题,但是我怎么修改也没有 ...
- 异常检测算法Robust Random Cut Forest(RRCF)关键定理引理证明
摘要:RRCF是亚马逊发表的一篇异常检测算法,是对周志华孤立森林的改进.但是相比孤立森林,具有更为扎实的理论基础.文章的理论论证相对较为晦涩,且没给出详细的证明过程.本文不对该算法进行详尽的描述,仅对 ...
- JMeter关联陌生又熟悉
JMeter关联是什么 JMeter关联,这几个字看着可能会有点陌生,实际上却是工作中经常会做的一件事情,尤其是接口自动化,它指的是把一个接口的响应作为另一个接口的参数,从而把接口关联起来. JMet ...
- [敏杰开发]知识路书——图形化文献管理大师 Beta版发布喽!!!
[敏杰开发]知识路书--图形化文献管理大师 Beta版发布喽!!! 一.总览 项目名称:知识路书 发布形式:网页应用 发布地址:http://roadmap.imcoming.top 二.运行环境与使 ...
- str.isdigit()可以判断变量是否为数字
字符串.isdigit()可以判断变量是否为数字 是则输出True 不是则输出False 好像只能字符串
- ipmitool -I lanplus -H 10.1.81.90 -U admin -P admin mc reset cold
ipmitool -I lanplus -H 10.1.81.90 -U admin -P admin mc reset cold