异构计算是指高效地使用系统中的所有处理器,包括 CPU 和 GPU 。为此,应用程序必须在多个处理器上并发执行函数。 CUDA 应用程序通过在 streams 中执行异步命令来管理并发性,这些命令是按顺序执行的。不同的流可以并发地执行它们的命令,也可以彼此无序地执行它们的命令。

在不指定流的情况下执行异步 CUDA 命令时,runtime使用默认流。在 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 。

多流示例

看一个小例子。下面的代码简单地在八个流上启动一个简单内核的八个副本。只为每个网格启动一个线程块,就有足够的资源同时运行多个线程块。默认流如何导致序列化的示例,在默认流上添加了不起作用的虚拟内核启动。这是密码。

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 :使用新的每线程默认流选项的多流示例,它支持完全并发执行。

多线程示例

来看另一个例子,该示例旨在演示新的默认流行为如何使多线程应用程序,更容易实现执行并发。下面的例子创建了八个 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 7 流并发性优化的更多相关文章

  1. CUDA 7 Stream流简化并发性

    CUDA 7 Stream流简化并发性 异构计算是指高效地使用系统中的所有处理器,包括 CPU 和 GPU .为此,应用程序必须在多个处理器上并发执行函数. CUDA 应用程序通过在 streams  ...

  2. CUDA 7流简化并发

    CUDA 7流简化并发 异构计算是指有效使用系统中的所有处理器,包括CPU和GPU.为此,应用程序必须在多个处理器上同时执行功能.CUDA应用程序通过在流(按顺序执行的命令序列)中,执行异步命令来管理 ...

  3. Flume-NG中Transaction并发性探究

    我们曾经在Flume-NG中的Channel与Transaction关系(原创)这篇文章中说了channel和Transaction的关系,但是在source和sink中都会使用Transaction ...

  4. 探索 ConcurrentHashMap 高并发性的实现机制--转

    ConcurrentHashMap 是 Java concurrent 包的重要成员.本文将结合 Java 内存模型,来分析 ConcurrentHashMap 的 JDK 源代码.通过本文,读者将了 ...

  5. Java并发性和多线程

    Java并发性和多线程介绍   java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题, ...

  6. Java并发性和多线程介绍

    java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题,多开线程就好: 快速响应,异步式 ...

  7. 【转】探索 ConcurrentHashMap 高并发性的实现机制

    原文链接:https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/  <探索 ConcurrentHashMap ...

  8. Nginx的火速蔓延与其并发性处理优势

    Nginx是俄罗斯人编写的十分轻量级的HTTP服务器.Nginx,它的发音为“engine X”, 是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器.Ngi ...

  9. 4、Java并发性和多线程-并发编程模型

    以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现. ...

随机推荐

  1. Windows远程时无法复制文件--杀进程rdpclip.exe,然后再启动

    1.远程登陆到主机上 2.任务管理器杀进程rdpclip.exe 3.[开始],搜索rdpclip.exe,点击运行 此时重新复制文件,可以跨主机复制啦 原以为是公司网络限制,现在看来还是没那么先进嘛

  2. C#-CMD

    private static string InvokeCmd(string cmdArgs) { string Tstr = ""; Process p = new Proces ...

  3. Windows PE 第十二章 PE变形技术

    PE变形技术 这章东西太多,太细了.这里我只记录了一些重点概念.为后面学习做铺垫. PE变形:改变PE结构之后,PE加载器依然可以成功加载运行我们的程序. 一 变形常用技术: 结构重叠技术.空间调整技 ...

  4. 神经网络与机器学习 笔记—LMS(最小均方算法)和学习率退火

    神经网络与机器学习 笔记-LMS(最小均方算法)和学习率退火 LMS算法和Rosenblatt感知器算法非常想,唯独就是去掉了神经元的压制函数,Rosenblatt用的Sgn压制函数,LMS不需要压制 ...

  5. php基础-php基本语法

    http://study.163.com/course/courseMain.htm?courseId=246003

  6. lombok,Invalid byte tag in constant pool: 19

    今天偶到一个奇怪的问题: 三台生产服务器部署同样的代码,同样的tomcat ,jdk等环境. 其中有一台服务器启动时报lombok-1.18.6.jar!   Invalid byte tag in ...

  7. 数据人必读!玩转数据可视化用这个就够了——高德LOCA API 2.0升级来袭!

    引言 "一图胜千言",大数据时代来临,数据与人们生活密切相关.复杂难懂且体量庞大的数据给人的感觉总是冷冰冰的,让人难以获取到重点信息,也找不出规律和特征,数据价值发挥不出来.空间数 ...

  8. 【Docker】2. Docker的架构介绍、安装与卸载 (CentOS 7)

    一.docker的基本组成 开局一张图. docker的组成: Client:客户端,可以通过它与docker服务进行交互.比如容器的构建.拉取.运行. DOCKER_HOST:就是docker服务. ...

  9. RabbitMQ一些实用方法

    https://blog.csdn.net/vbirdbest/article/details/78670550

  10. 进程间IPC通信-stop waiting for thing to happen,go out and make them happen!!!

    进程间通信: System V IPC对象: ipcs -q:查看消息队列   ipcs -m:查看共享内存 ipcs -s:查看信号灯集 ipcrm -q:删除消息队列   ipcrm -m:删除共 ...