Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions Modules/Core/Common/include/itkGreedyReduceAlgorithm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#ifndef itkGreedyReduceAlgorithm_h
#define itkGreedyReduceAlgorithm_h

#include "itkReduceAlgorithm.h"

namespace itk
{
/** \class GreedyReduceAlgorithm
* \brief Thread-safe parallel reduce using a greedy swap-and-merge strategy.
*
* Implements the parallel reduction pattern used in several ITK filters
* (e.g. LabelStatisticsImageFilter, LabelOverlapMeasuresImageFilter,
* ImageToHistogramFilter). Each thread calls Merge() with its local
* partial result; the implementation combines them without holding the
* global mutex while performing the (potentially expensive) merge step.
*
* \par Non-deterministic merge order
* The order in which partial results are combined depends on thread
* scheduling and is not deterministic across runs. Operations whose
* result is independent of evaluation order (e.g. integer counts,
* minimum, maximum) produce bit-identical outputs. Floating-point
* reductions (e.g. running sums, means) may produce results that differ
* in the last few ULPs between runs due to non-associativity of
* floating-point arithmetic.
*
* Algorithm (per Merge() call):
* -# Acquire the mutex.
* -# If no result is accumulated yet, store the local result and return.
* -# Otherwise, atomically take ownership of the current accumulated result,
* clearing the shared state to allow other threads to proceed immediately.
* -# Release the mutex.
* -# Merge the taken result into the local result (off the critical path).
* -# Repeat from step 1 until the local result is successfully deposited.
*
* A merge function must be supplied via SetMergeFunction() before calling
* Merge(). The function signature is:
* \code
* void mergeFunction(T & target, T & source);
* \endcode
* It must merge \c source into \c target.
*
* \tparam T The type of the object being reduced.
*
* \ingroup ITKCommon
*/
template <typename T>
class ITK_TEMPLATE_EXPORT GreedyReduceAlgorithm : public ReduceAlgorithm<T>
{
public:
ITK_DISALLOW_COPY_AND_MOVE(GreedyReduceAlgorithm);

/** Standard class type aliases. */
using Self = GreedyReduceAlgorithm;
using Superclass = ReduceAlgorithm<T>;
using Pointer = SmartPointer<Self>;
using ConstPointer = SmartPointer<const Self>;

/** \see LightObject::GetNameOfClass() */
itkOverrideGetNameOfClassMacro(GreedyReduceAlgorithm);

/** Method for creation through the object factory. */
itkNewMacro(Self);

/** The value type being reduced. */
using typename Superclass::ValueType;

/** Bring the chunk-ID overload into scope; Greedy ignores the ID and
* forwards to Merge(T&&) (see ReduceAlgorithm::Merge(SizeValueType, T&&)). */
using Superclass::Merge;

/** Merge a per-thread \p localResult into the accumulated output.
* This method is thread-safe; multiple threads may call it concurrently. */
void
Merge(T && localResult) override;

/** Return the accumulated result.
* Should only be called after all concurrent Merge() calls have finished. */
const T &
GetResult() const override;

/** Reset accumulated state so this object can be reused. */
void
Clear() override;

protected:
GreedyReduceAlgorithm() = default;
~GreedyReduceAlgorithm() override = default;

void
PrintSelf(std::ostream & os, Indent indent) const override;

private:
/** Accumulated result. Default-constructed value serves as initial state. */
T m_Result{};

/** True once the first Merge() has deposited a value. */
bool m_HasResult{ false };
};

} // namespace itk

#ifndef ITK_MANUAL_INSTANTIATION
# include "itkGreedyReduceAlgorithm.hxx"
#endif

#endif // itkGreedyReduceAlgorithm_h
88 changes: 88 additions & 0 deletions Modules/Core/Common/include/itkGreedyReduceAlgorithm.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#ifndef itkGreedyReduceAlgorithm_hxx
#define itkGreedyReduceAlgorithm_hxx

#include "itkGreedyReduceAlgorithm.h"

#include <utility>

