diff options
author | 2022-05-09 09:36:19 -0700 | |
---|---|---|
committer | 2022-05-16 08:13:29 -0700 | |
commit | b17c62120b646f378c0b842b3d7fcce8a5540e3e (patch) | |
tree | 13f01098a51f9c387d3e8c87683f55ef40de4ba4 /include/ftl | |
parent | bb448ce9aa521f9574d94c9ec2d57eb7d37382cb (diff) |
FTL: Yield futures without overhead
ftl::yield, which lifts T to std::future<T>, incurs the cost of
allocating, ref counting, and locking the latter's shared state.
Consolidate the existing std::future extensions into ftl::Future,
and optimize ftl::yield by including static storage for T within.
Bug: 232436803
Test: simpleperf (-31% cycles in postFramebuffer)
Change-Id: I9a7ca7de17e7af10515de97d2f6a0dfa24e35d7a
Diffstat (limited to 'include/ftl')
-rw-r--r-- | include/ftl/details/future.h | 98 | ||||
-rw-r--r-- | include/ftl/future.h | 146 |
2 files changed, 183 insertions, 61 deletions
diff --git a/include/ftl/details/future.h b/include/ftl/details/future.h new file mode 100644 index 0000000000..df1323e8be --- /dev/null +++ b/include/ftl/details/future.h @@ -0,0 +1,98 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +#pragma once + +namespace android::ftl { + +template <typename, template <typename> class> +class Future; + +namespace details { + +template <typename T> +struct future_result { + using type = T; +}; + +template <typename T> +struct future_result<std::future<T>> { + using type = T; +}; + +template <typename T> +struct future_result<std::shared_future<T>> { + using type = T; +}; + +template <typename T, template <typename> class FutureImpl> +struct future_result<Future<T, FutureImpl>> { + using type = T; +}; + +template <typename T> +using future_result_t = typename future_result<T>::type; + +struct ValueTag {}; + +template <typename, typename T, template <typename> class> +class BaseFuture; + +template <typename Self, typename T> +class BaseFuture<Self, T, std::future> { + using Impl = std::future<T>; + + public: + Future<T, std::shared_future> share() { + if (T* value = std::get_if<T>(&self())) { + return {ValueTag{}, std::move(*value)}; + } + + return std::get<Impl>(self()).share(); + } + + protected: + T get() { + if (T* value = std::get_if<T>(&self())) { + return std::move(*value); + } + + return std::get<Impl>(self()).get(); + } + + private: + auto& self() { return static_cast<Self&>(*this).future_; } +}; + +template <typename Self, typename T> +class BaseFuture<Self, T, std::shared_future> { + using Impl = std::shared_future<T>; + + protected: + const T& get() const { + if (const T* value = std::get_if<T>(&self())) { + return *value; + } + + return std::get<Impl>(self()).get(); + } + + private: + const auto& self() const { return static_cast<const Self&>(*this).future_; } +}; + +} // namespace details +} // namespace android::ftl diff --git a/include/ftl/future.h b/include/ftl/future.h index dd6358fa7d..c78f9b76b6 100644 --- a/include/ftl/future.h +++ b/include/ftl/future.h @@ -19,91 +19,115 @@ #include <future> #include <type_traits> #include <utility> +#include <variant> + +#include <ftl/details/future.h> namespace android::ftl { -// Creates a future that defers a function call until its result is queried. +// Thin wrapper around FutureImpl<T> (concretely std::future<T> or std::shared_future<T>) with +// extensions for pure values (created via ftl::yield) and continuations. // -// auto future = ftl::defer([](int x) { return x + 1; }, 99); -// assert(future.get() == 100); +// See also SharedFuture<T> shorthand below. // -template <typename F, typename... Args> -inline auto defer(F&& f, Args&&... args) { - return std::async(std::launch::deferred, std::forward<F>(f), std::forward<Args>(args)...); -} +template <typename T, template <typename> class FutureImpl = std::future> +class Future final : public details::BaseFuture<Future<T, FutureImpl>, T, FutureImpl> { + using Base = details::BaseFuture<Future, T, FutureImpl>; -// Creates a future that wraps a value. -// -// auto future = ftl::yield(42); -// assert(future.get() == 42); -// -// auto ptr = std::make_unique<char>('!'); -// auto future = ftl::yield(std::move(ptr)); -// assert(*future.get() == '!'); -// -template <typename T> -inline std::future<T> yield(T&& v) { - return defer([](T&& v) { return std::forward<T>(v); }, std::forward<T>(v)); -} - -namespace details { - -template <typename T> -struct future_result { - using type = T; -}; + friend Base; // For BaseFuture<...>::self. + friend details::BaseFuture<Future<T>, T, std::future>; // For BaseFuture<...>::share. -template <typename T> -struct future_result<std::future<T>> { - using type = T; -}; - -template <typename T> -using future_result_t = typename future_result<T>::type; + public: + // Constructs an invalid future. + Future() : future_(std::in_place_type<FutureImpl<T>>) {} -// Attaches a continuation to a future. The continuation is a function that maps T to either R or -// std::future<R>. In the former case, the chain wraps the result in a future as if by ftl::yield. -// -// auto future = ftl::yield(123); -// std::future<char> futures[] = {ftl::yield('a'), ftl::yield('b')}; -// -// std::future<char> chain = -// ftl::chain(std::move(future)) -// .then([](int x) { return static_cast<std::size_t>(x % 2); }) -// .then([&futures](std::size_t i) { return std::move(futures[i]); }); -// -// assert(chain.get() == 'b'); -// -template <typename T> -struct Chain { - // Implicit conversion. - Chain(std::future<T>&& f) : future(std::move(f)) {} - operator std::future<T>&&() && { return std::move(future); } + // Constructs a future from its standard counterpart, implicitly. + Future(FutureImpl<T>&& f) : future_(std::move(f)) {} - T get() && { return future.get(); } + bool valid() const { + return std::holds_alternative<T>(future_) || std::get<FutureImpl<T>>(future_).valid(); + } + // Forwarding functions. Base::share is only defined when FutureImpl is std::future, whereas the + // following are defined for either FutureImpl: + using Base::get; + + // Attaches a continuation to the future. The continuation is a function that maps T to either R + // or ftl::Future<R>. In the former case, the chain wraps the result in a future as if by + // ftl::yield. + // + // auto future = ftl::yield(123); + // ftl::Future<char> futures[] = {ftl::yield('a'), ftl::yield('b')}; + // + // auto chain = + // ftl::Future(std::move(future)) + // .then([](int x) { return static_cast<std::size_t>(x % 2); }) + // .then([&futures](std::size_t i) { return std::move(futures[i]); }); + // + // assert(chain.get() == 'b'); + // template <typename F, typename R = std::invoke_result_t<F, T>> - auto then(F&& op) && -> Chain<future_result_t<R>> { + auto then(F&& op) && -> Future<details::future_result_t<R>> { return defer( [](auto&& f, F&& op) { R r = op(f.get()); - if constexpr (std::is_same_v<R, future_result_t<R>>) { + if constexpr (std::is_same_v<R, details::future_result_t<R>>) { return r; } else { return r.get(); } }, - std::move(future), std::forward<F>(op)); + std::move(*this), std::forward<F>(op)); } - std::future<T> future; -}; + private: + template <typename V> + friend Future<V> yield(V&&); -} // namespace details + template <typename V, typename... Args> + friend Future<V> yield(Args&&...); + + template <typename... Args> + Future(details::ValueTag, Args&&... args) + : future_(std::in_place_type<T>, std::forward<Args>(args)...) {} + + std::variant<T, FutureImpl<T>> future_; +}; template <typename T> -inline auto chain(std::future<T>&& f) -> details::Chain<T> { - return std::move(f); +using SharedFuture = Future<T, std::shared_future>; + +// Deduction guide for implicit conversion. +template <typename T, template <typename> class FutureImpl> +Future(FutureImpl<T>&&) -> Future<T, FutureImpl>; + +// Creates a future that wraps a value. +// +// auto future = ftl::yield(42); +// assert(future.get() == 42); +// +// auto ptr = std::make_unique<char>('!'); +// auto future = ftl::yield(std::move(ptr)); +// assert(*future.get() == '!'); +// +template <typename V> +inline Future<V> yield(V&& value) { + return {details::ValueTag{}, std::move(value)}; +} + +template <typename V, typename... Args> +inline Future<V> yield(Args&&... args) { + return {details::ValueTag{}, std::forward<Args>(args)...}; +} + +// Creates a future that defers a function call until its result is queried. +// +// auto future = ftl::defer([](int x) { return x + 1; }, 99); +// assert(future.get() == 100); +// +template <typename F, typename... Args> +inline auto defer(F&& f, Args&&... args) { + return Future(std::async(std::launch::deferred, std::forward<F>(f), std::forward<Args>(args)...)); } } // namespace android::ftl |