关于cuda中修改flag数组的问题

2024-02-21

我正在研究 GPU 编程,并且有一个关于修改线程中的全局数组的问题。

__device__ float data[10] = {0,0,0,0,0,0,0,0,0,1};
__global__ void gradually_set_global_data() {
    while (1) {
        if (data[threadIdx.x + 1]) {
            atomicAdd(&data[threadIdx.x], data[threadIdx.x + 1]);
            break;
        }
    }
}

int main() {
    gradually_set_global_data<<<1, 9>>>();
    cudaDeviceReset();
    return 0;
}

内核应该完成执行data预计会保留 [1,1,1,1,1,1,1,1,1,1],但它陷入了无限循环。为什么会出现这种情况?


TL;DR:代码通过检查被破坏。 CUDA 线程模型不保证任何特定线程的前进进度除非符合以下规定:

  1. 假设至少有 1 个线程,则前进进度将在至少 1 个(可发布、非退休)线程中交付。
  2. 将遵守执行屏障语义

CUDA 编程模型未定义为第 1 项选择哪个或哪些线程。除非程序员使用执行障碍进行显式控制,否则 CUDA 线程模型可以随意安排单个线程,直到该线程退出或遇到显式执行障碍。

由于提供的代码没有执行障碍,因此 CUDA 工作调度程序(相对于 CUDA 语义)可以自由调度,例如线程 0,而没有其他线程。如果我们将该概念应用到所提供的代码中,很明显线程 0 如果单独运行,将出现无限循环。

Longer:

这恰好是观察到的行为,尽管如果是我,我不会将两者联系起来。挂起的原因(根据我尝试描述的方式)不是“为了正确性,此代码依赖于 CUDA 编程模型未提供的保证”,尽管我相信这是一个真实的陈述。要了解挂起的原因,我建议有必要使用 SASS(机器汇编代码)来检查低级机器行为。我实在没有能力穷尽这个话题,所以我只能对此提出有限的看法。

为什么要做出这样的区分呢?因为对所提供的代码进行相对较小的更改(实际上并不能解决正确性问题)可能会导致编译器生成不挂起的代码。缺乏仔细的治疗可能会导致人们得出这样的结论:因为它没有悬挂,所以它一定没问题。关键是代码是否挂起与它是否正确是不同的。我已经向自己证明了这一点。但是我不想提供该代码。正确的做法是设计正确的代码。请参阅下面我的尝试。

在我们深入研究 SASS 之前,我想指出代码中的另一个缺陷。 CUDA 编译器可以自由地将任何全局数据“优化”到寄存器中,同时保持单线程语义正确性。编译器大多只考虑单个线程,因此这可能会给依赖线程间通信的程序员带来麻烦(正如此代码所示)。为了正确性,在此代码中,线程 x 修改的数据必须(最终)对线程 x-1 可见。 CUDA 编程模型不保证这种线程间可见性,编译器通常也不强制执行。为了正确性,有必要通知编译器使该数据可见,并命令加载和存储来实现这一点。有多种方法可以实现这一点。我会建议将数据标记为volatile https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#volatile-qualifier为了简单起见,尽管可以通过执行障碍来做到这一点(例如__syncthreads(), __syncwarp())那也内置内存屏障 https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#synchronization-functions。无论选择哪种方法来强制执行线程间数据可见性,如果没有它,代码就会被破坏,而与任何其他考虑无关。

因此,在深入研究 SASS 之前,我建议对所提供的代码及其后面的 SASS 进行以下修改:

$ cat t1691.cu
__device__ volatile float data[10] = {0,0,0,0,0,0,0,0,0,1};
__global__ void gradually_set_global_data() {
    while (1) {
        if (data[threadIdx.x + 1]) {
            atomicAdd((float *)&data[threadIdx.x], data[threadIdx.x + 1]);
            break;
        }
    }
}

