ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

cdpAdvancedQuicksort:基于 CUDA Dynamic Parallelism 的进阶并行快速排序实现指南

cdpAdvancedQuicksort:基于 CUDA Dynamic Parallelism 的进阶并行快速排序实现指南 cdpAdvancedQuicksort基于 CUDA Dynamic Parallelism 的进阶并行快速排序实现指南【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples导读本文围绕 CUDA Samples 仓库中的 cdpAdvancedQuicksort 示例展开剖析如何使用 CUDA Dynamic ParallelismCDP在 GPU 设备端递归地派生内核实现一个自启动、自协调的并行快速排序。读完本文你将掌握 CDP 的设备端内核启动机制、基于 Warp 的并行分区算法、环形缓冲区的无锁栈分配技巧以及 bitonic sort 作为递归终止兜底策略的完整设计并能独立编译运行该示例、使用其全部命令行参数验证排序正确性与性能。示例概览CDP 如何让排序内核自己管自己cdpAdvancedQuicksort 是 NVIDIA CUDA Samples 中3_CUDA_Features目录下的一个进阶示例其核心思路是用 CUDA Dynamic Parallelism 实现一个可递归调用的快速排序。传统 GPU 排序需要主机端反复启动内核 → 同步 → 分析结果 → 再次启动而本示例让设备端内核在分区完成后直接从 GPU 上派生出两个新的子排序内核主机端只需做一次初始启动。按 README 的说明该示例的关键约束与特性如下依赖 CDP 特性需要计算能力Compute Capability3.5 或更高版本的设备关键概念Cooperative Groups 与 CUDA Dynamic Parallelism 两个特性叠加使用支持的操作系统Linux、Windows支持的 CPU 架构x86_64、armv7l构建/运行依赖CUDA Dynamic Parallelism详见根目录 README 的说明。整体算法架构三段式递归设计在 cdpAdvancedQuicksort.cu 的文件头注释中作者明确给出了该实现的三个组成部分小规模集合的插入排序small-set insertion sort对任意 32个元素的子集直接处理避免为过小的问题规模付出启动开销分区内核partitioning kernel给定一个 pivot把输入数组分离为 pivot与 pivot两段然后分别启动两个新的 quicksort 递归处理这两段快速排序协调器quicksort co-ordinator负责决定何时、以怎样的网格配置启动下一批内核。从实际实现看小规模集合的落地点是双调排序bitonic sort——源码中BITONICSORT_LEN 1024定义见 cdpQuicksort.h即长度小于等于 1024 就改用 bitonic sort 兜底的分界阈值。因此更准确的描述是递归主干qsort_warp内核负责并行分区并派生子任务递归终点 1段长 1024时用bitonicsort内核收尾递归终点 2递归深度超过QSORT_MAXDEPTH (16)时用单 block 的big_bitonicsort应急排序任意大小的剩余数据见 cdpBitonicSort.cu。核心内核qsort_warpWarp 级并行分区整个递归的主体是 qsort_warp 内核其工作流程可以拆成如下阶段1. 数据定位与 pivot 选择unsigned int thread_id threadIdx.x (blockIdx.x QSORT_BLOCKSIZE_SHIFT); unsigned int lane_id threadIdx.x (warpSize - 1); if (thread_id len) return; unsigned pivot indata[offset len / 2]; unsigned data indata[offset thread_id];每个线程对应一个元素一元素一线程thread_id越界即退出pivot 的选取策略是取当前子数组中间位置的元素注释也说明这是任意的 pivot 选择。2. 用 Cooperative Groups 做 Warp 级投票统计为了避免死循环代码处理了一个经典陷阱如果所有元素都 pivot则大于分区为空排序会原地空转。因此它先用cg::coalesced_group做一次 ballot 投票cg::coalesced_group active cg::coalesced_threads(); unsigned int greater (data pivot); unsigned int gt_mask active.ballot(greater); if (gt_mask 0) { greater (data pivot); // 调整比较器改为 pivot 视为大于 gt_mask active.ballot(greater); // 必须重新投票 }随后用__popc统计 Warp 内大于/不大于 pivot 的元素个数并由 lane 0 通过原子操作把各 Warp 的计数累加到全局偏移上if (lane_id 0) { if (lt_count 0) lt_offset atomicAdd((unsigned int *)atomicData-lt_offset, lt_count); if (gt_count 0) gt_offset len - (atomicAdd((unsigned int *)atomicData-gt_offset, gt_count) gt_count); } lt_offset active.shfl((int)lt_offset, 0); // 通过 shfl 广播给整个 Warp gt_offset active.shfl((int)gt_offset, 0);这里lt_offset从数组头部向后累加gt_offset则从数组尾部向前累加两者相向而行填充分区结果。3. 单指令 Warp 内扫描确定写入位置每个线程还需要知道本 Warp 内有多少 lane 号比我小、且与我去同一侧缓冲区的线程这通过%%lanemask_lt内联 PTX 与__popc一步完成asm(mov.u32 %0, %%lanemask_lt; : r(lane_mask_lt)); unsigned int my_mask greater ? gt_mask : lt_mask; unsigned int my_offset __popc(my_mask lane_mask_lt); my_offset greater ? gt_offset : lt_offset; outdata[offset my_offset] data;这是典型的popc 实现单操作 Warp scan技巧避免了一次完整的并行扫描开销。4. 最后一个完成的 Warp 负责派生下一层每个 Warp 由 lane 0 把本 Warp 已处理的元素数累加到sorted_count当累加值恰好等于len时说明本层所有元素都已完成分区由这个 Warp 承担协调器职责直接在设备端启动下一层内核if (atomicAdd((unsigned int *)atomicData-sorted_count, mycount) mycount len) { cudaStream_t lstream, rstream; cudaStreamCreateWithFlags(lstream, cudaStreamNonBlocking); cudaStreamCreateWithFlags(rstream, cudaStreamNonBlocking); // ... 对 lt 段与 gt 段分别启动 qsort_warp / bitonicsort / big_bitonicsort }这里用到的cudaStreamCreateWithFlags正是 README 中列出的核心 CDP API 之一设备端可以创建非阻塞流让左右两个子排序并行执行。递归终止与深度控制双保险设计从 cdpQuicksort.h 可以看到三个关键宏宏值含义QSORT_BLOCKSIZE_SHIFT9block 尺寸的对数QSORT_BLOCKSIZE 512BITONICSORT_LEN1024小于等于该长度改用 bitonic sort必须为 2 的幂QSORT_MAXDEPTH16最大递归深度超过后走big_bitonicsort兜底QSORT_STACK_ELEMS1M2^20环形缓冲区的栈元素个数必须为 2 的幂在qsort_warp内对左/右两段的处理逻辑完全对称len BITONICSORT_LEN且depth QSORT_MAXDEPTH继续递归从环形缓冲区分配新的qsortAtomicData以depth 1启动qsort_warplen BITONICSORT_LEN但深度越界启动big_bitonicsortcdpBitonicSort.cu 中的紧急 CTA 排序单 block 即可排序任意大小数据把非 2 的幂长度补齐为 2 的幂并用0xffffffffu填充越界元素1 len 1024启动bitonicsort线程数取1 (__qsflo(lt_len - 1U) 1)即能覆盖该长度的最小 2 的幂len 1且当前数据在indata单个元素直接搬运到正确位置。深度控制之所以必要是因为 CDP 的设备端递归会消耗待启动内核配额无限递归既可能耗尽配额也可能导致死锁所以用深度阈值 bitonic 兜底保证递归必然收敛。环形缓冲区设备端无锁栈分配器递归过程中每一层都需要一个新的qsortAtomicData内含 3 个原子计数器 索引数量无法预知因此在设备端用**环形缓冲区ring buffer**管理结构定义见 cdpQuicksort.hqsortAtomicData以__align__(128)对齐以避免同一缓存行内的原子争用false sharingringbufAlloc 通过atomicAdd申请槽位用index (stacksize - 1)实现环形回绕若缓冲区满则自旋等待最多重试 10000 次超时返回 NULL 以防内存耗尽死锁ringbufFree 采用atomicMax 技巧支持乱序归还每个元素归还时递增count并用atomicMax更新max当前最大已归还索引只有当已归还总数 已分配最大索引时tail才推进从而允许子任务以任意顺序完成而不丢失可分配空间。这个设计在注释中被反复强调先释放再用比复用更能减少碎片是设备端递归内存管理的一个实用范本。主机端调度run_quicksort_cdp一次启动、全程设备自驱动主机端入口 run_quicksort_cdp 相当简洁因为所有 launch 控制都在设备端完成cudaMalloc分配QSORT_STACK_ELEMS个qsortAtomicData作为栈并用cudaMemset清零首个元素构造并拷贝qsortRingbuf结构head 1表示已有一份初始分配占用其中stackbase指向上述栈创建cudaEvent对排序计时若count BITONICSORT_LEN则以每元素一线程的网格启动一次qsort_warp否则直接一次bitonicsort完成cudaDeviceSynchronize等待整棵递归树结束用cudaEventElapsedTime统计耗时把环形缓冲区拷回主机做一致性自检若buf.head ! buf.tail说明存在泄漏的栈槽位打印head/tail/count/max四元组报错。注意该函数要求调用方预先提供一个与数据等大的 scratch 缓冲——所有并行快排都需要等尺寸的临时缓冲来交换数据indata与outdata每层递归互换角色source_is_indata标志跟踪当前数据所在位置。命令行参数与运行验证main函数通过 helper_string.h 提供的checkCmdLineFlag/getCmdLineArgumentInt解析参数用法如下cdpAdvancedQuicksort [-sizenum] [-seednum] [-debug] [-loop-stepnum] [-verbose]参数默认值说明-sizenum1000000待排序元素个数-seednum0随机数种子0 时调用srand(seed)保证可复现-debug关闭调试模式数据以 0~9 的单字节构造便于人工核对-loop-stepnum0非 0 时从 1 递增到size步长为该值逐个规模运行并验证-verbose关闭打印输入/输出数组内容每行 32 个元素-help/-h—打印用法并 WAIVED 退出排序正确性由主机端逐元素检查data[check] data[check - 1]即判失败每轮输出cdpAdvancedQuicksort PASSED Sorted %u elems in %.3f ms (%.3f Melems/sec)设备能力检查同样在main中完成通过cudaGetDeviceProperties读取算力(major 3 minor 5) || major 4判定 CDP 可用性不满足则打印提示并EXIT_WAIVED退出。同时调用cudaDeviceSetLimit(cudaLimitDevRuntimePendingLaunchCount, 4096);即把设备端运行时待启动内核配额提升到 4096为深层递归留出余量——这是所有 CDP 递归型内核的通用最佳实践README 中列出的cudaDeviceSetLimit即此调用。构建方式示例采用 CMake 构建CMakeLists.txt 中的要点需要 CUDA Toolkitfind_package(CUDAToolkit REQUIRED)默认针对75 80 86 89 90 100 110 120等现代算力编译aarch64/Tegra 工具链下为87 110开启CUDA_SEPARABLE_COMPILATION可分离编译——这是 CDP 设备端启动的硬性要求设备端派生的内核必须经可分离编译链接进 cubin使用 C17 / CUDA 17 标准并开启--extended-lambda调试构建ENABLE_CUDA_DEBUG下以-G编译并限制寄存器数为 64针对big_bitonicsortRelease 构建则附加-lineinfo便于性能分析。从cpp目录执行标准的 CMake 流程即可构建构建产物为可执行文件cdpAdvancedQuicksort。与兄弟示例的关联本示例与 cdpSimpleQuicksort 形成进阶对照后者演示 CDP 快速排序的基础形态前者则加入了 Warp 级分区、环形缓冲区栈分配、bitonic 兜底与深度控制等工程化设计。同一目录下还有 cdpBezierTessellation、cdpQuadtree、cdpSimplePrint 等 CDP 示例共同构成完整的 CDP 学习路径目录索引见 cpp/3_CUDA_Features/README.md。小结cdpAdvancedQuicksort 的工程价值在于展示了 CDP 的三类核心技巧设备端派生递归最后一个完成的 Warp 通过cudaStreamCreateWithFlags派生左右子树主机端一次启动即可完成整棵递归树设备端内存自管理atomicAddatomicMax实现的环形缓冲区支持乱序归还、无锁分配配合cudaLimitDevRuntimePendingLaunchCount配额提升构成稳健的递归资源池确定性收敛Cooperative Groups 的 ballot 投票统计分区、popc 单指令 Warp scan、bitonic sort 兜底与深度上限共同保证排序必然完成且结果正确。该示例明确要求 SM 3.5 设备README 原始要求与可分离编译CMake 配置在较新的 Toolkit 上即可直接编译运行是理解 CDP 递归模型与 GPU 端任务自调度机制的理想范本。【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表