JayBeams  0.1
Another project to have fun coding.
ut_generic_reduce.cpp
Go to the documentation of this file.
1 #include <jb/opencl/config.hpp>
6 #include <jb/complex_traits.hpp>
7 
8 #include <boost/compute/command_queue.hpp>
9 #include <boost/compute/container.hpp>
10 #include <boost/compute/context.hpp>
11 #include <boost/compute/type_traits.hpp>
12 #include <boost/compute/types/complex.hpp>
13 #include <boost/test/unit_test.hpp>
14 #include <memory>
15 #include <random>
16 #include <sstream>
17 
18 namespace {
19 
20 /**
21  * A reducer to drive the test.
22  */
23 template <typename T, bool introduce_error = false>
24 class reduce_sum
25  : public jb::opencl::generic_reduce<reduce_sum<T, introduce_error>, T, T> {
26 public:
27  reduce_sum(std::size_t size, boost::compute::command_queue const& queue)
28  : jb::opencl::generic_reduce<reduce_sum<T, introduce_error>, T, T>(
29  size, queue) {
30  }
31 
32  /// @returns the body of the initialization function
33  static std::string initialize_body(char const* lhs) {
34  if (introduce_error) {
35  return std::string("*") + lhs + " = ; /* bad syntax error */";
36  }
37  return std::string("*") + lhs + " = 0;";
38  }
39 
40  /// @returns the body of the transform function
41  static std::string
42  transform_body(char const* lhs, char const* value, char const*) {
43  return std::string("*") + lhs + " = *" + value + ";";
44  }
45 
46  /// @returns the body of the combine function
47  static std::string combine_body(char const* accumulated, char const* value) {
48  return std::string("*") + accumulated + " = *" + accumulated + " + *" +
49  value + ";";
50  }
51 };
52 
53 } // anonymous namespace
54 
55 namespace {
56 
57 template <typename T>
58 std::function<T()> create_random_generator(unsigned int seed) {
59  std::mt19937 gen(seed);
60  std::uniform_int_distribution<T> dis(-1000, 1000);
61  using state_type = std::pair<std::mt19937, std::uniform_int_distribution<T>>;
62  std::shared_ptr<state_type> state(new state_type(gen, dis));
63  return [state]() { return state->second(state->first); };
64 }
65 
66 template <>
67 std::function<float()> create_random_generator<float>(unsigned int seed) {
68  std::mt19937 gen(seed);
69  std::uniform_real_distribution<float> dis(1, 2);
70  using state_type =
71  std::pair<std::mt19937, std::uniform_real_distribution<float>>;
72  std::shared_ptr<state_type> state(new state_type(gen, dis));
73  return [state]() { return state->second(state->first); };
74 }
75 
76 template <>
77 std::function<double()> create_random_generator<double>(unsigned int seed) {
78  std::mt19937 gen(seed);
79  std::uniform_real_distribution<double> dis(1, 2);
80  using state_type =
81  std::pair<std::mt19937, std::uniform_real_distribution<double>>;
82  std::shared_ptr<state_type> state(new state_type(gen, dis));
83  return [state]() { return state->second(state->first); };
84 }
85 
86 template <typename value_type, bool introduce_error = false>
87 void check_generic_reduce_sized(std::size_t size, std::size_t subset_size) {
88  BOOST_TEST_MESSAGE("Testing with size = " << size);
89  boost::compute::device device = jb::opencl::device_selector();
90  BOOST_TEST_MESSAGE("Running on device = " << device.name());
91  using scalar_type = typename jb::extract_value_type<value_type>::precision;
92  if (std::is_same<double, scalar_type>::value) {
93  if (not device.supports_extension("cl_khr_fp64")) {
94  BOOST_TEST_MESSAGE(
95  "Test disabled, device (" << device.name()
96  << ") does not support cl_khr_fp64, i.e., "
97  "double precision floating point");
98  return;
99  }
100  }
101 
102  boost::compute::context context(device);
103  boost::compute::command_queue queue(context, device);
104 
105  unsigned int seed = std::random_device()();
106  BOOST_TEST_MESSAGE("SEED = " << seed);
107  auto generator = create_random_generator<scalar_type>(seed);
108 
109  std::vector<value_type> asrc;
110  jb::testing::create_random_timeseries(generator, size, asrc);
111 
112  boost::compute::vector<value_type> a(size, context);
113 
114  boost::compute::copy(asrc.begin(), asrc.begin() + size, a.begin(), queue);
115  std::vector<value_type> acpy(size);
116  boost::compute::copy(a.begin(), a.begin() + size, acpy.begin(), queue);
117  for (std::size_t i = 0; i != acpy.size(); ++i) {
118  JB_LOG(trace) << " " << i << " " << acpy[i] << " " << asrc[i];
119  }
120 
121  reduce_sum<value_type, introduce_error> reducer(size, queue);
122  auto done = reducer.execute(a.begin(), a.begin() + subset_size);
123  done.wait();
124 
125  value_type expected =
126  std::accumulate(asrc.begin(), asrc.begin() + subset_size, value_type(0));
127  value_type actual = *done.get();
128  BOOST_CHECK_MESSAGE(
129  jb::testing::check_close_enough(actual, expected, size),
130  "mismatched C++ vs. OpenCL results expected(C++)="
131  << expected << " actual(OpenCL)=" << actual
132  << " delta=" << (actual - expected));
133 }
134 
135 template <typename value_type, bool introduce_error = false>
136 void check_generic_reduce(std::size_t size) {
137  check_generic_reduce_sized<value_type, introduce_error>(size, size);
138 }
139 
140 } // anonymous namespace
141 
142 /**
143  * @test Make sure the jb::tde::generic_reduce() works as
144  * expected.for sizes around ~256, which is close to
145  * MAX_WORK_GROUP_SIZE
146  */
147 BOOST_AUTO_TEST_CASE(generic_reduce_int_2e6) {
148  int const N = 16;
149  for (int i = -N / 2; i != N / 2; ++i) {
150  std::size_t const size = (1 << 8) + i;
151  check_generic_reduce<int>(size);
152  }
153 }
154 
155 /**
156  * @test Make sure the jb::tde::generic_reduce() works as
157  * expected.for sizes around ~256 * 32, which is close to
158  * MAX_WORK_GROUP_SIZE * MAX_COMPUTE_UNITS
159  */
160 BOOST_AUTO_TEST_CASE(generic_reduce_int_2e13) {
161  int const N = 16;
162  for (int i = -N / 2; i != N / 2; ++i) {
163  std::size_t const size = (1 << 13) + i;
164  check_generic_reduce<int>(size);
165  }
166 }
167 
168 /**
169  * @test Make sure the jb::tde::generic_reduce() works as
170  * expected for 2^20 (1 million binary)
171  */
172 BOOST_AUTO_TEST_CASE(generic_reduce_int_2e20) {
173  std::size_t const size = (1 << 20);
174  check_generic_reduce<int>(size);
175 }
176 
177 /**
178  * @test Make sure the jb::tde::generic_reduce() works as
179  * expected for 1000000
180  */
181 BOOST_AUTO_TEST_CASE(generic_reduce_int_1000000) {
182  std::size_t const size = 1000 * 1000;
183  check_generic_reduce<int>(size);
184 }
185 
186 /**
187  * @test Make sure the jb::tde::generic_reduce() works as
188  * expected for a number that does not have a lot of powers of 2.
189  */
190 BOOST_AUTO_TEST_CASE(generic_reduce_int_PRIMES) {
191  std::size_t const size = 2 * 3 * 5 * 7 * 11 * 13 * 17 * 19;
192  check_generic_reduce<int>(size);
193 }
194 
195 /**
196  * @test Make sure the jb::tde::generic_reduce() works as
197  * expected for a number without a lot of powers of 2
198  */
199 BOOST_AUTO_TEST_CASE(generic_reduce_float_PRIMES) {
200  std::size_t const size = 2 * 3 * 5 * 7 * 11 * 13 * 17;
201  check_generic_reduce<float>(size);
202 }
203 
204 /**
205  * @test Make sure the jb::tde::generic_reduce() works as
206  * expected for a number without a lot of powers of 2
207  */
208 BOOST_AUTO_TEST_CASE(generic_reduce_complex_float_PRIMES) {
209  std::size_t const size = 2 * 3 * 5 * 7 * 11 * 13;
210  check_generic_reduce<std::complex<float>>(size);
211 }
212 
213 /**
214  * @test Make sure the jb::tde::generic_reduce() works as
215  * expected for a number without a lot of powers of 2
216  */
217 BOOST_AUTO_TEST_CASE(generic_reduce_complex_double_PRIMES) {
218  std::size_t const size = 2 * 3 * 5 * 7 * 11 * 13;
219  check_generic_reduce<std::complex<double>>(size);
220 }
221 
222 /**
223  * @test Make sure the jb::tde::generic_reduce() works as
224  * expected when used with a subset of the input.
225  */
226 BOOST_AUTO_TEST_CASE(generic_reduce_double_subset) {
227  std::size_t const size = 1000000;
228  std::size_t const subset_size = size / 2;
229  check_generic_reduce_sized<double>(size, subset_size);
230 }
231 
232 /**
233  * @test Improve code coverage, test case of broken code.
234  */
235 BOOST_AUTO_TEST_CASE(generic_reduce_float_broken) {
236  std::size_t const size = 1 << 18;
237  BOOST_CHECK_THROW((check_generic_reduce<float, true>(size)), std::exception);
238 }
void create_random_timeseries(generator &gen, int nsamples, timeseries &ts)
Create a simple timeseries where the values look like a random.
Implement a generic reducer for OpenCL.
boost::compute::device device_selector(config const &cfg)
Select an OpenCL device matching the current configuration.
BOOST_AUTO_TEST_CASE(generic_reduce_int_2e6)
#define JB_LOG(lvl)
Definition: log.hpp:70
The top-level namespace for the JayBeams library.
Definition: as_hhmmss.hpp:7
bool check_close_enough(value_t num_a, value_t num_b, int tol)
Given two numbers of the same integer type check if the difference is within a given tolerance...