Я только начал играть с 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-bit):
====================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
). Тогда используемые вами ядра будут сохранены в вашей файловой системе и скомпилированы только при первом запуске приложения (по умолчанию NVIDIA в Linux уже делает это).
Я вижу одну возможную причину большой разницы. Сравните процессор и поток данных графического процессора:
CPU GPU
copy data to GPU
set up compute code
calculate sqrt calculate sqrt
sum sum
copy data from GPU
Учитывая это, кажется, что чип Intel — это всего лишь мусор при общих вычислениях, NVidia, вероятно, страдает от дополнительного копирования данных и настройки графического процессора для выполнения вычислений.
Вы должны попробовать ту же самую программу, но с гораздо более сложной операцией — sqrt и sum слишком просты, чтобы преодолеть дополнительные затраты при использовании GPU. Например, вы можете попробовать рассчитать баллы Mandlebrot.
В вашем примере перемещение лямбды в накопитель будет быстрее (один проход по памяти против двух проходов)
Вы получаете плохие результаты, потому что вы неправильно измеряете время.
OpenCL Device имеет свои собственные счетчики времени, которые не связаны со счетчиками хостов. Каждая задача OpenCL имеет 4 состояния, таймеры для которых можно запросить: (от Веб-сайт Хронос)
CL_PROFILING_COMMAND_QUEUED
когда команда, указанная в событии, ставится в очередь в очередь команд хостомCL_PROFILING_COMMAND_SUBMIT
когда команда, идентифицированная событием, которое было поставлено в очередь, передается хостом устройству, связанному с очередью команд. CL_PROFILING_COMMAND_START
когда команда, идентифицированная событием, начинает выполнение на устройстве. CL_PROFILING_COMMAND_END
когда команда, идентифицированная событием, завершила выполнение на устройстве. Учтите, что таймеры Устройство на стороне. Итак, чтобы измерить ядро & производительность очереди команд, вы можете запросить эти таймеры. В вашем случае нужны 2 последних таймера.
В вашем примере кода вы измеряете хозяин время, которое включает в себя время передачи данных (как Skizz сказал) плюс все время впустую на обслуживание очереди команд.
Итак, чтобы узнать реальную производительность ядра, вам нужно либо передать cl_event в ваше ядро (не знаю, как это сделать в boost :: compute) & запросить это событие для счетчиков производительности или сделать ваше ядро действительно огромным & сложно скрыть все накладные расходы.