5

我的这段代码是经过分析、优化和缓存高效的,因为我可能会根据我的知识水平得到它。它在概念上像这样在 CPU 上运行:

#pragma omp parallel for schedule(dynamic)
  for (int i = 0; i < numberOfTasks; ++i)
  {
    result[i] = RunTask(i); // result is some array where I store the result of RunTask.
  }

碰巧它RunTask()本质上是一组线性代数运算,每次都在同一个非常大的数据集上重复运行,因此它适合在 GPU 上运行。所以我想实现以下目标:

  1. 将一些任务卸载到 GPU
  2. 在 GPU 忙碌时,处理 CPU 上的其余任务
  3. 对于 CPU 级别的操作,保留我的 super-duperRunTask()功能,而无需修改它以符合restrict(amp). 我当然可以restrict(amp)为 GPU 任务设计一个兼容的 lambda。

最初我想这样做:

// assume we know exactly how much time the GPU/CPU needs per task, and this is the 
// most time-efficient combination:
int numberOfTasks = 1000;
int ampTasks = 800;

// RunTasksAMP(start,end) sends a restrict(amp) kernel to the GPU, and stores the result in the
// returned array_view on the GPU
Concurrency::array_view<ResulType, 1> concurrencyResult = RunTasksAMP(0,ampTasks);

// perform the rest of the tasks on the CPU while we wait
#pragma omp parallel for schedule(dynamic)
  for (int i = ampTasks; i < numberOfTasks; ++i)
  {
    result[i] = RunTask(i); // this is a thread-safe
  }

// do something to wait for the parallel_for_each in RunTasksAMP to finish.
concurrencyResult.synchronize();
//... now load the concurrencyResult array into the first elements of "result"

但我怀疑你可以做这样的事情,因为

对 parallel_for_each 的调用表现得好像它是同步的

( http://msdn.microsoft.com/en-us/library/hh305254.aspx )

那么是否有可能实现我的 1-3 个请求,还是我必须放弃第 3 个请求?即便如此,我将如何实现它?

4

1 回答 1

4

请参阅我对array_view.synchronize_asynch 是否等待 parallel_for_each 完成的回答?关于为什么parallel_for_each可以作为排队或调度操作而不是同步操作的解释。这解释了为什么您的代码应该满足您的要求 1 和 2。它也应该满足要求 3,尽管您可能需要考虑拥有一个功能,restrict(cpu, amp)因为这样可以减少需要维护的代码。

但是,您可能需要考虑您的方法的一些性能影响。

首先,parallel_for_each唯一的队列工作,来自主机和 GPU 内存的数据副本使用主机资源(假设您的 GPU 是离散的和/或不支持直接复制)。如果您在主机上的工作使保持 GPU 工作所需的所有资源饱和,那么您实际上可能会减慢 GPU 计算速度。

其次,对于许多数据并行且适合在 GPU 上运行的计算,它们的速度要快得多,以至于尝试在 CPU 上运行工作的额外开销不会导致整体加速。开销包括第一项(上图)和在主机上协调工作的额外开销(调度线程、合并结果等)。

最后,您上面的实现没有考虑在 GPU 和 CPU 上运行任务所用时间的任何可变性。它假设 800 个 AMP 任务将花费 200 个 cpu 任务。这在某些硬件上可能是正确的,但在其他硬件上则不然。如果一组任务花费的时间比预期的要长,那么您的应用程序将阻塞并等待较慢的一组任务完成。您可以使用 master/worker 模式从队列中提取任务,直到没有更多可用任务为止,从而避免这种情况。这种方法意味着在最坏的情况下,您的应用程序将不得不等待最终任务完成,而不是等待任务块。使用 master/worker 方法还意味着您的应用程序将以相同的效率运行,而不管相对 CPU/GPU 性能如何。

我的书讨论了使用 master/worker(n-body)和并行队列(cartoonizer)跨多个 GPU 调度工作的示例。您可以从CodePlex下载源代码。请注意,基于与 C++ AMP 产品团队的讨论,出于上述原因,它故意不包括在 CPU 和 GPU 上共享工作。

于 2013-11-11T16:07:00.870 回答