int main() {
    gradually_set_global_data<<<1, 9>>>();
    cudaDeviceReset();
    return 0;
}
$ nvcc -o t1691 t1691.cu
$ cuobjdump -sass ./t1691

Fatbin elf code:
================
arch = sm_30
code version = [1,7]
producer = <unknown>
host = linux
compile_size = 64bit

        code for sm_30

Fatbin elf code:
================
arch = sm_30
code version = [1,7]
producer = <unknown>
host = linux
compile_size = 64bit

        code for sm_30
                Function : _Z25gradually_set_global_datav
        .headerflags    @"EF_CUDA_SM30 EF_CUDA_PTX_SM(EF_CUDA_SM30)"
                                                                       /* 0x22f2c04272004307 */
        /*0008*/                   MOV R1, c[0x0][0x44];               /* 0x2800400110005de4 */
        /*0010*/                   S2R R0, SR_TID.X;                   /* 0x2c00000084001c04 */
        /*0018*/                   MOV32I R3, 0x0;                     /* 0x180000000000dde2 */
        /*0020*/                   SSY 0x68;                           /* 0x6000000100001c07 */
        /*0028*/                   IMAD R2.CC, R0, 0x4, R3;            /* 0x2007c00010009ca3 */
        /*0030*/                   MOV32I R3, 0x0;                     /* 0x180000000000dde2 */
        /*0038*/                   IMAD.U32.U32.HI.X R3, R0, 0x4, R3;  /* 0x2086c0001000dc43 */
                                                                       /* 0x22f043f2f2e2c3f7 */
        /*0048*/                   LD.E.CV R0, [R2+0x4];               /* 0x8400000010201f85 */
        /*0050*/                   FSETP.NEU.AND P0, PT, R0, RZ, PT;   /* 0x268e0000fc01dc00 */
        /*0058*/              @!P0 BRA 0x40;                           /* 0x4003ffff800021e7 */
        /*0060*/                   NOP.S;                              /* 0x4000000000001df4 */
        /*0068*/                   LD.E.CV R4, [R2+0x4];               /* 0x8400000010211f85 */
        /*0070*/                   RED.E.ADD.F32.FTZ.RN [R2], R4;      /* 0x2c00000000211e05 */
        /*0078*/                   EXIT;                               /* 0x8000000000001de7 */
        /*0080*/                   BRA 0x80;                           /* 0x4003ffffe0001de7 */
        /*0088*/                   NOP;                                /* 0x4000000000001de4 */
        /*0090*/                   NOP;                                /* 0x4000000000001de4 */
        /*0098*/                   NOP;                                /* 0x4000000000001de4 */
        /*00a0*/                   NOP;                                /* 0x4000000000001de4 */
        /*00a8*/                   NOP;                                /* 0x4000000000001de4 */
        /*00b0*/                   NOP;                                /* 0x4000000000001de4 */
        /*00b8*/                   NOP;                                /* 0x4000000000001de4 */
                .........................................



Fatbin ptx code:
================
arch = sm_30
code version = [6,4]
producer = <unknown>
host = linux
compile_size = 64bit
compressed
$

根据我在 cc3.5 和 cc7.0 设备上的测试,上述代码仍然挂起,因此我们没有通过这些更改修改其观察到的行为。 (注意,上面的SASS代码适用于cc3.0,使用CUDA 10.1.243编译)。

该代码将表现出扭曲发散行为,IMO 这对于理解挂起至关重要,因此我们将重点关注 SASS 代码的条件区域:

        /*0038*/                   IMAD.U32.U32.HI.X R3, R0, 0x4, R3;  /* 0x2086c0001000dc43 */
                                                                       /* 0x22f043f2f2e2c3f7 */
        /*0048*/                   LD.E.CV R0, [R2+0x4];               /* 0x8400000010201f85 */
        /*0050*/                   FSETP.NEU.AND P0, PT, R0, RZ, PT;   /* 0x268e0000fc01dc00 */
        /*0058*/              @!P0 BRA 0x40;                           /* 0x4003ffff800021e7 */
        /*0060*/                   NOP.S;                              /* 0x4000000000001df4 */
        /*0068*/                   LD.E.CV R4, [R2+0x4];               /* 0x8400000010211f85 */
        /*0070*/                   RED.E.ADD.F32.FTZ.RN [R2], R4;      /* 0x2c00000000211e05 */
        /*0078*/                   EXIT;                               /* 0x8000000000001de7 */

