From 38b0211ae897cc9b36e9b200b7ddba9a28fae608 Mon Sep 17 00:00:00 2001 From: retornaz-thomas Date: Wed, 28 Oct 2026 11:39:30 +0100 Subject: [PATCH 1/4] [feature] implement max_element cf issue #20 --- include/xsimd_algorithm/stl/max_element.hpp | 121 ++++++++++++++++++++ test/test_max_element.cpp | 110 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 include/xsimd_algorithm/stl/max_element.hpp create mode 100644 test/test_max_element.cpp diff --git a/include/xsimd_algorithm/stl/max_element.hpp b/include/xsimd_algorithm/stl/max_element.hpp new file mode 100644 index 0000000..d673920 --- /dev/null +++ b/include/xsimd_algorithm/stl/max_element.hpp @@ -0,0 +1,121 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and * + * Martin Renou * + * Copyright (c) QuantStack * + * Copyright (c) Serge Guelton * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XSIMD_ALGORITHMS_MAX_ELEMENT_HPP +#define XSIMD_ALGORITHMS_MAX_ELEMENT_HPP + +#include "xsimd/xsimd.hpp" + +#include + +namespace xsimd +{ + namespace detail + { + template + ForwardIterator sequential_max_element(ForwardIterator first, ForwardIterator last, Compare&& comp) noexcept + { + if (first == last) + { + return last; + } + + ForwardIterator max_it = first; + + for (++first; first != last; ++first) + { + if (comp(*max_it, *first)) + { + max_it = first; + } + } + + return max_it; + } + } + + template + ContiguousIterator max_element(ContiguousIterator first, ContiguousIterator last, Compare&& comp) noexcept + { + using value_type = typename std::decay::type; + using batch_type = batch; + + const std::size_t size = static_cast(std::distance(first, last)); + constexpr std::size_t simd_size = batch_type::size; + + // Quick return for small ranges + if (size < simd_size) + { + return detail::sequential_max_element(first, last, std::forward(comp)); + } + + // prologue: process elements until the first aligned one + // compute alignment offsets + const auto* const ptr_begin = &(*first); + const std::size_t align_begin = xsimd::get_alignment_offset(ptr_begin, size, simd_size); + const std::size_t align_end = align_begin + ((size - align_begin) & ~(simd_size - 1)); + + const auto align_begin_it = std::next(first, align_begin); + const auto align_end_it = std::next(first, align_end); + + auto max_it = detail::sequential_max_element(first, align_begin_it, std::forward(comp)); + auto current_max = *max_it; + auto current_max_index = std::distance(first, max_it); + + // Generate a batch of the current max value + batch_type current_max_batch = batch_type(current_max); + + // main loop: process aligned elements + for (auto current = align_begin_it; current != align_end_it; std::advance(current, simd_size)) + { + batch_type batch_val = batch_type::load_aligned(&(*current)); + + // Compare the current batch with the current max batch + auto mask = comp(current_max_batch, batch_val); + + // Update ? + if (xsimd::any(mask)) + { + // Update the current max batch + current_max_batch = xsimd::select(mask, batch_val, current_max_batch); + // extract the max value + current_max = xsimd::reduce_max(current_max_batch); + //retrieve position of the max value in the current batch + auto max_pos = std::find(current, current + simd_size, current_max); + if (max_pos != current + simd_size) + { + current_max_index = std::distance(first, max_pos); + } + } + } + + max_it = std::next(first, current_max_index); + + for (auto current = align_end_it; current != last; ++current) + { + if (comp(current_max, *current)) + { + current_max = *current; + max_it = current; + } + } + return max_it; + } + + template + ContiguousIterator max_element(ContiguousIterator first, ContiguousIterator last) noexcept + { + return xsimd::max_element(first, last, [](const auto& lhs, const auto& rhs) { return lhs < rhs; }); + } + +} // namespace xsimd + +#endif // XSIMD_ALGORITHMS_MAX_ELEMENT_HPP \ No newline at end of file diff --git a/test/test_max_element.cpp b/test/test_max_element.cpp new file mode 100644 index 0000000..5ad7bec --- /dev/null +++ b/test/test_max_element.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and * + * Martin Renou * + * Copyright (c) QuantStack * + * Copyright (c) Serge Guelton * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#include "xsimd_algorithm/stl/max_element.hpp" + +#ifndef XSIMD_NO_SUPPORTED_ARCHITECTURE + +#include "doctest/doctest.h" + +#include +#include + +#if XSIMD_WITH_NEON && !XSIMD_WITH_NEON64 +#define MAX_ELEMENT_TYPES int, float +#else +#define MAX_ELEMENT_TYPES int, long, float, double +#endif + +template +struct max_element_test +{ + using vector = std::vector; + static constexpr std::size_t simd_size = xsimd::batch::size; + + static vector fill_data(size_t size, Type max, size_t position) + { + vector result(size,Type(0)); + + if (position < size) + { + result[position] = max; + } + + return result; + } + + void test_max_element_aligned( size_t size, size_t position) const + { + const auto maximum = size -1; + const vector data = fill_data(size, maximum, position); + const auto result = xsimd::max_element(data.begin(), data.end()); + const auto expected = std::max_element(data.begin(), data.end()); + CHECK(result == expected); + } + + void test_max_element_misaligned(size_t size, size_t position) const + { + const auto maximum = size -1; + const size_t missalignment = 1; + const vector data = fill_data(size + missalignment * 2, maximum, position); + auto first = data.begin() + missalignment; + auto last = first + size; + + const auto result = xsimd::max_element(first, last); + const auto expected = std::max_element(first, last); + + CHECK(result == expected); + } +}; + +TEST_CASE_TEMPLATE("max_element test", T, MAX_ELEMENT_TYPES) +{ + using Test = max_element_test; + + const std::array test_sizes { + size_t { 0 }, + size_t { 1 }, + Test::simd_size - 1, + Test::simd_size, + Test ::simd_size + 1, + Test ::simd_size *4, + }; + + const std::array positions { + size_t { 0 }, + size_t { 1 }, + Test::simd_size / 4, + Test::simd_size / 2, + Test::simd_size - 1, + }; + + Test test; + + size_t sub_case_id = 0; + for (const auto size : test_sizes) + { + for (const auto position : positions) + { + const std::string aligned_subcase_name = "Aligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size) + ", position: " + std::to_string(position); + SUBCASE(aligned_subcase_name.c_str()) + { + test.test_max_element_aligned(size, position); + } + const std::string misaligned_subcase_name = "Misaligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size) + ", position: " + std::to_string(position); + SUBCASE(misaligned_subcase_name.c_str()) + { + test.test_max_element_misaligned(size, position); + } + } + } +} +#endif \ No newline at end of file From 67edb6b155a37c3e04bacb8034574c8d3a5fcf09 Mon Sep 17 00:00:00 2001 From: retornaz-thomas Date: Wed, 28 Oct 2026 11:39:50 +0100 Subject: [PATCH 2/4] [feature] implement min_element cf issue #20 --- include/xsimd_algorithm/stl/min_element.hpp | 121 ++++++++++++++++++++ test/test_min_element.cpp | 112 ++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 include/xsimd_algorithm/stl/min_element.hpp create mode 100644 test/test_min_element.cpp diff --git a/include/xsimd_algorithm/stl/min_element.hpp b/include/xsimd_algorithm/stl/min_element.hpp new file mode 100644 index 0000000..eb8db36 --- /dev/null +++ b/include/xsimd_algorithm/stl/min_element.hpp @@ -0,0 +1,121 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and * + * Martin Renou * + * Copyright (c) QuantStack * + * Copyright (c) Serge Guelton * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#ifndef XSIMD_ALGORITHMS_MIN_ELEMENT_HPP +#define XSIMD_ALGORITHMS_MIN_ELEMENT_HPP + +#include "xsimd/xsimd.hpp" + +#include + +namespace xsimd +{ + namespace detail + { + template + ForwardIterator sequential_min_element(ForwardIterator first, ForwardIterator last, Compare&& comp) noexcept + { + if (first == last) + { + return last; + } + + ForwardIterator min_it = first; + + for (++first; first != last; ++first) + { + if (comp(*min_it, *first)) + { + min_it = first; + } + } + + return min_it; + } + } + + template + ContiguousIterator min_element(ContiguousIterator first, ContiguousIterator last, Compare&& comp) noexcept + { + using value_type = typename std::decay::type; + using batch_type = batch; + + const std::size_t size = static_cast(std::distance(first, last)); + constexpr std::size_t simd_size = batch_type::size; + + // Quick return for small ranges + if (size < simd_size) + { + return detail::sequential_min_element(first, last, std::forward(comp)); + } + + // prologue: process elements until the first aligned one + // compute alignment offsets + const auto* const ptr_begin = &(*first); + const std::size_t align_begin = xsimd::get_alignment_offset(ptr_begin, size, simd_size); + const std::size_t align_end = align_begin + ((size - align_begin) & ~(simd_size - 1)); + + const auto align_begin_it = std::next(first, align_begin); + const auto align_end_it = std::next(first, align_end); + + auto min_it = detail::sequential_min_element(first, align_begin_it, std::forward(comp)); + auto current_min = *min_it; + auto current_min_index = std::distance(first, min_it); + + // Generate a batch of the current min value + batch_type current_min_batch = batch_type(current_min); + + // main loop: process aligned elements + for (auto current = align_begin_it; current != align_end_it; std::advance(current, simd_size)) + { + batch_type batch_val = batch_type::load_aligned(&(*current)); + + // Compare the current batch with the current min batch + auto mask = comp(current_min_batch, batch_val); + + // Update ? + if (xsimd::any(mask)) + { + // Update the current min batch + current_min_batch = xsimd::select(mask, batch_val, current_min_batch); + // extract the min value + current_min = xsimd::reduce_min(current_min_batch); + //retrieve position of the min value in the current batch + auto min_pos = std::find(current, current + simd_size, current_min); + if (min_pos != current + simd_size) + { + current_min_index = std::distance(first, min_pos); + } + } + } + + min_it = std::next(first, current_min_index); + + for (auto current = align_end_it; current != last; ++current) + { + if (comp(current_min, *current)) + { + current_min = *current; + min_it = current; + } + } + return min_it; + } + + template + ContiguousIterator min_element(ContiguousIterator first, ContiguousIterator last) noexcept + { + return xsimd::min_element(first, last, [](const auto& lhs, const auto& rhs) { return lhs > rhs; }); + } + +} // namespace xsimd + +#endif // XSIMD_ALGORITHMS_MIN_ELEMENT_HPP \ No newline at end of file diff --git a/test/test_min_element.cpp b/test/test_min_element.cpp new file mode 100644 index 0000000..a40b1e3 --- /dev/null +++ b/test/test_min_element.cpp @@ -0,0 +1,112 @@ +/*************************************************************************** + * Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and * + * Martin Renou * + * Copyright (c) QuantStack * + * Copyright (c) Serge Guelton * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ****************************************************************************/ + +#include "xsimd_algorithm/stl/min_element.hpp" + +#ifndef XSIMD_NO_SUPPORTED_ARCHITECTURE + +#include "doctest/doctest.h" + +#include +#include + +#if XSIMD_WITH_NEON && !XSIMD_WITH_NEON64 +#define MIN_ELEMENT_TYPES int, float +#else +#define MIN_ELEMENT_TYPES int, long, float, double +#endif + +template +struct min_element_test +{ + using vector = std::vector; + static constexpr std::size_t simd_size = xsimd::batch::size; + + static vector fill_data(size_t size, Type min, size_t position) + { + vector result(size,Type(0)); + + if (position < size) + { + result[position] = min; + } + + return result; + } + + void test_min_element_aligned( size_t size, size_t position) const + { + const auto minimum = Type(-42); + const vector data = fill_data(size, minimum, position); + + const auto result = xsimd::min_element(data.begin(), data.end()); + const auto expected = std::min_element(data.begin(), data.end()); + + CHECK(result == expected); + } + + void test_min_element_misaligned(size_t size, size_t position) const + { + const auto minimum = Type(-42); + const size_t missalignment = 1; + const vector data = fill_data(size + missalignment * 2, minimum, position); + auto first = data.begin() + missalignment; + auto last = first + size; + + const auto result = xsimd::min_element(first, last); + const auto expected = std::min_element(first, last); + + CHECK(result == expected); + } +}; + +TEST_CASE_TEMPLATE("min_element test", T, MIN_ELEMENT_TYPES) +{ + using Test = min_element_test; + + const std::array test_sizes { + size_t { 0 }, + size_t { 1 }, + Test::simd_size - 1, + Test::simd_size, + Test ::simd_size + 1, + Test ::simd_size *4, + }; + + const std::array positions { + size_t { 0 }, + size_t { 1 }, + Test::simd_size / 4, + Test::simd_size / 2, + Test::simd_size - 1, + }; + + Test test; + + size_t sub_case_id = 0; + for (const auto size : test_sizes) + { + for (const auto position : positions) + { + const std::string aligned_subcase_name = "Aligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size) + ", position: " + std::to_string(position); + SUBCASE(aligned_subcase_name.c_str()) + { + test.test_min_element_aligned(size, position); + } + const std::string misaligned_subcase_name = "Misaligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size) + ", position: " + std::to_string(position); + SUBCASE(misaligned_subcase_name.c_str()) + { + test.test_min_element_misaligned(size, position); + } + } + } +} +#endif \ No newline at end of file From ec242086484533d93ef42d3d4814d08af699a26d Mon Sep 17 00:00:00 2001 From: retornaz-thomas Date: Wed, 28 Oct 2026 11:40:47 +0100 Subject: [PATCH 3/4] [feature] add benchmark for min/max_element cf issue #20 --- benchmark/bench_min_max.cpp | 114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 benchmark/bench_min_max.cpp diff --git a/benchmark/bench_min_max.cpp b/benchmark/bench_min_max.cpp new file mode 100644 index 0000000..88fa32d --- /dev/null +++ b/benchmark/bench_min_max.cpp @@ -0,0 +1,114 @@ +#include +#include +#include + +#include + +#include "xsimd_algorithm/stl/max_element.hpp" +#include "xsimd_algorithm/stl/min_element.hpp" + +#define BENCH_ARGS ->Arg(kSmall)->Arg(kMedium)->Arg(kLarge) + +namespace +{ + template + typename std::iterator_traits::value_type dummy_std_max_value(ForwardIterator first, ForwardIterator last) noexcept + { + auto candidate = std::numeric_limits::value_type>::lowest(); + for (; first != last; ++first) + { + if (candidate < *first) + { + candidate = *first; + } + } + return candidate; + } + + template + std::vector make_input(std::size_t n, T constant) + { + std::vector v(n); + std::fill(v.begin(), v.end(), constant); + return v; + } + + template + void BM_std_max_element(benchmark::State& state) + { + const auto constant = T { 5 }; + auto src = make_input(static_cast(state.range(0)), constant); + src[state.range(0)-1] = T { 10 }; // Ensure there is a maximum value at the end + + for (auto _ : state) + { + auto result = std::max_element(src.begin(), src.end()); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + + template + void BM_xsimd_max_element(benchmark::State& state) + { + const auto constant = T { 5 }; + auto src = make_input(static_cast(state.range(0)), constant); + src[state.range(0)-1] = T { 10 }; // Ensure there is a maximum value at the end + for (auto _ : state) + { + auto result = xsimd::max_element(src.begin(), src.end()); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + + template + void BM_std_min_element(benchmark::State& state) + { + const auto constant = T { 5 }; + auto src = make_input(static_cast(state.range(0)), constant); + src[state.range(0)-1] = T { 10 }; // Ensure there is a maximum value at the end + + for (auto _ : state) + { + auto result = std::min_element(src.begin(), src.end()); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + + template + void BM_xsimd_min_element(benchmark::State& state) + { + const auto constant = T { 5 }; + auto src = make_input(static_cast(state.range(0)), constant); + src[state.range(0)-1] = T { 10 }; // Ensure there is a maximum value at the end + for (auto _ : state) + { + auto result = xsimd::min_element(src.begin(), src.end()); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + + static constexpr long long kSmall = 1LL << 12; + static constexpr long long kMedium = 1LL << 18; + static constexpr long long kLarge = 1LL << 22; + + // max_element + BENCHMARK_TEMPLATE(BM_std_max_element, float) + BENCH_ARGS; + BENCHMARK_TEMPLATE(BM_xsimd_max_element, float) + BENCH_ARGS; + + // min_element + BENCHMARK_TEMPLATE(BM_std_min_element, float) + BENCH_ARGS; + BENCHMARK_TEMPLATE(BM_xsimd_min_element, float) + BENCH_ARGS; + +} \ No newline at end of file From 89c11c930f6703a47baeabd5b2b4644cb65ff800 Mon Sep 17 00:00:00 2001 From: retornaz-thomas Date: Wed, 28 Oct 2026 11:41:19 +0100 Subject: [PATCH 4/4] [feature] populate main header and cmake lists --- benchmark/CMakeLists.txt | 1 + include/xsimd_algorithm/algorithms.hpp | 2 ++ test/CMakeLists.txt | 2 ++ 3 files changed, 5 insertions(+) diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 3bf0b19..b9b24cb 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -21,6 +21,7 @@ set(XSIMD_ALGORITHM_BENCHMARKS bench_math.cpp bench_map.cpp bench_of.cpp + bench_min_max.cpp ) add_executable(benchmark_xsimd_algorithm ${XSIMD_ALGORITHM_BENCHMARKS}) diff --git a/include/xsimd_algorithm/algorithms.hpp b/include/xsimd_algorithm/algorithms.hpp index 21d03f2..394d94d 100644 --- a/include/xsimd_algorithm/algorithms.hpp +++ b/include/xsimd_algorithm/algorithms.hpp @@ -15,6 +15,8 @@ #include "xsimd_algorithm/stl/all_of.hpp" #include "xsimd_algorithm/stl/any_of.hpp" #include "xsimd_algorithm/stl/arange.hpp" +#include "xsimd_algorithm/stl/max_element.hpp" +#include "xsimd_algorithm/stl/min_element.hpp" //#include "xsimd_algorithm/stl/none_of.hpp" #include "xsimd_algorithm/stl/reduce.hpp" #include "xsimd_algorithm/stl/transform.hpp" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 86e6b9b..8c60b89 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -56,6 +56,8 @@ set(XSIMD_ALGORITHM_TESTS test_math.cpp test_reduce.cpp test_transform.cpp + test_max_element.cpp + test_min_element.cpp ) add_executable(test_xsimd_algorithm ${XSIMD_ALGORITHM_TESTS})