[英]Boost.Compute slower than plain CPU?
我刚开始玩Boost.Compute,看看它能为我们带来多少速度,我写了一个简单的程序:
#include <iostream>
#include <vector>
#include <algorithm>
#include <boost/foreach.hpp>
#include <boost/compute/core.hpp>
#include <boost/compute/platform.hpp>
#include <boost/compute/algorithm.hpp>
#include <boost/compute/container/vector.hpp>
#include <boost/compute/functional/math.hpp>
#include <boost/compute/types/builtin.hpp>
#include <boost/compute/function.hpp>
#include <boost/chrono/include.hpp>
namespace compute = boost::compute;
int main()
{
// generate random data on the host
std::vector<float> host_vector(16000);
std::generate(host_vector.begin(), host_vector.end(), rand);
BOOST_FOREACH (auto const& platform, compute::system::platforms())
{
std::cout << "====================" << platform.name() << "====================\n";
BOOST_FOREACH (auto const& device, platform.devices())
{
std::cout << "device: " << device.name() << std::endl;
compute::context context(device);
compute::command_queue queue(context, device);
compute::vector<float> device_vector(host_vector.size(), context);
// copy data from the host to the device
compute::copy(
host_vector.begin(), host_vector.end(), device_vector.begin(), queue
);
auto start = boost::chrono::high_resolution_clock::now();
compute::transform(device_vector.begin(),
device_vector.end(),
device_vector.begin(),
compute::sqrt<float>(), queue);
auto ans = compute::accumulate(device_vector.begin(), device_vector.end(), 0, queue);
auto duration = boost::chrono::duration_cast<boost::chrono::milliseconds>(boost::chrono::high_resolution_clock::now() - start);
std::cout << "ans: " << ans << std::endl;
std::cout << "time: " << duration.count() << " ms" << std::endl;
std::cout << "-------------------\n";
}
}
std::cout << "====================plain====================\n";
auto start = boost::chrono::high_resolution_clock::now();
std::transform(host_vector.begin(),
host_vector.end(),
host_vector.begin(),
[](float v){ return std::sqrt(v); });
auto ans = std::accumulate(host_vector.begin(), host_vector.end(), 0);
auto duration = boost::chrono::duration_cast<boost::chrono::milliseconds>(boost::chrono::high_resolution_clock::now() - start);
std::cout << "ans: " << ans << std::endl;
std::cout << "time: " << duration.count() << " ms" << std::endl;
return 0;
}
这是我的机器上的示例输出(win7 64位):
====================Intel(R) OpenCL====================
device: Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
ans: 1931421
time: 64 ms
-------------------
device: Intel(R) HD Graphics 4600
ans: 1931421
time: 64 ms
-------------------
====================NVIDIA CUDA====================
device: Quadro K600
ans: 1931421
time: 4 ms
-------------------
====================plain====================
ans: 1931421
time: 0 ms
我的问题是:为什么普通(非opencl)版本更快?
正如其他人所说的那样,你的内核中很可能没有足够的计算来使得在GPU上运行单组数据是值得的(你受到内核编译时间和GPU传输时间的限制)。
为了获得更好的性能数字,你应该多次运行算法(并且很可能会丢弃第一个算法,因为它包含编译和存储内核的时间,因此会更大)。
此外,不应将transform()
和accumulate()
作为单独的操作运行,而应使用融合的transform_reduce()
算法,该算法使用单个内核执行转换和缩减。 代码如下所示:
float ans = 0;
compute::transform_reduce(
device_vector.begin(),
device_vector.end(),
&ans,
compute::sqrt<float>(),
compute::plus<float>(),
queue
);
std::cout << "ans: " << ans << std::endl;
您还可以使用Boost.Compute和-DBOOST_COMPUTE_USE_OFFLINE_CACHE
编译代码,这将启用脱机内核缓存(这需要与boost_filesystem
链接)。 然后,您使用的内核将存储在您的文件系统中,并且只在您第一次运行应用程序时进行编译(默认情况下,Linux上的NVIDIA已经执行此操作)。
我可以看到一个可能的原因造成重大差异。 比较CPU和GPU数据流: -
CPU GPU
copy data to GPU
set up compute code
calculate sqrt calculate sqrt
sum sum
copy data from GPU
鉴于此,看起来英特尔芯片在一般计算上只是有点垃圾,NVidia可能会受到额外数据复制和设置GPU进行计算的困扰。
您应该尝试相同的程序,但操作更复杂 - sqrt和sum太简单,无法克服使用GPU的额外开销。 例如,您可以尝试计算Mandlebrot点数。
在你的例子中,将lambda移动到累积中会更快(一次通过内存而不是两次通过)
你得到的结果不好,因为你的测量时间不正确。
OpenCL设备有自己的时间计数器,与主机计数器无关。 每个OpenCL任务都有4个状态,可以查询定时器:(来自Khronos网站)
CL_PROFILING_COMMAND_QUEUED
,当事件标识的命令被主机排入命令队列时 CL_PROFILING_COMMAND_SUBMIT
,当由已排队的事件标识的命令由主机提交CL_PROFILING_COMMAND_SUBMIT
命令队列关联的设备时。 CL_PROFILING_COMMAND_START
,当事件标识的命令在设备上开始执行时。 CL_PROFILING_COMMAND_END
,当事件标识的命令在设备上完成执行时。 考虑到,计时器是设备端 。 因此,要测量内核和命令队列性能,您可以查询这些计时器。 在您的情况下,需要2个最后的计时器。
在您的示例代码中,您正在测量主机时间,其中包括数据传输时间(如Skizz所述)以及在命令队列维护上浪费的所有时间。
因此,要了解实际的内核性能,您需要将cl_event传递给内核(不知道如何在boost :: compute中执行)并查询该事件以获得性能计数器,或者使内核真正庞大而复杂以隐藏所有开销。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.