到0038行,所有的设置工作已经完成。在第 0048 行,线程正在加载它的__device__ data来自全局内存的值(.CV on the LD指令是我们的结果volatile装饰),条件测试在第 0050 行执行,条件分支在第 0058 行执行。如果线程拾取了非零值,则它将继续执行到第 0060 行(最终执行原子操作并退出)。如果没有,则返回0040行重复加载和测试。

现在,我们观察到的是挂起。通过条件测试的线程和未通过条件测试的线程不会同时由 warp 调度程序调度。它必须安排一组(例如通过)或另一组(例如失败)。扭曲调度程序必须重复做出同样的决定。如果我们观察到挂起,唯一可能的结论是,未通过条件测试的线程被重复调度(选择发出),而通过条件测试的线程未得到调度。

这是合法的,根据 CUDA 编程模型和此代码设计,任何关于传递线程“最终”应该得到调度的结论都是无效的结论。保证传递的线程得到调度的唯一方法是为 warp 调度程序提供一种没有其他可用选择的情况,这与本答案顶部的原则 1 保持一致。

(旁白:请注意,我们可能还观察到,warp 调度程序选择传递线程而不是失败线程来调度/发出。在这种情况下,因为这些传递线程最终在此实现中退出/退休,我预计这会导致在不挂起的代码中。传递的线程最终将全部退出,并且通过本答案顶部的第 1 项,warp 调度程序将被迫开始调度失败的线程。不挂在这里将是一个同样有效和可能的观察,就此处概述的扭曲调度特征而言。但基于该结果得出的任何正确性结论仍然是错误的。)

那么,延伸这个想法,人们可能会问“有没有一种合法的方式来实现这种模式?”我建议我们现在知道,如果我们要使这项工作成功,我们可能需要执行障碍。我们来选择一下__syncwarp()。对于该屏障,屏障的合法使用通常要求我们拥有完整的经线(或多个经线)。因此,我们需要重新编写代码以允许完整的扭曲处于活动状态,但只有所需的线程(总共 9 个)执行“工作”。

接下来是实现这一目标的一种可能方法。我确信还有其他方法。根据我的测试,此代码不会挂在 cc3.5 或 cc7.0 设备上:

__device__ volatile float data[10] = {0,0,0,0,0,0,0,0,0,1};
__global__ void gradually_set_global_data(int sz) {
    int tflag = (threadIdx.x < sz) ? 1:0; // choose the needed threads to do the "work"
    unsigned wflag = 1;  // initially, the entire warp is marked active
    while (wflag) {  // run the entire warp, or exit the entire warp
        if (tflag)  // if this thread still needs to do its "work"
          if (data[threadIdx.x + 1]) {
            atomicAdd((float *)&data[threadIdx.x], data[threadIdx.x + 1]);
            tflag = 0;  // the work for this thread is completed
          }
        __syncwarp();
        wflag = __ballot_sync(0xFFFFFFFFU, tflag);  //deactivate warp when all threads done
    }
}

int main() {
    gradually_set_global_data<<<1, 32>>>(9);
    cudaDeviceReset();
    return 0;
}

请注意,如果我们想要更接近所提供的代码,可以使用以下命令重新编写上面的代码while(1)循环,并在循环内发出一个break if wflag为零(投票操作后)。我认为这种认识没有任何有意义的差异。

