cudaStreamSynchronize之后的任何CUDA操作都会阻塞,直到所有流完成

时间:2014-04-21 18:55:33

标签: c++ cuda

在使用NVIDIA Visual Profiler分析我的CUDA应用程序时,我注意到cudaStreamSynchronize之后的任何操作都会阻塞,直到所有流完成。这是非常奇怪的行为,因为如果cudaStreamSynchronize返回意味着流已完成,对吧?这是我的伪代码:

std::list<std::thread> waitingThreads;

void startKernelsAsync() {
    for (int i = 0; i < 200; ++i) {
        cudaHostAlloc(cpuPinnedMemory, size, cudaHostAllocDefault);
        memcpy(cpuPinnedMemory, data, size);
        cudaMalloc(gpuMemory);

        cudaStreamCreate(&stream);
        cudaMemcpyAsync(gpuMemory, cpuPinnedMemory, size, cudaMemcpyHostToDevice, stream);
        runKernel<<<32, 32, 0, stream>>>(gpuMemory);
        cudaMemcpyAsync(cpuPinnedMemory, gpuMemory, size, cudaMemcpyDeviceToHost, stream);

        waitingThreads.push_back(std::move(std::thread(waitForFinish, cpuPinnedMemory, stream)));
    }

    while (waitingThreads.size() > 0) {
        waitingThreads.front().join();
        waitingThreads.pop_front();
    }
}

void waitForFinish(void* cpuPinnedMemory, cudaStream_t stream, ...) {
    cudaStreamSynchronize(stream);
    cudaStreamDestroy(stream);  // <== This blocks until all streams are finished.
    memcpy(data, cpuPinnedMemory, size);
    cudaFreeHost(cpuPinnedMemory);
    cudaFree(gpuMemory);
}

如果我将cudaFreeHost放在cudaStreamDestroy之前,那么它就会成为阻止操作。

这里有什么概念上的错误吗?

编辑:我发现了另一个奇怪的行为,有时它会在流处理过程中解锁,然后处理其余的流。

正常行为:

Normal behavior

奇怪的行为(经常发生):

Strange behavior

EDIT2:我正在使用CUDA 6.0上的计算能力为3.5的Tesla K40c卡进行测试。

正如评论中所建议的那样,减少流的数量可能是可行的,但是在我的应用程序中,内存传输速度非常快,我想主要使用流来动态地为GPU工作。问题是在流完成之后我需要从固定内存下载数据并清除已分配的内存以用于进一步的流,这似乎是阻塞操作。

我每个数据集使用一个流,因为每个数据集的大小都不同,处理时间也不可预测。

任何想法如何解决这个问题?

2 个答案:

答案 0 :(得分:1)

我还没有找到为什么操作会阻塞但我得出的结论是我无法做任何事情所以我决定实现内存和流池(如评论中所建议的)重用GPU内存,固定CPU内存和流以避免任何类型的删除。

如果有人感兴趣,这是我的解决方案。启动内核表现为异步操作,调度内核并在内核完成后调用回调。

std::vector<Instance*> m_idleInstances;
std::vector<Instance*> m_workingInstances;

void startKernelAsync(...) {
    // Search for finished stream.
    while (m_idleInstances.size() == 0) {
        findFinishedInstance();
        if (m_idleInstances.size() == 0) {
            std::chrono::milliseconds dur(10);
            std::this_thread::sleep_for(dur);
        }
    }

    Instance* instance = m_idleInstances.back();
    m_idleInstances.pop_back();

    // Fill CPU pinned memory

    cudaMemcpyAsync(..., stream);
    runKernel<<<32, 32, 0, stream>>>(gpuMemory);
    cudaMemcpyAsync(..., stream);

    m_workingInstances.push_back(clusteringInstance);
}

void findFinishedInstance() {
    for (auto it = m_workingInstances.begin(); it != m_workingInstances.end();) {
        Instance* inst = *it;
        cudaError_t status = cudaStreamQuery(inst->stream);
        if (status == cudaSuccess) {
            it = m_workingInstances.erase(it);
            m_callback(instance->clusterGroup);
            m_idleInstances.push_back(inst);
        }
        else {
            ++it;
        }
    }
}

然后等待所有人完成:

virtual void waitForFinish() {
    while (m_workingInstances.size() > 0) {
        Instance* instance = m_workingInstances.back();
        m_workingInstances.pop_back();
        m_idleInstances.push_back(instance);
        cudaStreamSynchronize(instance->stream);
        finalizeInstance(instance);
    }
}

这是一个图形表格分析器,作为一个魅力!

Graph of streams

答案 1 :(得分:0)

查看工具包随附的Cuda C编程指南PDF中的“隐式同步”规则列表。 (我的副本中的第3.2.5.5.4节,但您可能有不同的版本。)

如果您的GPU是“计算能力3.0或更低”,则可以使用一些特殊规则。我的猜测是cudaStreamDestroy()正在遇到其中一个限制。