diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 3bf0b19..46c876d 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_count.cpp ) add_executable(benchmark_xsimd_algorithm ${XSIMD_ALGORITHM_BENCHMARKS}) diff --git a/benchmark/bench_count.cpp b/benchmark/bench_count.cpp new file mode 100644 index 0000000..de8e4d9 --- /dev/null +++ b/benchmark/bench_count.cpp @@ -0,0 +1,61 @@ +#include +#include + +#include + +#include "xsimd_algorithm/stl/count.hpp" + +#define BENCH_ARGS ->Arg(kSmall)->Arg(kMedium)->Arg(kLarge) + +namespace +{ + 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_count(benchmark::State& state) + { + const auto constant = T { 5 }; + + auto src = make_input(static_cast(state.range(0)), constant); + + for (auto _ : state) + { + auto result = std::count(src.begin(), src.end(), constant); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + state.SetItemsProcessed(state.iterations() * state.range(0)); + } + + template + void BM_xsimd_count(benchmark::State& state) + { + const auto constant = T { 5 }; + auto src = make_input(static_cast(state.range(0)), constant); + for (auto _ : state) + { + auto result = xsimd::count(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; + + // count + BENCHMARK_TEMPLATE(BM_std_count, float) + BENCH_ARGS; + BENCHMARK_TEMPLATE(BM_xsimd_count, float) + BENCH_ARGS; + +} \ No newline at end of file diff --git a/include/xsimd_algorithm/algorithms.hpp b/include/xsimd_algorithm/algorithms.hpp index 21d03f2..943fcd4 100644 --- a/include/xsimd_algorithm/algorithms.hpp +++ b/include/xsimd_algorithm/algorithms.hpp @@ -15,7 +15,9 @@ #include "xsimd_algorithm/stl/all_of.hpp" #include "xsimd_algorithm/stl/any_of.hpp" #include "xsimd_algorithm/stl/arange.hpp" -//#include "xsimd_algorithm/stl/none_of.hpp" +#include "xsimd_algorithm/stl/count.hpp" +#include "xsimd_algorithm/stl/count_if.hpp" +#include "xsimd_algorithm/stl/none_of.hpp" #include "xsimd_algorithm/stl/reduce.hpp" #include "xsimd_algorithm/stl/transform.hpp" diff --git a/include/xsimd_algorithm/stl/count.hpp b/include/xsimd_algorithm/stl/count.hpp new file mode 100644 index 0000000..d8828e9 --- /dev/null +++ b/include/xsimd_algorithm/stl/count.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_COUNT_HPP +#define XSIMD_ALGORITHMS_COUNT_HPP + +#include "xsimd/xsimd.hpp" +#include "xsimd_algorithm/stl/count_if.hpp" + +#include + +namespace xsimd +{ + template ::value_type> + std::iter_difference_t count(ContiguousIterator first, ContiguousIterator last, const T& value) noexcept + { + return count_if(first, last, [value](const auto& x) { return x == value; }); + } +} + +#endif // XSIMD_ALGORITHMS_COUNT_HPP \ No newline at end of file diff --git a/include/xsimd_algorithm/stl/count_if.hpp b/include/xsimd_algorithm/stl/count_if.hpp new file mode 100644 index 0000000..946ce91 --- /dev/null +++ b/include/xsimd_algorithm/stl/count_if.hpp @@ -0,0 +1,82 @@ +/*************************************************************************** + * 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_COUNT_IF_HPP +#define XSIMD_ALGORITHMS_COUNT_IF_HPP + +#include "xsimd/xsimd.hpp" + +#include + +namespace xsimd +{ + namespace detail + { + template + std::iter_difference_t sequential_count_if(ForwardIterator first, ForwardIterator last, Predicate&& p) noexcept + { + std::iter_difference_t count = {0}; + for (; first != last; ++first) + { + if (p(*first)) + { + ++count; + } + } + return count; + } + } + + template + std::iter_difference_t count_if(ContiguousIterator first, ContiguousIterator last, Predicate&& p) noexcept + { + std::iter_difference_t count = {0}; + + 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_count_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); + + count += detail::sequential_count_if(first, align_begin_it, std::forward(p)); + + // main loop: process aligned elements + for (auto current = align_begin_it; current != align_end_it; std::advance(current, simd_size)) + { + auto batch_val = batch_type::load_aligned(&(*current)); + auto mask_batch = p(batch_val); + count += xsimd::count(mask_batch); + } + + // epilogue: process remaining elements + count += detail::sequential_count_if(align_end_it, last, std::forward(p)); + + return count; + } + +} + +#endif // XSIMD_ALGORITHMS_COUNT_IF_HPP \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 86e6b9b..66d55be 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_count.cpp + test_count_if.cpp ) add_executable(test_xsimd_algorithm ${XSIMD_ALGORITHM_TESTS}) diff --git a/test/test_count.cpp b/test/test_count.cpp new file mode 100644 index 0000000..d924ceb --- /dev/null +++ b/test/test_count.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * 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/count.hpp" + +#ifndef XSIMD_NO_SUPPORTED_ARCHITECTURE + +#include "doctest/doctest.h" + +#include +#include + +#if XSIMD_WITH_NEON && !XSIMD_WITH_NEON64 +#define COUNT_TYPES int, float +#else +#define COUNT_TYPES int, long, float, double +#endif + +template +struct count_test +{ + using vector = std::vector; + static constexpr std::size_t simd_size = xsimd::batch::size; + + static vector fill_data(size_t size, Type constant) + { + vector result(size); + + for (size_t i = 0; i < size; ++i) + { + result[i] = constant; + } + + return result; + } + + void test_count_aligned( size_t size, Type constant= {5}) const + { + const vector data = fill_data(size, constant); + + const auto result = xsimd::count(data.begin(), data.end(), constant); + + CHECK(result == size); + } + + void test_count_misaligned(size_t size, Type constant= {5}) const + { + const size_t missalignment = 1; + const vector data = fill_data(size + missalignment * 2, constant); + auto first = data.begin() + missalignment; + auto last = first + size; + + const auto result = xsimd::count(first, last, constant); + + CHECK(result == std::distance(first, last)); + } +}; + +TEST_CASE_TEMPLATE("count test", T, COUNT_TYPES) +{ + using Test = count_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_count_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_count_misaligned(size); + } + } +} +#endif \ No newline at end of file diff --git a/test/test_count_if.cpp b/test/test_count_if.cpp new file mode 100644 index 0000000..39a7c3d --- /dev/null +++ b/test/test_count_if.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * 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/count_if.hpp" + +#ifndef XSIMD_NO_SUPPORTED_ARCHITECTURE + +#include "doctest/doctest.h" + +#include +#include + +#if XSIMD_WITH_NEON && !XSIMD_WITH_NEON64 +#define COUNT_IF_TYPES int, float +#else +#define COUNT_IF_TYPES int, long, float, double +#endif + +template +struct count_if_test +{ + using vector = std::vector; + static constexpr std::size_t simd_size = xsimd::batch::size; + + static vector fill_data(size_t size, Type constant) + { + vector result(size); + + for (size_t i = 0; i < size; ++i) + { + result[i] = constant; + } + + return result; + } + + void test_count_if_aligned( size_t size, Type constant= {5}) const + { + const vector data = fill_data(size, constant); + + const auto result = xsimd::count_if(data.begin(), data.end(), [constant](const auto& x) { return x == constant; }); + + CHECK(result == size); + } + + void test_count_if_misaligned(size_t size, Type constant= {5}) const + { + const size_t missalignment = 1; + const vector data = fill_data(size + missalignment * 2, constant); + auto first = data.begin() + missalignment; + auto last = first + size; + + const auto result = xsimd::count_if(first, last, [constant](const auto& x) { return x == constant; }); + + CHECK(result == std::distance(first, last)); + } +}; + +TEST_CASE_TEMPLATE("count_if test", T, COUNT_IF_TYPES) +{ + using Test = count_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_count_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_count_if_misaligned(size); + } + } +} +#endif \ No newline at end of file