diff options
5 files changed, 88 insertions, 53 deletions
diff --git a/vespalib/src/tests/coro/generator/CMakeLists.txt b/vespalib/src/tests/coro/generator/CMakeLists.txt index e2534274f7c..d2c8ec9b857 100644 --- a/vespalib/src/tests/coro/generator/CMakeLists.txt +++ b/vespalib/src/tests/coro/generator/CMakeLists.txt @@ -9,6 +9,7 @@ vespa_add_executable(vespalib_generator_test_app TEST vespa_add_executable(vespalib_generator_bench_app TEST SOURCES generator_bench.cpp + hidden_sequence.cpp DEPENDS vespalib GTest::GTest diff --git a/vespalib/src/tests/coro/generator/generator_bench.cpp b/vespalib/src/tests/coro/generator/generator_bench.cpp index ae0e0fce894..4fa9c6186f5 100644 --- a/vespalib/src/tests/coro/generator/generator_bench.cpp +++ b/vespalib/src/tests/coro/generator/generator_bench.cpp @@ -1,5 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "hidden_sequence.h" #include <vespa/vespalib/coro/generator.h> #include <vespa/vespalib/util/benchmark_timer.h> #include <vespa/vespalib/util/sequence.h> @@ -11,23 +12,8 @@ using vespalib::coro::Generator; using vespalib::BenchmarkTimer; using vespalib::Sequence; -size_t calc_sum(const std::vector<size_t> &values) { - size_t sum = 0; - for (auto&& value: values) { - sum += value; - } - return sum; -} - -size_t calc_sum(Generator<size_t> values) { - size_t sum = 0; - for (auto&& value: values) { - sum += value; - } - return sum; -} - -size_t calc_sum(Generator<const size_t &> values) { +template <std::ranges::input_range T> +size_t calc_sum(T &&values) { size_t sum = 0; for (auto&& value: values) { sum += value; @@ -42,6 +28,7 @@ size_t calc_sum(Sequence<size_t> &seq) { } return sum; } +size_t calc_sum(Sequence<size_t>::UP &ptr) { return calc_sum(*ptr); } std::vector<size_t> make_data() __attribute__((noinline)); std::vector<size_t> make_data() { @@ -83,9 +70,9 @@ size_t calc_sum_generator(const std::vector<size_t> &data) { return calc_sum(gen_values(data)); } -Generator<const size_t &> gen_values_noinline(const std::vector<size_t> &data) __attribute__((noinline)); -Generator<const size_t &> gen_values_noinline(const std::vector<size_t> &data) { - for (const size_t &value: data) { +Generator<size_t> gen_values_noinline(const std::vector<size_t> &data) __attribute__((noinline)); +Generator<size_t> gen_values_noinline(const std::vector<size_t> &data) { + for (size_t value: data) { co_yield value; } } @@ -104,22 +91,37 @@ double bench(auto fun, const std::vector<size_t> &data, size_t &res) { return timer.min_time() * 1000.0; } +double bench_indirect(auto factory, const std::vector<size_t> &data, size_t &res) { + BenchmarkTimer timer(5.0); + while (timer.has_budget()) { + auto seq = factory(data); + timer.before(); + res = calc_sum(seq); + timer.after(); + } + return timer.min_time() * 1000.0; +} + TEST(GeneratorBench, direct_vs_generated_for_loop) { auto data = make_data(); - size_t result[4] = { 0, 1, 2, 3 }; + size_t result[5] = { 0, 1, 2, 3, 4 }; double sequence_ms = bench(calc_sum_sequence, data, result[0]); fprintf(stderr, "sequence: %g ms\n", sequence_ms); - double generator_noinline_ms = bench(calc_sum_generator_noinline, data, result[1]); + double hidden_sequence_ms = bench_indirect(make_ext_seq, data, result[1]); + fprintf(stderr, "hidden sequence: %g ms\n", hidden_sequence_ms); + double generator_noinline_ms = bench(calc_sum_generator_noinline, data, result[2]); fprintf(stderr, "generator_noinline: %g ms\n", generator_noinline_ms); - double generator_ms = bench(calc_sum_generator, data, result[2]); + double generator_ms = bench(calc_sum_generator, data, result[3]); fprintf(stderr, "generator: %g ms\n", generator_ms); - double direct_ms = bench(calc_sum_direct, data, result[3]); + double direct_ms = bench(calc_sum_direct, data, result[4]); fprintf(stderr, "direct: %g ms\n", direct_ms); EXPECT_EQ(result[0], result[1]); EXPECT_EQ(result[0], result[2]); EXPECT_EQ(result[0], result[3]); + EXPECT_EQ(result[0], result[4]); fprintf(stderr, "ratio (generator/direct): %g\n", (generator_ms/direct_ms)); fprintf(stderr, "ratio (generator_noinline/generator): %g\n", (generator_noinline_ms/generator_ms)); + fprintf(stderr, "ratio (sequence/generator_noinline): %g\n", (sequence_ms/generator_noinline_ms)); fprintf(stderr, "ratio (sequence/generator): %g\n", (sequence_ms/generator_ms)); } diff --git a/vespalib/src/tests/coro/generator/hidden_sequence.cpp b/vespalib/src/tests/coro/generator/hidden_sequence.cpp new file mode 100644 index 00000000000..39eedb4623c --- /dev/null +++ b/vespalib/src/tests/coro/generator/hidden_sequence.cpp @@ -0,0 +1,21 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "hidden_sequence.h" + +namespace { + +struct MyHiddenSeq : vespalib::Sequence<size_t> { + const std::vector<size_t> &data; + size_t pos; + MyHiddenSeq(const std::vector<size_t> &data_in) + : data(data_in), pos(0) {} + bool valid() const override { return pos < data.size(); } + size_t get() const override { return data[pos]; } + void next() override { ++pos; } +}; + +} + +vespalib::Sequence<size_t>::UP make_ext_seq(const std::vector<size_t> &data) { + return std::make_unique<MyHiddenSeq>(data); +} diff --git a/vespalib/src/tests/coro/generator/hidden_sequence.h b/vespalib/src/tests/coro/generator/hidden_sequence.h new file mode 100644 index 00000000000..917b50b1129 --- /dev/null +++ b/vespalib/src/tests/coro/generator/hidden_sequence.h @@ -0,0 +1,8 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/util/sequence.h> +#include <vector> + +vespalib::Sequence<size_t>::UP make_ext_seq(const std::vector<size_t> &data); diff --git a/vespalib/src/vespa/vespalib/coro/generator.h b/vespalib/src/vespa/vespalib/coro/generator.h index 9f6b6c9fde5..1f1468d1d19 100644 --- a/vespalib/src/vespa/vespalib/coro/generator.h +++ b/vespalib/src/vespa/vespalib/coro/generator.h @@ -24,51 +24,54 @@ namespace vespalib::coro { * make it easier for compilers to perform HALO, code inlining and * even constant folding. **/ -template <typename T, typename ValueType = std::remove_cvref<T>> +template <typename R, typename V = void> class [[nodiscard]] Generator { public: - using value_type = ValueType; - using Pointer = std::add_pointer_t<T>; + // these are from the std::generator proposal (P2502R2) + using value_type = std::conditional_t<std::is_void_v<V>, std::remove_cvref_t<R>, V>; + using ref_type = std::conditional_t<std::is_void_v<V>, R &&, R>; + using yield_type = std::conditional_t<std::is_reference_v<ref_type>, ref_type, const ref_type &>; + using cref_yield = const std::remove_reference_t<yield_type> &; + using ptr_type = std::add_pointer_t<yield_type>; + using cpy_type = std::remove_cvref_t<yield_type>; + static constexpr bool extra_yield = std::is_rvalue_reference_v<yield_type> && std::constructible_from<cpy_type, cref_yield>; class promise_type; using Handle = std::coroutine_handle<promise_type>; class promise_type { private: - Pointer _ptr; + ptr_type _state; + struct copy_awaiter : std::suspend_always { + copy_awaiter(const cpy_type &value, ptr_type &ptr) + : value_cpy(value) + { + ptr = std::addressof(value_cpy); + } + copy_awaiter(copy_awaiter&&) = delete; + copy_awaiter(const copy_awaiter&) = delete; + cpy_type value_cpy; + }; + public: promise_type(promise_type &&) = delete; promise_type(const promise_type &) = delete; - promise_type() noexcept : _ptr(nullptr) {} - Generator<T> get_return_object() { return Generator(Handle::from_promise(*this)); } + promise_type() noexcept : _state() {} + Generator get_return_object() { return Generator(Handle::from_promise(*this)); } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } - std::suspend_always yield_value(T &&value) noexcept { - _ptr = &value; + std::suspend_always yield_value(yield_type value) noexcept { + _state = std::addressof(value); return {}; } - auto yield_value(const T &value) - noexcept(std::is_nothrow_constructible_v<T, const T &>) - requires(!std::is_reference_v<T> && std::copy_constructible<T>) - { - struct awaiter : std::suspend_always { - awaiter(const T &value_in, Pointer &ptr) - noexcept(std::is_nothrow_constructible_v<T, const T &>) - : value_cpy(value_in) - { - ptr = std::addressof(value_cpy); - } - awaiter(awaiter&&) = delete; - awaiter(const awaiter&) = delete; - T value_cpy; - }; - return awaiter(value, _ptr); + auto yield_value(cref_yield value) requires extra_yield { + return copy_awaiter(value, _state); } void return_void() noexcept {} void unhandled_exception() { throw; } - T &&result() noexcept { - return std::forward<T>(*_ptr); + ref_type result() noexcept { + return static_cast<ref_type>(*_state); } template<typename U> U &&await_transform(U &&value) = delete; }; @@ -87,7 +90,7 @@ public: } using iterator_concept = std::input_iterator_tag; using difference_type = std::ptrdiff_t; - using value_type = std::remove_cvref_t<T>; + using value_type = Generator::value_type; bool operator==(std::default_sentinel_t) const { return _handle.done(); } @@ -98,8 +101,8 @@ public: void operator++(int) { operator++(); } - decltype(auto) operator*() const { - return std::forward<T>(_handle.promise().result()); + ref_type operator*() const { + return _handle.promise().result(); } }; |