【问题标题】:How do you prevent thrust::reduce_by_key from writing to pageable memory?如何防止thrust::reduce_by_key 写入可分页内存?
【发布时间】:2020-11-04 12:39:10
【问题描述】:

我正在编写一个使用多个并发 CUDA 流的应用程序。当我的thrust::reduce_by_key 调用似乎写入可分页内存时,我的其他流被阻塞。我认为返回值是问题。

如何防止返回值被写入可分页内存?

我将包含演示我尝试的解决方案的代码。


#include <thrust/system/cuda/vector.h>
#include <thrust/host_vector.h>
#include <thrust/pair.h>
#include <iostream>
#include <thrust/device_vector.h>
#include <thrust/reduce.h>
#include <thrust/random.h>

int main(void)
{
  int N = 20;
  thrust::default_random_engine rng;
  thrust::uniform_int_distribution<int> dist(10, 99);

  // initialize data
  thrust::device_vector<int> array(N);
  for (size_t i = 0; i < array.size(); i++)
    array[i] = dist(rng);

  // allocate storage for sums and indices
  thrust::device_vector<int> sums(N);
  thrust::device_vector<int> indices(N);

  // make a pinned memory location for the returned pair of iterators
  typedef thrust::device_vector<int>::iterator  dIter;
  thrust::pair<dIter, dIter>*  new_end;

  const unsigned int bytes =  sizeof(thrust::pair<dIter, dIter>);
  cudaMallocHost((void**)&new_end, bytes);

  for(int i = 0 ; i< 20; i++){   // you can see in the profiler each operator writes 4 bytes to pageable memory

        *new_end = thrust::reduce_by_key
            (thrust::make_counting_iterator(0),
             thrust::make_counting_iterator(N),
             array.begin(),
             indices.begin(),
             sums.begin(),
             thrust::equal_to<int>(),
             thrust::plus<int>());
  }
  std::cout << "done \n";
  return 0;
}

这是我的分析器的图片,显示了从设备到主机可分页内存的副本

【问题讨论】:

  • 只是一个提示 -- Stack Overflow 是一个问答网站。 “我需要帮助”不是问题。这个地方的整个想法是您提出可能与更广阔的世界相关且有用的问题。有人回答了这个问题,并且问答对添加到每个人都可以使用的潜在有用信息的语料库中
  • 使用 cub::DeviceReduce::ReduceByKey 代替。然后你需要自己管理复制到主机,并且可以为它使用固定内存。
  • 我更改了标题和文字,希望更贴切地反映 Stack Overflow 的目的。我希望这个问题的答案能帮助更多的开发者,而不仅仅是我。
  • 补充讨论here

标签: cuda thrust


【解决方案1】:

我正在编写一个使用多个并发 CUDA 流的应用程序。当我的 thrust::reduce_by_key 似乎要写入可分页内存时,我的其他流正在阻塞

这种阻塞行为不是由“写入可分页内存”引起的。这是由explicit synchronization call 引起的。一般来说,从 CUDA 10.1(推力 1.9.4)版本开始,all normal synchronous algorithms are blocking。您可以通过使用分析器检查 API 跟踪来自己确认这一点。但是,您至少可以通过向stream 发起调用来限制阻塞的范围,尽管我懒得测试这是否会以有用的方式修改cuda_cub::synchronize 的行为。

如何防止返回值被写入可分页内存?

并不是说这与您的问题有任何关系,但您不能。重要的是要记住,与您最初的问题所断言的相反,thrust::reduce_by_key 不是内核,它是执行一系列操作的主机代码,包括copying the return value from device memory to a host stack variable。内部没有程序员控制,显然您尝试使用自己的固定内存值来接受按值传递的结果是荒谬的,并且不会产生任何效果。

正如 cmets 中所建议的,如果您需要问题所建议的操作内部控制粒度级别,那么推力是错误的选择。使用cub::device::reduce_by_key——这与thrust 使用的算法实现相同,但您可以明确控制暂存内存、同步、流以及如何访问调用结果。但是,这不适合初学者。

【讨论】:

  • 感谢您查看我的问题。我对阻塞单个流和线程的调用很好,因为这是预期的行为。我正在使用单独的流并将调用启动到流中不会改变导致问题的行为。问题是当返回值从设备内存写入未固定(可分页内存)时,它会强制阻止所有流的 cudaDeviceSynchronize。我猜任何减少转换都会导致所有流的阻塞。我想我一定是做错了什么,因为我不相信这是预期的行为。
  • “问题是当返回值从设备内存写入未固定(可分页内存)时,它会强制执行 cudaDeviceSynchronize”——这不是发生的事情,也是不可能的。 cuda_cub::synchronize 正常解析为 cudaDeviceSynchronize。我认为如果选择了基于流的执行策略,它可能会更改为 cudaStreamSynchronize
  • 至于“我不相信这是预期的行为”——请考虑一下。 按值返回函数的结果。在结果可用之前,它怎么可能在不阻塞调用者的情况下做到这一点?我们在这里用命令式语言编写
  • 对不起,我不清楚。我一直相信我调用的线程和我使用的流会被阻塞。正如您所说,这是唯一可行的方法。我曾希望,因为我正在分离我的工作线程并让每个线程使用不同的流,所以我不会有一个 reduce 块所有流。我能找到的唯一解释是从设备写入可分页内存。我想不出流定向减少需要设备同步的原因。它应该只需要一个streamSynchronize
  • 当您查看 nvprof --print-api-trace 的输出时,您可以清楚地看到对 cudaDeviceSynchronize 的调用,因此我认为您认为 cuda_cub::synchronize 解析为 cudaDeviceSynchronize 的假设是正确的。因此,在这种情况下,推力对我不起作用,我曾认为对可分页内存的写入会导致隐式同步。我在 CUDA cs.unc.edu/~tamert/papers/ecrts18b.pdf 上找到了一篇论文,该论文看起来经过充分研究,并指出写入可分页内存不会隐式同步。表 3.
猜你喜欢
  • 1970-01-01
  • 2016-08-04
  • 1970-01-01
  • 1970-01-01
  • 2019-02-20
  • 1970-01-01
  • 2022-11-28
  • 2021-12-25
  • 2016-03-15
相关资源
最近更新 更多