Boost.Compute медленнее, чем обычный процессор?

Я только начал играть с 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) версия быстрее?

6

Решение

Как уже говорили другие, в вашем ядре, скорее всего, недостаточно вычислений, чтобы иметь смысл работать на 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 уже делает это).

8

Другие решения

Я вижу одну возможную причину большой разницы. Сравните процессор и поток данных графического процессора:

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.

В вашем примере перемещение лямбды в накопитель будет быстрее (один проход по памяти против двух проходов)

2

Вы получаете плохие результаты, потому что вы неправильно измеряете время.

OpenCL Device имеет свои собственные счетчики времени, которые не связаны со счетчиками хостов. Каждая задача OpenCL имеет 4 состояния, таймеры для которых можно запросить: (от Веб-сайт Хронос)

  1. CL_PROFILING_COMMAND_QUEUEDкогда команда, указанная в событии, ставится в очередь в очередь команд хостом
  2. CL_PROFILING_COMMAND_SUBMITкогда команда, идентифицированная событием, которое было поставлено в очередь, передается хостом устройству, связанному с очередью команд.
  3. CL_PROFILING_COMMAND_STARTкогда команда, идентифицированная событием, начинает выполнение на устройстве.
  4. CL_PROFILING_COMMAND_ENDкогда команда, идентифицированная событием, завершила выполнение на устройстве.

Учтите, что таймеры Устройство на стороне. Итак, чтобы измерить ядро & производительность очереди команд, вы можете запросить эти таймеры. В вашем случае нужны 2 последних таймера.

В вашем примере кода вы измеряете хозяин время, которое включает в себя время передачи данных (как Skizz сказал) плюс все время впустую на обслуживание очереди команд.

Итак, чтобы узнать реальную производительность ядра, вам нужно либо передать cl_event в ваше ядро ​​(не знаю, как это сделать в boost :: compute) & запросить это событие для счетчиков производительности или сделать ваше ядро ​​действительно огромным & сложно скрыть все накладные расходы.

1