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
diff --git a/include/ftl/future.h b/include/ftl/future.h
index dd6358f..c78f9b7 100644
--- a/include/ftl/future.h
+++ b/include/ftl/future.h
@@ -19,18 +19,87 @@
#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>;
+
+ friend Base; // For BaseFuture<...>::self.
+ friend details::BaseFuture<Future<T>, T, std::future>; // For BaseFuture<...>::share.
+
+ public:
+ // Constructs an invalid future.
+ Future() : future_(std::in_place_type<FutureImpl<T>>) {}
+
+ // Constructs a future from its standard counterpart, implicitly.
+ Future(FutureImpl<T>&& f) : future_(std::move(f)) {}
+
+ 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) && -> Future<details::future_result_t<R>> {
+ return defer(
+ [](auto&& f, F&& op) {
+ R r = op(f.get());
+ if constexpr (std::is_same_v<R, details::future_result_t<R>>) {
+ return r;
+ } else {
+ return r.get();
+ }
+ },
+ std::move(*this), std::forward<F>(op));
+ }
+
+ private:
+ template <typename V>
+ friend Future<V> yield(V&&);
+
+ 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>
+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.
//
@@ -41,69 +110,24 @@
// 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));
+template <typename V>
+inline Future<V> yield(V&& value) {
+ return {details::ValueTag{}, std::move(value)};
}
-namespace details {
+template <typename V, typename... Args>
+inline Future<V> yield(Args&&... args) {
+ return {details::ValueTag{}, std::forward<Args>(args)...};
+}
-template <typename T>
-struct future_result {
- using type = T;
-};
-
-template <typename T>
-struct future_result<std::future<T>> {
- using type = T;
-};
-
-template <typename T>
-using future_result_t = typename future_result<T>::type;
-
-// 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.
+// Creates a future that defers a function call until its result is queried.
//
-// auto future = ftl::yield(123);
-// std::future<char> futures[] = {ftl::yield('a'), ftl::yield('b')};
+// auto future = ftl::defer([](int x) { return x + 1; }, 99);
+// assert(future.get() == 100);
//
-// 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); }
-
- T get() && { return future.get(); }
-
- template <typename F, typename R = std::invoke_result_t<F, T>>
- auto then(F&& op) && -> Chain<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>>) {
- return r;
- } else {
- return r.get();
- }
- },
- std::move(future), std::forward<F>(op));
- }
-
- std::future<T> future;
-};
-
-} // namespace details
-
-template <typename T>
-inline auto chain(std::future<T>&& f) -> details::Chain<T> {
- return std::move(f);
+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