// 6.中间结果计算(流0) dim3 original_block(BLOCK_SIZE); dim3 demodulate_grid((dstPixels + original_block.x - 1) / original_block.x); demodulate_gray_kernel << < demodulate_grid, original_block, 0, stream[0] >> > (fringes_vec_d, mid_vec_d, bitShift, dstPixels); // 7.降采样初始化 mGpuData.vecSz.clear(); mGpuData.vecSz.resize(SAMPLE_NUM + 1); mGpuData.vecSz[0] = cv::Size(mBasicParams.mCol, mBasicParams.mRow); for (int i = 1; i < SAMPLE_NUM + 1; i++) mGpuData.vecSz[i] = cv::Size((mGpuData.vecSz[i - 1].width + 1) >> 1, (mGpuData.vecSz[i - 1].height + 1) >> 1); if (mBasicParams.mReMallocCuda) { int sampleBytes = 0; for (int i = 0; i < mGpuData.vecGxxDev.size(); ++i) { cudaFree(mGpuData.vecGxxDev[i]); cudaFree(mGpuData.vecGxyDev[i]); cudaFree(mGpuData.vecGyxDev[i]); cudaFree(mGpuData.vecGyyDev[i]); } mGpuData.vecGxxDev.resize(SAMPLE_NUM + 1, nullptr); mGpuData.vecGxyDev.resize(SAMPLE_NUM + 1, nullptr); mGpuData.vecGyxDev.resize(SAMPLE_NUM + 1, nullptr); mGpuData.vecGyyDev.resize(SAMPLE_NUM + 1, nullptr); // 重复执行时也不会进入这里,不太需要进行流分配显存 for (int i = 0; i < SAMPLE_NUM + 1; i++) { sampleBytes = mGpuData.vecSz[i].area() * sizeof(short); cudaMalloc((void**)&mGpuData.vecGxxDev[i], sampleBytes); cudaMalloc((void**)&mGpuData.vecGxyDev[i], sampleBytes); cudaMalloc((void**)&mGpuData.vecGyxDev[i], sampleBytes); cudaMalloc((void**)&mGpuData.vecGyyDev[i], sampleBytes); } } // 8.降采样(流1) dim3 pyrDown_grid; std::vector<std::vector<T*>> fringeTower; // 设备上条纹图金字塔 std::vector<T**> sample_fringes_dev_d; // 降采样条纹图数组的数组 fringeTower.emplace_back(fringes_d); sample_fringes_dev_d.emplace_back(fringes_vec_d); fringeTower.resize(SAMPLE_NUM + 1); sample_fringes_dev_d.resize(SAMPLE_NUM + 1, nullptr); for (int i = 0; i < SAMPLE_NUM; ++i) { fringeTower[i + 1].resize(FRINGE_NUM, nullptr); } for (int i = 0; i < FRINGE_NUM; ++i) { for (int j = 0; j < SAMPLE_NUM; ++j) { int sampleAmount = mGpuData.vecSz[j + 1].area(); pyrDown_grid.x = (sampleAmount + original_block.x - 1) / original_block.x; cudaMallocAsync((void**)&fringeTower[j + 1][i], sampleAmount* dataType, stream[1]); pyrDown_kernel << < pyrDown_grid, original_block, 0, stream[1] >> > (fringeTower[j][i], fringeTower[j + 1][i], mGpuData.vecSz[j].width, mGpuData.vecSz[j].height, mGpuData.vecSz[j + 1].width, mGpuData.vecSz[j + 1].height); } } //cudaDeviceSynchronize(); cudaStreamSynchronize(stream[1]); 为什么我这段代码里流0和流1没有并行而是在串行计算?
时间: 2025-07-06 12:45:16 浏览: 9
<think>嗯,用户的问题是关于CUDA流中流0和流1没有并行执行,而是串行计算的原因。我需要仔细分析他提供的代码,找出可能的原因。
首先,用户提到代码中的流0和流1分别在步骤6和步骤8中使用。流0执行了demodulate_gray_kernel,而流1执行了pyrDown_kernel。用户观察到这两个流没有并行,而是按顺序执行,想知道为什么。
我需要回忆一下CUDA流的同步机制。CUDA中,如果操作是在不同的流中,默认情况下它们可能会并发执行,但有几个条件:比如没有依赖关系,资源足够,以及正确的流分配。如果其中一个流有同步操作,比如cudaStreamSynchronize,或者隐式同步点,就会导致串行。
看用户的代码,步骤6之后是步骤7的降采样初始化,这部分主要是CPU端的操作,比如调整vector的大小,循环中释放和分配显存。特别是,在步骤7中有一个条件判断,如果mBasicParams.mReMallocCuda为真,会执行cudaFree和cudaMalloc。这些CUDA内存操作可能引起隐式的设备同步。因为CUDA的内存管理函数,如cudaFree和cudaMalloc,通常是同步的,会导致主机线程等待所有之前的CUDA操作完成,这可能包括流0中的kernel执行。
接下来是步骤8的降采样处理,其中在循环中调用了cudaMallocAsync和pyrDown_kernel,使用流1。但在步骤8的末尾,调用了cudaStreamSynchronize(stream[1]),这会显式地同步流1,但可能在这之前,步骤7中的cudaMalloc或cudaFree已经导致流0的kernel必须完成,从而破坏了并发。
可能的同步点出现在步骤7的cudaFree和cudaMalloc,因为这些函数会隐式同步设备。当在流0中启动的demodulate_gray_kernel之后,执行这些同步的CUDA API调用时,主机必须等待流0的kernel完成,才能继续执行后续操作。因此,步骤7中的内存操作导致流0的kernel必须完成,之后才能启动流1的操作,导致两者串行。
此外,步骤8中的cudaMallocAsync是异步的,但需要检查是否在支持UM(统一内存)的环境下,或者是否正确使用流参数。如果cudaMallocAsync的调用没有正确指定流,或者在某些CUDA版本中不支持,可能会导致隐式同步。
另一个可能是在步骤8的循环中,对于每个i和j,可能连续启动多个pyrDown_kernel到流1,但如果在每次循环迭代中,启动kernel之后没有其他可以并行的操作,或者循环结构导致必须按顺序执行,也可能影响并行性。不过这可能更多影响流内部的并行,而不是流之间的。
总结,主要问题可能出在步骤7中的cudaFree和cudaMalloc引起的隐式同步,导致流0的kernel必须完成,之后才能执行流1的操作。此外,步骤8中同步流1的操作可能进一步导致串行,但关键点可能在步骤7的同步函数。
为了验证这一点,应该检查步骤7中的cudaFree和cudaMalloc是否是同步调用,如果是的话,它们会强制主机等待所有之前的CUDA操作完成,包括流0中的kernel,这样流1的操作只能在流0完成后启动,导致串行。解决方法可能包括使用异步的内存管理函数,或者重新安排内存操作,避免在流0和流1之间插入同步点。</think>您的代码中流0和流1出现串行执行的主要原因如下:
1. **隐式同步点(关键原因)**
在步骤7的显存重分配代码中:
```cpp
cudaFree(mGpuData.vecGxxDev[i]); // 同步操作
cudaMalloc(...); // 同步操作
```
这些CUDA内存操作会触发隐式设备同步,导致:
- 主机线程会阻塞直到之前所有流(包括流0)的任务完成
- 流0的`demodulate_gray_kernel`必须执行完毕后才能继续后续操作
2. **显式同步点**
步骤8最后的:
```cpp
cudaStreamSynchronize(stream[1]); // 强制等待流1完成
```
这会导致后续代码必须等待流1全部完成,但更关键的是前一个同步点已经破坏了并行性。
3. **依赖关系**
```cpp
// 步骤6(流0)
demodulate_gray_kernel <<<..., stream[0]>>>;
// 步骤7(隐式同步点)
cudaFree/cudaMalloc; // 这里会等待流0完成
// 步骤8(流1)
pyrDown_kernel <<<..., stream[1]>>>; // 必须等待流0完成后才开始
```
4. **内存操作特性**
`cudaMallocAsync`在代码中被正确使用,但步骤7中的传统`cudaMalloc`仍然是同步操作,且:
- 需要CUDA 11.2+和适当硬件支持异步内存操作
- 需要确认运行环境是否满足异步内存操作条件
**解决方案建议:**
1. **异步内存管理**
```cpp
// 替换同步操作为异步版本(需要CUDA 11.2+)
cudaFreeAsync(mGpuData.vecGxxDev[i], stream[0]);
cudaMallocAsync(&mGpuData.vecGxxDev[i], ..., stream[0]);
```
2. **重构资源管理**
- 在初始化阶段预先分配所有显存
- 避免在计算过程中进行显存分配/释放
3. **增加异步间隙**
```cpp
// 在流0启动kernel后立即安排流1的任务
demodulate_gray_kernel <<<..., stream[0]>>>;
pyrDown_kernel <<<..., stream[1]>>>; // 不等待流0完成直接启动
```
4. **使用CUDA Graph**
```cpp
// 将整个流程封装为CUDA Graph
cudaGraph_t graph;
cudaGraphCreate(&graph, 0);
// 添加所有操作节点...
cudaGraphInstantiate(...);
```
**验证方法:**
```cpp
// 在关键位置插入计时
cudaEvent_t start0, end0, start1, end1;
cudaEventCreate(&start0); ...
cudaEventRecord(start0, stream[0]);
demodulate_gray_kernel <<<..., stream[0]>>>;
cudaEventRecord(end0, stream[0]);
cudaEventRecord(start1, stream[1]);
pyrDown_kernel <<<..., stream[1]>>>;
cudaEventRecord(end1, stream[1]);
// 检查时间重叠区间
cudaEventElapsedTime(&time0, start0, end0);
cudaEventElapsedTime(&time1, start1, end1);
```
建议优先检查隐式同步点问题,这是CUDA流编程中最常见的性能陷阱。通过Nsight Systems工具可以直观看到kernel执行的时间线重叠情况。
阅读全文
相关推荐
















