diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 3bf0b19..3784541 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_find.cpp ) add_executable(benchmark_xsimd_algorithm ${XSIMD_ALGORITHM_BENCHMARKS}) diff --git a/benchmark/bench_find.cpp b/benchmark/bench_find.cpp new file mode 100644 index 0000000..7187143 --- /dev/null +++ b/benchmark/bench_find.cpp @@ -0,0 +1,63 @@ +#include +#include +#include + +#include + +#include "xsimd_algorithm/stl/find.hpp" + +#define BENCH_ARGS ->Arg(kSmall)->Arg(kMedium)->Arg(kLarge) + +namespace +{ + template + std::vector make_input(size_t size, size_t position, T constant) + { + std::vector result(size); + std::iota(result.begin(), result.end(), T(1)); + if (position < size) + { + result[position] = constant; + } + return result; + } + + template + void BM_std_find(benchmark::State& state) + { + const auto constant = T { -42 }; + auto src = make_input(static_cast(state.range(0)), static_cast(state.range(0)) - 1, constant); + for (auto _ : state) + { + auto result = std::find(src.begin(), src.end(), constant); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + + template + void BM_xsimd_find(benchmark::State& state) + { + const auto constant = T { -42 }; + auto src = make_input(static_cast(state.range(0)), static_cast(state.range(0)) - 1, constant); + for (auto _ : state) + { + auto result = xsimd::find(src.begin(), src.end(), constant); + 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; + + // find + BENCHMARK_TEMPLATE(BM_std_find, float) + BENCH_ARGS; + BENCHMARK_TEMPLATE(BM_xsimd_find, float) + BENCH_ARGS; + +} \ No newline at end of file diff --git a/include/xsimd_algorithm/stl/find.hpp b/include/xsimd_algorithm/stl/find.hpp new file mode 100644 index 0000000..cc1fda2 --- /dev/null +++ b/include/xsimd_algorithm/stl/find.hpp @@ -0,0 +1,29 @@ +/*************************************************************************** + * 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_FIND_HPP +#define XSIMD_ALGORITHMS_FIND_HPP + +#include "xsimd/xsimd.hpp" +#include "xsimd_algorithm/stl/find_if.hpp" + +#include +#include + +namespace xsimd +{ + template ::value_type> + ContiguousIterator find(ContiguousIterator first, ContiguousIterator last, const T& value) noexcept + { + return find_if(first, last, [value](const auto& x) { return x == value; }); + } +} +#endif // XSIMD_ALGORITHMS_FIND_HPP \ No newline at end of file diff --git a/include/xsimd_algorithm/stl/find_if.hpp b/include/xsimd_algorithm/stl/find_if.hpp new file mode 100644 index 0000000..43be142 --- /dev/null +++ b/include/xsimd_algorithm/stl/find_if.hpp @@ -0,0 +1,90 @@ +/*************************************************************************** + * 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_FIND_IF_HPP +#define XSIMD_ALGORITHMS_FIND_IF_HPP + +#include "xsimd/xsimd.hpp" + +#include +#include + +namespace xsimd +{ + namespace detail + { + template + ForwardIterator sequential_find_if(ForwardIterator first, ForwardIterator last, Predicate&& p) noexcept + { + for (; first != last; ++first) + { + if (p(*first)) + { + return first; + } + } + return last; + } + } + + template + ContiguousIterator find_if(ContiguousIterator first, ContiguousIterator last, Predicate&& p) 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 exit for small sizes + if (size < simd_size) + { + return detail::sequential_find_if(first, last, std::forward(p)); + } + + // 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); + + const auto prologue_it_result = detail::sequential_find_if(first, align_begin_it, std::forward(p)); + + if (prologue_it_result!= align_begin_it) + { + return prologue_it_result; + } + + // main loop: process aligned elements + for (auto current = align_begin_it; current != align_end_it; std::advance(current, simd_size)) + { + batch_type batch_data = batch_type::load_aligned(&(*current)); + auto offset = xsimd::countr_zero(p(batch_data)); + if (offset != simd_size) + { + return std::next(current, offset); + } + } + + // epilogue: process remaining elements + const auto epilogue_it_result = detail::sequential_find_if(align_end_it, last, std::forward(p)); + if (epilogue_it_result != last) + { + return epilogue_it_result; + } + + return last; + } +} +#endif // XSIMD_ALGORITHMS_FIND_IF_HPP \ No newline at end of file diff --git a/include/xsimd_algorithm/stl/find_if_not.hpp b/include/xsimd_algorithm/stl/find_if_not.hpp new file mode 100644 index 0000000..99a71d4 --- /dev/null +++ b/include/xsimd_algorithm/stl/find_if_not.hpp @@ -0,0 +1,30 @@ +/*************************************************************************** + * 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_FIND_IF_NOT_HPP +#define XSIMD_ALGORITHMS_FIND_IF_NOT_HPP + +#include "xsimd/xsimd.hpp" +#include "xsimd_algorithm/stl/find_if.hpp" + +#include +#include + +namespace xsimd +{ + template + ContiguousIterator find_if_not(ContiguousIterator first, ContiguousIterator last, Predicate&& p) noexcept + { + return find_if(first, last, [p](const auto& x) { return !p(x); }); + } +} + +#endif // XSIMD_ALGORITHMS_FIND_IF_NOT_HPP \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 86e6b9b..ece86bd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,6 +51,9 @@ set(XSIMD_ALGORITHM_TESTS test_any_of.cpp test_none_of.cpp test_arange.cpp + test_find.cpp + test_find_if.cpp + test_find_if_not.cpp test_map.cpp test_iterator.cpp test_math.cpp diff --git a/test/test_find.cpp b/test/test_find.cpp new file mode 100644 index 0000000..77a061d --- /dev/null +++ b/test/test_find.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * 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/find.hpp" + + +#ifndef XSIMD_NO_SUPPORTED_ARCHITECTURE + +#include "doctest/doctest.h" + +#include +#include +#include + +#if XSIMD_WITH_NEON && !XSIMD_WITH_NEON64 +#define FIND_TYPES int, float +#else +#define FIND_TYPES int, long, float, double +#endif + + +template +struct find_test +{ + using vector = std::vector; + static constexpr std::size_t simd_size = xsimd::batch::size; + + static vector fill_data(size_t size, size_t position, Type constant) + { + vector result(size); + std::iota(begin(result), end(result), Type(1)); + if (position < size) + { + result[position] = constant; + } + return result; + } + + void test_find_aligned( size_t size, Type constant= {-42}) const + { + const auto position = size / 2; + const vector data = fill_data(size, position, constant); + + const auto result = xsimd::find(data.begin(), data.end(), constant); + const auto expected = std::find(data.begin(), data.end(), constant); + CHECK(result == expected); + } + + void test_find_misaligned(size_t size, Type constant= {-42}) const + { + const auto position = size / 2; + const size_t missalignment = 1; + const vector data = fill_data(size + missalignment * 2, position, constant); + auto first = data.begin() + missalignment; + auto last = first + size; + + const auto result = xsimd::find(first, last, constant); + const auto expected = std::find(first, last, constant); + + CHECK(result == expected); + } +}; + +TEST_CASE_TEMPLATE("find test", T, FIND_TYPES) +{ + using Test = find_test; + + const std::array test_sizes { + size_t { 0 }, + size_t { 1 }, + Test::simd_size - 1, + Test::simd_size, + Test ::simd_size + 1, + }; + + Test test; + + size_t sub_case_id = 0; + for (const auto size : test_sizes) + { + const std::string aligned_subcase_name = "Aligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size); + SUBCASE(aligned_subcase_name.c_str()) + { + test.test_find_aligned(size); + } + const std::string misaligned_subcase_name = "Misaligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size); + SUBCASE(misaligned_subcase_name.c_str()) + { + test.test_find_misaligned(size); + } + } +} +#endif \ No newline at end of file diff --git a/test/test_find_if.cpp b/test/test_find_if.cpp new file mode 100644 index 0000000..a8eb690 --- /dev/null +++ b/test/test_find_if.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * 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/find_if.hpp" + + +#ifndef XSIMD_NO_SUPPORTED_ARCHITECTURE + +#include "doctest/doctest.h" + +#include +#include +#include + +#if XSIMD_WITH_NEON && !XSIMD_WITH_NEON64 +#define FIND_IF_TYPES int, float +#else +#define FIND_IF_TYPES int, long, float, double +#endif + + +template +struct find_if_test +{ + using vector = std::vector; + static constexpr std::size_t simd_size = xsimd::batch::size; + + static vector fill_data(size_t size, size_t position, Type constant) + { + vector result(size); + std::iota(begin(result), end(result), Type(1)); + if (position < size) + { + result[position] = constant; + } + return result; + } + + void test_find_if_aligned( size_t size, Type constant= {-42}) const + { + const auto position = size / 2; + const vector data = fill_data(size, position, constant); + + const auto result = xsimd::find_if(data.begin(), data.end(), [constant](const auto& x) { return x == constant; }); + const auto expected = std::find_if(data.begin(), data.end(), [constant](const auto& x) { return x == constant; }); + CHECK(result == expected); + } + + void test_find_if_misaligned(size_t size, Type constant= {-42}) const + { + const auto position = size / 2; + const size_t missalignment = 1; + const vector data = fill_data(size + missalignment * 2, position, constant); + auto first = data.begin() + missalignment; + auto last = first + size; + + const auto result = xsimd::find_if(first, last, [constant](const auto& x) { return x == constant; }); + const auto expected = std::find_if(first, last, [constant](const auto& x) { return x == constant; }); + + CHECK(result == expected); + } +}; + +TEST_CASE_TEMPLATE("find_if test", T, FIND_IF_TYPES) +{ + using Test = find_if_test; + + const std::array test_sizes { + size_t { 0 }, + size_t { 1 }, + Test::simd_size - 1, + Test::simd_size, + Test ::simd_size + 1, + }; + + Test test; + + size_t sub_case_id = 0; + for (const auto size : test_sizes) + { + const std::string aligned_subcase_name = "Aligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size); + SUBCASE(aligned_subcase_name.c_str()) + { + test.test_find_if_aligned(size); + } + const std::string misaligned_subcase_name = "Misaligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size); + SUBCASE(misaligned_subcase_name.c_str()) + { + test.test_find_if_misaligned(size); + } + } +} +#endif \ No newline at end of file diff --git a/test/test_find_if_not.cpp b/test/test_find_if_not.cpp new file mode 100644 index 0000000..171d022 --- /dev/null +++ b/test/test_find_if_not.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * 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/find_if_not.hpp" + + +#ifndef XSIMD_NO_SUPPORTED_ARCHITECTURE + +#include "doctest/doctest.h" + +#include +#include +#include + +#if XSIMD_WITH_NEON && !XSIMD_WITH_NEON64 +#define FIND_IF_NOT_TYPES int, float +#else +#define FIND_IF_NOT_TYPES int, long, float, double +#endif + + +template +struct find_if_not_test +{ + using vector = std::vector; + static constexpr std::size_t simd_size = xsimd::batch::size; + + static vector fill_data(size_t size, size_t position, Type constant) + { + vector result(size); + std::iota(begin(result), end(result), Type(1)); + if (position < size) + { + result[position] = constant; + } + return result; + } + + void test_find_if_not_naligned( size_t size, Type constant= {-42}) const + { + const auto position = size / 2; + const vector data = fill_data(size, position, constant); + + const auto result = xsimd::find_if_not(data.begin(), data.end(), [constant](const auto& x) { return x != constant; }); + const auto expected = std::find_if_not(data.begin(), data.end(), [constant](const auto& x) { return x != constant; }); + CHECK(result == expected); + } + + void test_find_if_not_misaligned(size_t size, Type constant= {-42}) const + { + const auto position = size / 2; + const size_t missalignment = 1; + const vector data = fill_data(size + missalignment * 2, position, constant); + auto first = data.begin() + missalignment; + auto last = first + size; + + const auto result = xsimd::find_if_not(first, last, [constant](const auto& x) { return x != constant; }); + const auto expected = std::find_if_not(first, last, [constant](const auto& x) { return x != constant; }); + + CHECK(result == expected); + } +}; + +TEST_CASE_TEMPLATE("find_if_not test", T, FIND_IF_NOT_TYPES) +{ + using Test = find_if_not_test; + + const std::array test_sizes { + size_t { 0 }, + size_t { 1 }, + Test::simd_size - 1, + Test::simd_size, + Test ::simd_size + 1, + }; + + Test test; + + size_t sub_case_id = 0; + for (const auto size : test_sizes) + { + const std::string aligned_subcase_name = "Aligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size); + SUBCASE(aligned_subcase_name.c_str()) + { + test.test_find_if_not_naligned(size); + } + const std::string misaligned_subcase_name = "Misaligned" + std::to_string(sub_case_id++) + ", size: " + std::to_string(size); + SUBCASE(misaligned_subcase_name.c_str()) + { + test.test_find_if_not_misaligned(size); + } + } +} +#endif \ No newline at end of file