namespace itk
{

template <typename T>
void
GreedyReduceAlgorithm<T>::Merge(T && localResult)
{
// Greedy swap-and-merge strategy:
// Acquire the mutex only long enough to swap ownership of the shared
// accumulator. The actual merge work is done outside the critical section
// so other threads can proceed concurrently.
while (true)
{
T tomerge{};
{
const std::lock_guard<std::mutex> lockGuard(this->m_Mutex);

if (!m_HasResult)
{
// No accumulated result yet: store the local result and return.
m_Result = std::move(localResult);
m_HasResult = true;
return;
}

// Take ownership of the current accumulated result so other threads
// can deposit their own results immediately after we release the lock.
std::swap(m_Result, tomerge);
m_HasResult = false;
} // release lock

// Merge the taken result into localResult outside the critical section.
this->m_MergeFunction(localResult, tomerge);
}
}

template <typename T>
const T &
GreedyReduceAlgorithm<T>::GetResult() const
{
return m_Result;
}

template <typename T>
void
GreedyReduceAlgorithm<T>::Clear()
{
const std::lock_guard<std::mutex> lockGuard(this->m_Mutex);
m_Result = T{};
m_HasResult = false;
Superclass::Clear();
}

template <typename T>
void
GreedyReduceAlgorithm<T>::PrintSelf(std::ostream & os, Indent indent) const
{
Superclass::PrintSelf(os, indent);
os << indent << "HasResult: " << (m_HasResult ? "true" : "false") << std::endl;
}

} // namespace itk

#endif // itkGreedyReduceAlgorithm_hxx
120 changes: 120 additions & 0 deletions Modules/Core/Common/include/itkLinearReduceAlgorithm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#ifndef itkLinearReduceAlgorithm_h
#define itkLinearReduceAlgorithm_h

#include "itkReduceAlgorithm.h"

#include <optional>
#include <vector>

namespace itk
{
/** \class LinearReduceAlgorithm
* \brief Deterministic reduce using an ordered linear merge strategy.
*
* Each work unit deposits its partial result into a chunk-indexed array.
* The actual merge is deferred until GetResult() is called: it walks the
* array once, in ascending chunk-ID order, and combines the values under a
* single lock. Because the merge order is fixed by chunk ID rather than by
* thread scheduling, the final result is bit-identical across runs.
*
* \par Usage
* Call SetNumberOfWorkUnits() \em before any Merge() calls; this allocates
* the internal array. Each work unit then calls Merge(chunkId, value)
* exactly once with its 0-based chunk identifier, in any order. Call
* GetResult() only after every chunk has called Merge().
*
* \tparam T The type of the object being reduced.
*
* \ingroup ITKCommon
*/
template <typename T>
class ITK_TEMPLATE_EXPORT LinearReduceAlgorithm : public ReduceAlgorithm<T>
{
public:
ITK_DISALLOW_COPY_AND_MOVE(LinearReduceAlgorithm);

/** Standard class type aliases. */
using Self = LinearReduceAlgorithm;
using Superclass = ReduceAlgorithm<T>;
using Pointer = SmartPointer<Self>;
using ConstPointer = SmartPointer<const Self>;

/** \see LightObject::GetNameOfClass() */
itkOverrideGetNameOfClassMacro(LinearReduceAlgorithm);

/** Method for creation through the object factory. */
itkNewMacro(Self);

/** The value type being reduced. */
using typename Superclass::ValueType;

/** Set the total number of work units. Allocates the internal array.
* Must be called before the first Merge(). */
void
SetNumberOfWorkUnits(SizeValueType numberOfWorkUnits) override;

/** Not supported — use Merge(SizeValueType chunkId, T &&) instead.
* Always throws itk::ExceptionObject. */
void
Merge(T && localResult) override;

/** Deposit \p localResult for chunk \p chunkId. Thread-safe; merely
* stores the value, the actual merge happens lazily in GetResult(). */
void
Merge(SizeValueType chunkId, T && localResult) override;

/** Merge every deposited chunk, in ascending chunk-ID order, and return
* the result. The merge is performed once, under a lock, and cached;
* later calls return the cached result. Behaviour is defined only after
* every chunk has called Merge(). */
const T &
GetResult() const override;

/** Reset the array so this object can be reused with the same N.
* Does not change the merge function or work-unit count. */
void
Clear() override;

protected:
LinearReduceAlgorithm() = default;
~LinearReduceAlgorithm() override = default;

void
PrintSelf(std::ostream & os, Indent indent) const override;

private:
/** Chunk-indexed array of partial results awaiting the merge in
* GetResult(). Mutable because GetResult() consumes it lazily. */
mutable std::vector<std::optional<T>> m_Values{};

/** Cached result of merging m_Values, computed lazily by GetResult(). */
mutable T m_Result{};

/** True once GetResult() has performed the merge. */
mutable bool m_Merged{ false };
};

} // namespace itk

#ifndef ITK_MANUAL_INSTANTIATION
# include "itkLinearReduceAlgorithm.hxx"
#endif

#endif // itkLinearReduceAlgorithm_h
Loading
Loading