我仍然不声明此代码或我发布的任何其他代码的正确性。任何使用我发布的代码的人都需要自行承担风险。我只是声称我试图解决我在原始帖子中发现的缺陷,并提供一些解释。我并不是声称我的代码没有缺陷,或者它适合任何特定目的。使用(或不使用)它的风险由您自行承担。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于cuda中修改flag数组的问题 的相关文章

  • 在并发程序中从 BlockingQueue 获取对象的最佳方法?

    在并发程序中 从 BlockingQueue 中取出对象而不遇到竞争条件的最佳方法是什么 我目前正在执行以下操作 但我不相信这是最好的方法 BlockingQueue
  • 如何为一系列任务设计执行引擎

    我正在尝试用 Java 编写一个问题 我必须执行一堆任务 Problem 执行由多个任务组成的作业 并且这些任务之间具有依赖关系 一个作业将有一个任务列表 每个这样的任务将进一步有一个后续任务列表 每个后续任务将有自己的后续任务 您可以在此
  • CUDA 估计 2D 网格数据的每块线程数和块数

    首先我要说的是 我已经仔细阅读了所有类似的问题 确定每个块的线程和每个网格的块 https stackoverflow com questions 4391162 cuda determining threads per block blo
  • Erlang 如何并发处理访问邮箱

    关于如何使用erlang邮箱的信息有很多 但很少找到一篇论文或文档描述erlang如何在VM内部同时实际访问邮箱 据我了解 Erlang VM 必须执行锁定或 CAS 操作以确保消息完整性 erlang幕后有没有什么精巧的方法 我假设您所说
  • SQL Server 2008:在没有任何锁的情况下出现死锁

    我目前正在 SQL Server 2008 数据库上进行一些实验 更具体地说 我有一个 JDBC 应用程序 它使用数百个并发线程来执行数千个任务 每个任务都在数据库上运行以下查询 UPDATE from Table A where rowI
  • Thread.yield()之后线程的Thread.State是什么?

    是什么Thread State之后的一个线程Thread yield 是不是一个Thread State WAITING 谢谢 不 线程仍会在RUNNABLE http download oracle com docs cd E17409
  • 并发 log4j

    我有自己的日志引擎 它将日志写入带有阻塞队列的单独线程上 为了使用 标准软件 我正在考虑切换到 log4j 我不希望我的高并发软件因日志命令而变慢 这些日志命令在调用命令时将所有内容写入磁盘 log4j 可以用作垃圾箱吗 Log4j 是大多
  • 串流期货列表的最有效方式

    我通过流式传输对象列表来调用异步客户端方法 该方法返回 Future 迭代调用后返回的 Future 列表的最佳方法是什么 以便处理先出现的 Future 注意 异步客户端仅返回 Future 而不返回 CompletableFuture
  • Python中的键盘可中断阻塞队列

    It seems import Queue Queue Queue get timeout 10 键盘可中断 ctrl c 而 import Queue Queue Queue get 不是 我总是可以创建一个循环 import Queue
  • cudaSetDevice() 对 CUDA 设备的上下文堆栈有何作用?

    假设我有一个与设备关联的活动 CUDA 上下文i 我现在打电话cudaSetDevice i 会发生什么 Nothing 主上下文取代了堆栈顶部 主上下文被压入堆栈 事实上 这似乎是不一致的 我编写了这个程序 在具有单个设备的机器上运行 i
  • python 线程是如何工作的?

    我想知道 python 线程是并发运行还是并行运行 例如 如果我有两个任务并在两个线程中运行它们 它们是同时运行还是计划同时运行 我知道GIL并且线程仅使用一个 CPU 核心 这是一个复杂的问题 需要大量解释 我将坚持使用 CPython
  • 编写潜在并发问题的证明

    我正在阅读 Java 并发实践 并尝试编写一段代码来表明第 3 5 1 章中作为示例提供的类确实会引入问题 public class Holder public int n public Holder int n this n n publ
  • 非法监控状态异常

    如何将轮询线程传递给另一个线程进行处理 程序执行在控制器类中 该类具有 main 方法和线程池 主类控制器 public static void main String args throws InterruptedException Ru
  • 如何在 AppEngine (GAE) 中进行数据库锁定?

    在 GAE 中 我有一个充满 一次性 的表 诸如 最后使用的序列号 之类的东西 这些东西并不真正属于其他表 它是一个简单的字符串键和字符串值对 我有一些代码来获取命名整数并递增它 如下所示 PersistenceCapable detach
  • 并发:C++11 内存模型中的原子性和易失性

    全局变量在 2 个不同内核上的 2 个并发运行的线程之间共享 线程对变量进行写入和读取 对于原子变量 一个线程可以读取过时的值吗 每个核心可能在其缓存中具有共享变量的值 并且当一个线程写入缓存中的其副本时 不同核心上的另一个线程可能会从其自
  • 插入并发问题-多线程环境

    我有一个问题 即使用完全相同的参数在完全相同的时间调用相同的存储过程 存储过程的目的是获取记录 如果存在 或创建并获取记录 如果不存在 问题是两个线程都在检查记录是否存在并报告错误 然后都插入新记录 在数据库中创建重复记录 我尝试将操作保留
  • Openresty 中的并发模型是什么?

    我很难理解 openresty 或 nginx 的并发模型 我读了Lua变量作用域 http wiki nginx org HttpLuaModule Lua Variable Scope 它解释了变量的生命周期 但它没有说明对它们的并发访
  • 为什么在 10 个 Java 线程中递增一个数字不会得到 10 的值?

    我不明白 a 的值为0 为什么 a 不是10 那段代码的运行过程是怎样的 是否需要从Java内存模型来分析 这是我的测试代码 package com study concurrent demo import lombok extern sl
  • Guzzle 中的“并发”到底是什么?

    我没有找到太多关于concurrency选项中Pool 如果这是可以在服务器上打开的 TCP 套接字数量 那么问题是 我可以使用多少并发来更快地处理请求 我有这个使用的例子Pool I am using Laravel this is ba
  • 断点会停止所有线程吗?

    如果我的程序中有两个线程同时运行 并在其中一个线程上设置了断点 那么当遇到此断点时 另一个线程也会停止 还是会继续执行 我用 Java 编写并使用 NetBeans 断点可以选择它们的行为方式 挂起单个线程或所有线程

