1 //---------------------------------------------------------------------------//
2 // Copyright (c) 2013 Kyle Lutz <kyle.r.lutz@gmail.com>
4 // Distributed under the Boost Software License, Version 1.0
5 // See accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt
8 // See http://boostorg.github.com/compute for more information.
9 //---------------------------------------------------------------------------//
11 #ifndef BOOST_COMPUTE_ALGORITHM_REDUCE_HPP
12 #define BOOST_COMPUTE_ALGORITHM_REDUCE_HPP
16 #include <boost/compute/system.hpp>
17 #include <boost/compute/functional.hpp>
18 #include <boost/compute/detail/meta_kernel.hpp>
19 #include <boost/compute/command_queue.hpp>
20 #include <boost/compute/container/array.hpp>
21 #include <boost/compute/container/vector.hpp>
22 #include <boost/compute/algorithm/copy_n.hpp>
23 #include <boost/compute/algorithm/detail/inplace_reduce.hpp>
24 #include <boost/compute/algorithm/detail/reduce_on_gpu.hpp>
25 #include <boost/compute/algorithm/detail/reduce_on_cpu.hpp>
26 #include <boost/compute/detail/iterator_range_size.hpp>
27 #include <boost/compute/memory/local_buffer.hpp>
28 #include <boost/compute/type_traits/result_of.hpp>
34 template<class InputIterator, class OutputIterator, class BinaryFunction>
35 size_t reduce(InputIterator first,
37 OutputIterator result,
39 BinaryFunction function,
43 std::iterator_traits<InputIterator>::value_type
46 boost::compute::result_of<BinaryFunction(input_type, input_type)>::type
49 const context &context = queue.get_context();
50 size_t block_count = count / 2 / block_size;
51 size_t total_block_count =
52 static_cast<size_t>(std::ceil(float(count) / 2.f / float(block_size)));
55 meta_kernel k("block_reduce");
56 size_t output_arg = k.add_arg<result_type *>(memory_object::global_memory, "output");
57 size_t block_arg = k.add_arg<input_type *>(memory_object::local_memory, "block");
60 "const uint gid = get_global_id(0);\n" <<
61 "const uint lid = get_local_id(0);\n" <<
63 // copy values to local memory
65 function(first[k.make_var<uint_>("gid*2+0")],
66 first[k.make_var<uint_>("gid*2+1")]) << ";\n" <<
69 "for(uint i = 1; i < " << uint_(block_size) << "; i <<= 1){\n" <<
70 " barrier(CLK_LOCAL_MEM_FENCE);\n" <<
71 " uint mask = (i << 1) - 1;\n" <<
72 " if((lid & mask) == 0){\n" <<
74 function(k.expr<input_type>("block[lid]"),
75 k.expr<input_type>("block[lid+i]")) << ";\n" <<
79 // write block result to global output
81 " output[get_group_id(0)] = block[0];\n";
83 kernel kernel = k.compile(context);
84 kernel.set_arg(output_arg, result.get_buffer());
85 kernel.set_arg(block_arg, local_buffer<input_type>(block_size));
87 queue.enqueue_1d_range_kernel(kernel,
89 block_count * block_size,
93 // serially reduce any leftovers
94 if(block_count * block_size * 2 < count){
95 size_t last_block_start = block_count * block_size * 2;
97 meta_kernel k("extra_serial_reduce");
98 size_t count_arg = k.add_arg<uint_>("count");
99 size_t offset_arg = k.add_arg<uint_>("offset");
100 size_t output_arg = k.add_arg<result_type *>(memory_object::global_memory, "output");
101 size_t output_offset_arg = k.add_arg<uint_>("output_offset");
104 k.decl<result_type>("result") << " = \n" <<
105 first[k.expr<uint_>("offset")] << ";\n" <<
106 "for(uint i = offset + 1; i < count; i++)\n" <<
108 function(k.var<result_type>("result"),
109 first[k.var<uint_>("i")]) << ";\n" <<
110 "output[output_offset] = result;\n";
112 kernel kernel = k.compile(context);
113 kernel.set_arg(count_arg, static_cast<uint_>(count));
114 kernel.set_arg(offset_arg, static_cast<uint_>(last_block_start));
115 kernel.set_arg(output_arg, result.get_buffer());
116 kernel.set_arg(output_offset_arg, static_cast<uint_>(block_count));
118 queue.enqueue_task(kernel);
121 return total_block_count;
124 template<class InputIterator, class BinaryFunction>
126 typename boost::compute::result_of<
128 typename std::iterator_traits<InputIterator>::value_type,
129 typename std::iterator_traits<InputIterator>::value_type
133 block_reduce(InputIterator first,
136 BinaryFunction function,
137 command_queue &queue)
140 std::iterator_traits<InputIterator>::value_type
143 boost::compute::result_of<BinaryFunction(input_type, input_type)>::type
146 const context &context = queue.get_context();
147 size_t total_block_count =
148 static_cast<size_t>(std::ceil(float(count) / 2.f / float(block_size)));
149 vector<result_type> result_vector(total_block_count, context);
151 reduce(first, count, result_vector.begin(), block_size, function, queue);
153 return result_vector;
156 template<class InputIterator, class OutputIterator, class BinaryFunction>
157 inline void generic_reduce(InputIterator first,
159 OutputIterator result,
160 BinaryFunction function,
161 command_queue &queue)
164 std::iterator_traits<InputIterator>::value_type
167 boost::compute::result_of<BinaryFunction(input_type, input_type)>::type
170 const device &device = queue.get_device();
171 const context &context = queue.get_context();
173 size_t count = detail::iterator_range_size(first, last);
175 if(device.type() & device::cpu){
176 array<result_type, 1> value(context);
177 detail::reduce_on_cpu(first, last, value.begin(), function, queue);
178 boost::compute::copy_n(value.begin(), 1, result, queue);
181 size_t block_size = 256;
184 vector<result_type> results = detail::block_reduce(first,
190 if(results.size() > 1){
191 detail::inplace_reduce(results.begin(),
197 boost::compute::copy_n(results.begin(), 1, result, queue);
201 template<class InputIterator, class OutputIterator, class T>
202 inline void dispatch_reduce(InputIterator first,
204 OutputIterator result,
205 const plus<T> &function,
206 command_queue &queue)
208 const context &context = queue.get_context();
209 const device &device = queue.get_device();
211 // reduce to temporary buffer on device
212 array<T, 1> value(context);
213 if(device.type() & device::cpu){
214 detail::reduce_on_cpu(first, last, value.begin(), function, queue);
217 reduce_on_gpu(first, last, value.begin(), function, queue);
220 // copy to result iterator
221 copy_n(value.begin(), 1, result, queue);
224 template<class InputIterator, class OutputIterator, class BinaryFunction>
225 inline void dispatch_reduce(InputIterator first,
227 OutputIterator result,
228 BinaryFunction function,
229 command_queue &queue)
231 generic_reduce(first, last, result, function, queue);
234 } // end detail namespace
236 /// Returns the result of applying \p function to the elements in the
237 /// range [\p first, \p last).
239 /// If no function is specified, \c plus will be used.
241 /// \param first first element in the input range
242 /// \param last last element in the input range
243 /// \param result iterator pointing to the output
244 /// \param function binary reduction function
245 /// \param queue command queue to perform the operation
247 /// The \c reduce() algorithm assumes that the binary reduction function is
248 /// associative. When used with non-associative functions the result may
249 /// be non-deterministic and vary in precision. Notably this affects the
250 /// \c plus<float>() function as floating-point addition is not associative
251 /// and may produce slightly different results than a serial algorithm.
253 /// This algorithm supports both host and device iterators for the
254 /// result argument. This allows for values to be reduced and copied
255 /// to the host all with a single function call.
257 /// For example, to calculate the sum of the values in a device vector and
258 /// copy the result to a value on the host:
260 /// \snippet test/test_reduce.cpp sum_int
262 /// Note that while the the \c reduce() algorithm is conceptually identical to
263 /// the \c accumulate() algorithm, its implementation is substantially more
264 /// efficient on parallel hardware. For more information, see the documentation
265 /// on the \c accumulate() algorithm.
267 /// \see accumulate()
268 template<class InputIterator, class OutputIterator, class BinaryFunction>
269 inline void reduce(InputIterator first,
271 OutputIterator result,
272 BinaryFunction function,
273 command_queue &queue = system::default_queue())
279 detail::dispatch_reduce(first, last, result, function, queue);
283 template<class InputIterator, class OutputIterator>
284 inline void reduce(InputIterator first,
286 OutputIterator result,
287 command_queue &queue = system::default_queue())
289 typedef typename std::iterator_traits<InputIterator>::value_type T;
295 detail::dispatch_reduce(first, last, result, plus<T>(), queue);
298 } // end compute namespace
299 } // end boost namespace
301 #endif // BOOST_COMPUTE_ALGORITHM_REDUCE_HPP