Skip to content

Commit 621389b

Browse files
committed
Add kahan sum reduce helper class.
This adds a KahanSum class to help do Kahan sums. This also adds tests and generalizes the existing tests for reduce helper classes to accommodate slightly different interfaces.
1 parent 725dcc1 commit 621389b

4 files changed

Lines changed: 309 additions & 77 deletions

File tree

include/RAJA/util/reduce.hpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ struct LeftFoldReduce
9191
m_accumulated_value = m_op(std::move(m_accumulated_value), std::move(val));
9292
}
9393

94+
/*!
95+
\brief combine a value into the reducer
96+
*/
97+
RAJA_HOST_DEVICE RAJA_INLINE void operator+=(T val)
98+
{
99+
combine(std::move(val));
100+
}
101+
94102
private:
95103
BinaryOp m_op;
96104
T m_accumulated_value;
@@ -214,6 +222,14 @@ struct BinaryTreeReduce
214222
++m_count;
215223
}
216224

225+
/*!
226+
\brief combine a value into the reducer
227+
*/
228+
RAJA_HOST_DEVICE RAJA_INLINE void operator+=(T val)
229+
{
230+
combine(std::move(val));
231+
}
232+
217233
private:
218234
BinaryOp m_op;
219235

@@ -241,6 +257,80 @@ struct BinaryTreeReduce
241257
}
242258
};
243259

260+
/*!
261+
\brief Reduce class that does a reduction with a left fold.
262+
*/
263+
template<typename T>
264+
struct KahanSum
265+
{
266+
static_assert(std::is_floating_point_v<T>, "T must be a floating point type");
267+
268+
RAJA_HOST_DEVICE RAJA_INLINE constexpr explicit KahanSum(
269+
T init = T()) noexcept
270+
: m_accumulated_value(std::move(init))
271+
, m_accumulated_carry(T())
272+
{}
273+
274+
KahanSum(KahanSum const&) = delete;
275+
KahanSum& operator=(KahanSum const&) = delete;
276+
KahanSum(KahanSum&&) = delete;
277+
KahanSum& operator=(KahanSum&&) = delete;
278+
279+
~KahanSum() = default;
280+
281+
/*!
282+
\brief reset the combined value of the reducer to the identity
283+
*/
284+
RAJA_HOST_DEVICE RAJA_INLINE void clear() noexcept
285+
{
286+
m_accumulated_value = T();
287+
m_accumulated_carry = T();
288+
}
289+
290+
/*!
291+
\brief return the combined value and clear the reducer
292+
*/
293+
RAJA_HOST_DEVICE RAJA_INLINE T get_and_clear()
294+
{
295+
T accumulated_value = std::move(m_accumulated_value);
296+
297+
clear();
298+
299+
return accumulated_value;
300+
}
301+
302+
/*!
303+
\brief return the combined value
304+
*/
305+
RAJA_HOST_DEVICE RAJA_INLINE T get() { return m_accumulated_value; }
306+
307+
/*!
308+
\brief combine a value into the reducer
309+
*/
310+
RAJA_HOST_DEVICE RAJA_INLINE void combine(T val)
311+
{
312+
// volatile used to prevent compiler optimizations that assume
313+
// floating-point operations are associative
314+
T y = val - m_accumulated_carry;
315+
volatile T t = m_accumulated_value + y;
316+
volatile T z = t - m_accumulated_value;
317+
m_accumulated_carry = z - y;
318+
m_accumulated_value = t;
319+
}
320+
321+
/*!
322+
\brief combine a value into the reducer
323+
*/
324+
RAJA_HOST_DEVICE RAJA_INLINE void operator+=(T val)
325+
{
326+
combine(std::move(val));
327+
}
328+
329+
private:
330+
T m_accumulated_value;
331+
T m_accumulated_carry;
332+
};
333+
244334
template<typename T, typename BinaryOp>
245335
using HighAccuracyReduce =
246336
std::conditional_t<RAJA::operators::is_fp_associative<T>::value,
@@ -291,6 +381,25 @@ binary_tree_reduce(Iter begin, Iter end, T init, BinaryOp op)
291381
return reducer.get_and_clear();
292382
}
293383

384+
/*!
385+
\brief Combine into a single value using a kahan sum using O(N) operations
386+
and O(1) memory
387+
*/
388+
template<typename Iter, typename T>
389+
RAJA_HOST_DEVICE RAJA_INLINE T
390+
kahan_sum(Iter begin, Iter end, T init)
391+
{
392+
KahanSum<T> reducer(std::move(init));
393+
394+
for (; begin != end; ++begin)
395+
{
396+
397+
reducer.combine(*begin);
398+
}
399+
400+
return reducer.get_and_clear();
401+
}
402+
294403
/*!
295404
\brief reducer that uses a high accuracy implementation when round-off error
296405
is a concern, or a faster algorithm with it is not a concern
@@ -358,6 +467,25 @@ RAJA_HOST_DEVICE RAJA_INLINE
358467
std::move(op));
359468
}
360469

470+
/*!
471+
\brief Accumulate given range to a single value
472+
using a left fold algorithm in O(N) operations and O(1) extra memory
473+
see https://en.cppreference.com/w/cpp/algorithm/accumulate
474+
*/
475+
template<typename Container,
476+
typename T = detail::ContainerVal<Container>>
477+
RAJA_HOST_DEVICE RAJA_INLINE
478+
concepts::enable_if_t<T, type_traits::is_range<Container>,
479+
std::is_floating_point<T>>
480+
kahan_sum(Container&& c,
481+
T init = T())
482+
{
483+
using std::begin;
484+
using std::end;
485+
486+
return detail::kahan_sum(begin(c), end(c), std::move(init));
487+
}
488+
361489
/*!
362490
\brief Reduce given range to a single value
363491
using an algorithm with high accuracy when floating point round off is a

test/unit/algorithm/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ if(RAJA_ENABLE_HIP)
8989
endif()
9090

9191

92-
set( UTIL_REDUCES BinaryTree Accumulate )
92+
set( UTIL_REDUCES BinaryTree Accumulate Kahan )
9393

9494
RAJA_GENERATE_ALGORITHM_UTIL_TESTS( reduce Sequential Default "${UTIL_REDUCES}" )
9595

0 commit comments

Comments
 (0)