随机推荐

  • 从Python调用Matlab函数

    我有一个项目 其中有一个一个 matlab 代码 我必须运行 Django 我尝试安装 Mlabwrap 但它给了我以下错误 Traceback most recent call last File
  • 应用程序不再可用

    在移动设备上安装应用程序后 并将设备时间更改为一周前 并尝试打开应用程序它说 MyAppName Is No Longer Available 任何遇到此问题的人请告诉我 您的意见非常重要 我们有很多理由这样做 如果您没有有效的开发者许可证
  • 使用 tweepy 保存推文全文

    我是一个Python新手程序员 我在尝试使用以下命令提取一系列推文的文本时遇到问题tweepy并将其保存到文本文件 我省略了身份验证和其他内容 search api search hello count 10 textlist for i
  • 解析本地HTML文件

    我可以使用 PowerShell 解析 HTML 页面 PS gt foo Invoke WebRequest http example com PS gt foo Links Count 1 但是 如果我下载该页面 PS gt Invok
  • 结构错误中的联合

    我有以下结构 struct type1 struct type2 node union element struct type3 e int val 初始化指针时 f指向一个实例type1并做类似的事情 f element gt e甚至只是
  • 在 MATLAB 中更改 seqlogo 图形的 x 轴

    我正在制作大量seqlogos http www mathworks com access helpdesk help toolbox bioinfo ref seqlogo html以编程方式 它们有数百列宽 因此运行seqlogo通常会
  • 如何让网格为空白单元格绘制边框?

    我有一个 ItemsControl 它使用Grid as the ItemsPanelTemplate 并设置 Grid Column 和 Grid RowItemContainerStyle在网格中定位数据项 有没有办法将网格线添加到网格
  • CSS3 flexbox 调整垂直对齐元素的高度

    是否有可能使 item 1高度灵活可调 item 2高度 例如 if item 1高度是10 then item 2高度是90 if item 1高度是11 then item 2高度是89 所以根据内容 item 1我们应该调整它的大小
  • 时间:如何获得下周五?

    我怎样才能得到下周五乔达时间 http www joda org joda time API The LocalDate http www joda org joda time apidocs org joda time LocalDate
  • json_normalize JSON 文件,包含包含字典的多级列表(包含示例)

    最初是从上一个问题 https stackoverflow com questions 51236433 json normalize json file with list containing dictionary sample inc
  • HTML5 Canvas 描边未消除锯齿

    我只是想在画布上用粗的抗锯齿笔画画一个圆圈 圆圈按预期绘制 但笔划的边缘非常锯齿状 我一直读到 Chrome 强制抗锯齿 所以不知道该怎么办 Fiddle http jsfiddle net nipponese hWsxw http jsf
  • 类型错误:列表索引必须是整数或切片,而不是 str“转换字符”

    Number 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 for n in range 0 20 print Number n 1 n InputNum3 input Number I
  • VueJs - 表单提交时的 PreventDefault()

    我需要以编程方式提交表单 但我需要它preventDefault以及 现在我有以下内容 submit this refs form submit 它工作正常 但我无法阻止提交的默认设置 最终刷新页面 简短回答 您可以添加 prevent修饰
  • Qt:如何在 QAbstractItemModel 中的子表上设置标题?

    QAbstractItemModel 有一个 setHeaderData int section 方法 该方法采用行或列的部分 具体取决于标题方向 我有一个模型 其中包含几个表 这些表都是顶部项目的子项 也就是说 我的模型层次结构的第一级
  • 使用 C# 将查询插入 Paradox 表时出现问题

    我需要连接 Paradox 5 x 表才能进行选择和更新 我正在使用 OLEDBConnection 从表中选择我没有问题 在尝试插入表时 我遇到了一个问题 当我输入硬编码的字段名称时 出现错误 INSERT INTO 语句包含以下未知字段
  • 使用 Devise 身份验证进行 Ruby on Rails 功能测试

    我正在寻找一个奇怪问题的解决方案 我有一个控制器 需要身份验证 使用 devise gem 我添加了 Devise TestHelpers 但无法让它工作 require test helper class KeysControllerTe
  • 如何按名称删除 S3 存储桶中的旧文件?

    很像在使用前缀的 S3 Bucket 管理 生命周期 https stackoverflow com questions 38969953 aws s3 lifecycle rule on multiple folders 38970507
  • 将早期绑定代码转换为后期绑定

    我经常在 VBA 和 VB6 中编写早期绑定代码来自动化办公应用程序 Word Excel 等 然后将其切换到后期绑定以处理这些应用程序的多个版本 我正在尝试做同样的事情 但我正在自动化 ESRI ArcMap GIS 应用程序 而我过去使
  • @ExtendWith(SpringExtension.class) 和 @ExtendWith(MockitoExtension.class) 有什么区别?

    我正在使用 RunWith MockitoJUnitRunner class 我用mockito进行junit测试 但现在我正在使用 spring boot 和 JUnit 5 这两个注释有什么区别 我可以只使用 ExtendWith Sp
  • 关于cuda中修改flag数组的问题

    我正在研究 GPU 编程 并且有一个关于修改线程中的全局数组的问题 device float data 10 0 0 0 0 0 0 0 0 0 1 global void gradually set global data while 1