Commit 594ff7a5 authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

add pool_executor in pushmi/examples/pool.h, use it when updating the pushmi...

add pool_executor in pushmi/examples/pool.h, use it when updating the pushmi benchmark and migrating it to Folly.Benchmark

Summary: The PushmiBenchmark was badly bit-rotted. Update it for the latest pushmi design, migrate it to Folly.Benchmark, and implement it in terms of a new pushmi::pool_executor that is a thin wrapper over a pointer to a folly::CPUThreadPoolExecutor.

Reviewed By: kirkshoop

Differential Revision: D14550589

fbshipit-source-id: d808d6f5795ead421e6ee88439adb9ee652d3146
parent 79f11efb
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <numeric>
#include <thread>
#include <vector> #include <vector>
#include <folly/experimental/pushmi/o/defer.h> #include <folly/experimental/pushmi/o/defer.h>
...@@ -32,7 +35,11 @@ ...@@ -32,7 +35,11 @@
#include <folly/experimental/pushmi/entangle.h> #include <folly/experimental/pushmi/entangle.h>
#include <folly/experimental/pushmi/receiver.h> #include <folly/experimental/pushmi/receiver.h>
#include <folly/experimental/pushmi/pool.h> #include <folly/experimental/pushmi/examples/pool.h>
#include <folly/Benchmark.h>
#include <folly/Optional.h>
#include <folly/container/Foreach.h>
using namespace folly::pushmi::aliases; using namespace folly::pushmi::aliases;
...@@ -47,20 +54,18 @@ struct countdown { ...@@ -47,20 +54,18 @@ struct countdown {
template <class ExecutorRef> template <class ExecutorRef>
void value(ExecutorRef exec); void value(ExecutorRef exec);
template <class E> template <class E>
void error(E e) { void error(E) {
std::terminate(); std::terminate();
} }
void done() {} void done() {}
PUSHMI_TEMPLATE(class Up) PUSHMI_TEMPLATE(class Up)
(requires mi::Invocable< (requires mi::ReceiveValue<Up, std::ptrdiff_t>) //
decltype(mi::set_value), void starting(Up up) {
Up,
std::ptrdiff_t>)void starting(Up up) {
mi::set_value(up, 1); mi::set_value(up, 1);
} }
PUSHMI_TEMPLATE(class Up) PUSHMI_TEMPLATE(class Up)
(requires mi::True<>&& mi:: (requires mi::ReceiveValue<Up>&& (!mi::ReceiveValue<Up, std::ptrdiff_t>)) //
Invocable<decltype(mi::set_value), Up>)void starting(Up up) volatile { void starting(Up up) {
mi::set_value(up); mi::set_value(up);
} }
}; };
...@@ -69,7 +74,7 @@ template <class R> ...@@ -69,7 +74,7 @@ template <class R>
template <class ExecutorRef> template <class ExecutorRef>
void countdown<R>::value(ExecutorRef exec) { void countdown<R>::value(ExecutorRef exec) {
if (--*counter >= 0) { if (--*counter >= 0) {
exec | op::submit(R{}(*this)); exec.schedule() | op::submit(R{}(*this));
} }
} }
...@@ -80,37 +85,55 @@ using countdownflowmany = countdown<decltype(mi::make_flow_receiver)>; ...@@ -80,37 +85,55 @@ using countdownflowmany = countdown<decltype(mi::make_flow_receiver)>;
struct inline_time_executor { struct inline_time_executor {
using properties = mi::property_set< using properties = mi::property_set<
mi::is_time<>,
mi::is_executor<>, mi::is_executor<>,
mi::is_fifo_sequence<>, mi::is_time<>,
mi::is_always_blocking<>, mi::is_fifo_sequence<>>;
mi::is_single<>>;
struct task {
std::chrono::system_clock::time_point at;
using properties = mi::property_set<
mi::is_sender<>,
mi::is_always_blocking<>,
mi::is_single<>>;
template <class Out>
void submit(Out out) {
std::this_thread::sleep_until(at);
::mi::set_value(out, inline_time_executor{});
::mi::set_done(out);
}
};
std::chrono::system_clock::time_point top() { std::chrono::system_clock::time_point top() {
return std::chrono::system_clock::now(); return std::chrono::system_clock::now();
} }
auto executor() { auto schedule(std::chrono::system_clock::time_point at) {
return *this; return task{at};
} }
template <class Out> auto schedule() {
void submit(std::chrono::system_clock::time_point at, Out out) { return schedule(top());
std::this_thread::sleep_until(at);
::mi::set_value(out, *this);
} }
}; };
struct inline_executor { struct inline_executor {
using properties = mi::property_set< using properties = mi::property_set<
mi::is_sender<>, mi::is_executor<>,
mi::is_fifo_sequence<>, mi::is_fifo_sequence<>>;
mi::is_always_blocking<>,
mi::is_single<>>; struct task {
auto executor() { using properties = mi::property_set<
return inline_time_executor{}; mi::is_sender<>,
} mi::is_always_blocking<>,
template <class Out> mi::is_single<>>;
void submit(Out out) {
::mi::set_value(out, *this); template <class Out>
void submit(Out out) {
::mi::set_value(out, inline_executor{});
::mi::set_done(out);
}
};
auto schedule() {
return task{};
} }
}; };
...@@ -119,45 +142,55 @@ struct inline_executor_flow_single { ...@@ -119,45 +142,55 @@ struct inline_executor_flow_single {
CancellationFactory cf; CancellationFactory cf;
using properties = mi::property_set< using properties = mi::property_set<
mi::is_sender<>, mi::is_executor<>,
mi::is_flow<>, mi::is_fifo_sequence<>>;
mi::is_fifo_sequence<>,
mi::is_maybe_blocking<>, struct task {
mi::is_single<>>; CancellationFactory cf;
auto executor() {
return inline_time_executor{}; using properties = mi::property_set<
} mi::is_sender<>,
template <class Out> mi::is_flow<>,
void submit(Out out) { mi::is_maybe_blocking<>,
auto tokens = cf(); mi::is_single<>>;
using Stopper = decltype(tokens.second); template <class Out>
struct Data : mi::receiver<> { void submit(Out out) {
explicit Data(Stopper stopper) : stopper(std::move(stopper)) {} auto tokens = cf();
Stopper stopper;
}; using Stopper = decltype(tokens.second);
auto up = mi::MAKE(receiver)( struct Data : mi::receiver<> {
Data{std::move(tokens.second)}, explicit Data(Stopper stopper) : stopper(std::move(stopper)) {}
[](auto& data) {}, Stopper stopper;
[](auto& data, auto e) noexcept { };
auto both = lock_both(data.stopper); auto up = mi::make_receiver(
(*(both.first))(both.second); Data{std::move(tokens.second)},
}, [](auto&) {},
[](auto& data) { [](auto& data, auto) noexcept {
auto both = lock_both(data.stopper); auto both = lock_both(data.stopper);
(*(both.first))(both.second); (*(both.first))(both.second);
}); },
[](auto& data) {
// pass reference for cancellation. auto both = lock_both(data.stopper);
::mi::set_starting(out, std::move(up)); (*(both.first))(both.second);
});
auto both = lock_both(tokens.first);
if (!!both.first && !*(both.first)) { // pass reference for cancellation.
::mi::set_value(out, *this); ::mi::set_starting(out, std::move(up));
} else {
// cancellation is not an error auto both = lock_both(tokens.first);
::mi::set_done(out); if (!!both.first && !*(both.first)) {
::mi::set_value(out, inline_executor_flow_single{cf});
::mi::set_done(out);
} else {
// cancellation is not an error
::mi::set_done(out);
}
} }
};
auto schedule() {
return task{cf};
} }
}; };
...@@ -193,402 +226,445 @@ using inline_executor_flow_single_entangled = ...@@ -193,402 +226,445 @@ using inline_executor_flow_single_entangled =
struct inline_executor_flow_single_ignore { struct inline_executor_flow_single_ignore {
using properties = mi::property_set< using properties = mi::property_set<
mi::is_sender<>, mi::is_executor<>,
mi::is_flow<>, mi::is_fifo_sequence<>>;
mi::is_fifo_sequence<>,
mi::is_maybe_blocking<>, struct task {
mi::is_single<>>; using properties = mi::property_set<
auto executor() { mi::is_sender<>,
return inline_time_executor{}; mi::is_flow<>,
} mi::is_maybe_blocking<>,
template <class Out> mi::is_single<>>;
void submit(Out out) {
// pass reference for cancellation. template <class Out>
::mi::set_starting(out, mi::receiver<>{}); void submit(Out out) {
// pass reference for cancellation.
::mi::set_starting(out, mi::receiver<>{});
::mi::set_value(out, inline_executor_flow_single_ignore{});
::mi::set_done(out);
}
};
::mi::set_value(out, *this); auto schedule() {
return task{};
} }
}; };
struct inline_executor_flow_many { struct inline_executor_flow_many {
inline_executor_flow_many() : counter(nullptr) {} inline_executor_flow_many() = default;
inline_executor_flow_many(std::atomic<int>& c) : counter(&c) {} inline_executor_flow_many(std::atomic<int>& c) : counter(&c) {}
std::atomic<int>* counter; std::atomic<int>* counter = nullptr;
using properties = mi::property_set< using properties = mi::property_set<
mi::is_sender<>, mi::is_executor<>,
mi::is_flow<>, mi::is_fifo_sequence<>>;
mi::is_fifo_sequence<>,
mi::is_maybe_blocking<>, struct task {
mi::is_many<>>; std::atomic<int>* counter = nullptr;
auto executor() { using properties = mi::property_set<
return inline_time_executor{}; mi::is_sender<>,
} mi::is_flow<>,
template <class Out> mi::is_maybe_blocking<>,
void submit(Out out) { mi::is_many<>>;
// boolean cancellation
struct producer { template <class Out>
producer(Out out, bool s) : out(std::move(out)), stop(s) {} void submit(Out out) {
Out out; // boolean cancellation
std::atomic<bool> stop; struct producer {
}; producer(Out out, bool s) : out(std::move(out)), stop(s) {}
auto p = std::make_shared<producer>(std::move(out), false); Out out;
std::atomic<bool> stop;
};
auto p = std::make_shared<producer>(std::move(out), false);
struct Data : mi::receiver<> {
explicit Data(std::shared_ptr<producer> p) : p(std::move(p)) {}
std::shared_ptr<producer> p;
};
auto up = mi::make_receiver(
Data{p},
[counter = this->counter](auto& data, auto requested) {
if (requested < 1) {
return;
}
// this is re-entrant
while (!data.p->stop && --requested >= 0 &&
(!counter || --*counter >= 0)) {
::mi::set_value(
data.p->out,
!!counter ? inline_executor_flow_many{*counter}
: inline_executor_flow_many{});
}
if (!counter || *counter == 0) {
::mi::set_done(data.p->out);
}
},
[](auto& data, auto) noexcept {
data.p->stop.store(true);
::mi::set_done(data.p->out);
},
[](auto& data) {
data.p->stop.store(true);
::mi::set_done(data.p->out);
});
struct Data : mi::receiver<> { // pass reference for cancellation.
explicit Data(std::shared_ptr<producer> p) : p(std::move(p)) {} ::mi::set_starting(p->out, std::move(up));
std::shared_ptr<producer> p; }
}; };
auto up = mi::MAKE(receiver)( auto schedule() {
Data{p}, return task{counter};
[counter = this->counter](auto& data, auto requested) {
if (requested < 1) {
return;
}
// this is re-entrant
while (!data.p->stop && --requested >= 0 &&
(!counter || --*counter >= 0)) {
::mi::set_value(
data.p->out,
!!counter ? inline_executor_flow_many{*counter}
: inline_executor_flow_many{});
}
if (!counter || *counter == 0) {
::mi::set_done(data.p->out);
}
},
[](auto& data, auto e) noexcept {
data.p->stop.store(true);
::mi::set_done(data.p->out);
},
[](auto& data) {
data.p->stop.store(true);
::mi::set_done(data.p->out);
});
// pass reference for cancellation.
::mi::set_starting(p->out, std::move(up));
} }
}; };
struct inline_executor_flow_many_ignore { struct inline_executor_flow_many_ignore {
using properties = mi::property_set< using properties = mi::property_set<
mi::is_sender<>, mi::is_executor<>,
mi::is_flow<>, mi::is_fifo_sequence<>>;
mi::is_fifo_sequence<>,
mi::is_always_blocking<>, struct task {
mi::is_many<>>; using properties = mi::property_set<
auto executor() { mi::is_sender<>,
return inline_time_executor{}; mi::is_flow<>,
} mi::is_always_blocking<>,
template <class Out> mi::is_many<>>;
void submit(Out out) { template <class Out>
// pass reference for cancellation. void submit(Out out) {
::mi::set_starting(out, mi::receiver<>{}); // pass reference for cancellation.
::mi::set_starting(out, mi::receiver<>{});
::mi::set_value(out, *this); ::mi::set_value(out, inline_executor_flow_many_ignore{});
::mi::set_done(out);
}
};
::mi::set_done(out); auto schedule() {
return task{};
} }
}; };
struct inline_executor_many { struct inline_executor_many {
using properties = mi::property_set< using properties = mi::property_set<
mi::is_sender<>, mi::is_executor<>,
mi::is_fifo_sequence<>, mi::is_fifo_sequence<>>;
mi::is_always_blocking<>,
mi::is_many<>>; struct task {
auto executor() { using properties = mi::property_set<
return inline_time_executor{}; mi::is_sender<>,
} mi::is_always_blocking<>,
template <class Out> mi::is_many<>>;
void submit(Out out) { template <class Out>
::mi::set_value(out, *this); void submit(Out out) {
::mi::set_done(out); ::mi::set_value(out, inline_executor_many{});
::mi::set_done(out);
}
};
auto schedule() {
return task{};
} }
}; };
#define concept Concept BENCHMARK(ready_1000_single_get_submit) {
#include <nonius/nonius.h++>
NONIUS_BENCHMARK("ready 1'000 single get (submit)", [](nonius::chronometer meter){
int counter{0}; int counter{0};
meter.measure([&]{ counter = 1'000;
counter = 1'000; std::atomic<int> result{0};
while (--counter >=0) { while (--counter >=0) {
auto fortyTwo = op::just(42) | op::get<int>; result += op::just(42) | op::get<int>;
} }
return counter; (void) result.load();
}); }
})
NONIUS_BENCHMARK("ready 1'000 single get (blocking_submit)", [](nonius::chronometer meter){ BENCHMARK(ready_1000_single_get_blocking_submit) {
int counter{0}; int counter{0};
meter.measure([&]{ counter = 1'000;
counter = 1'000; std::atomic<int> result{0};
while (--counter >=0) { while (--counter >=0) {
auto fortyTwo = mi::make_single_sender([](auto out){ mi::set_value(out, 42); mi::set_done(out);}) | op::get<int>; result += mi::make_single_sender(
} [](auto out){
return counter; mi::set_value(out, 42);
}); mi::set_done(out);
}) })
| op::get<int>;
}
(void) result.load();
}
NONIUS_BENCHMARK("inline 1'000 single", [](nonius::chronometer meter){ BENCHMARK(inline_1000_single, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor{}; auto ie = inline_executor{};
using IE = decltype(ie); using IE = decltype(ie);
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_receiver(single)); ie.schedule() | op::submit(mi::make_receiver(single));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1'000 time single", [](nonius::chronometer meter){ BENCHMARK(inline_1000_time_single, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_time_executor{}; auto ie = inline_time_executor{};
using IE = decltype(ie); using IE = decltype(ie);
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_receiver(single)); ie.schedule() | op::submit(mi::make_receiver(single));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1'000 many", [](nonius::chronometer meter){ BENCHMARK(inline_1000_many, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_many{}; auto ie = inline_executor_many{};
using IE = decltype(ie); using IE = decltype(ie);
countdownmany many{counter}; countdownmany many{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_receiver(many)); ie.schedule() | op::submit(mi::make_receiver(many));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1'000 flow_single shared", [](nonius::chronometer meter){ BENCHMARK(inline_1000_flow_single_shared, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_flow_single_shared{}; auto ie = inline_executor_flow_single_shared{};
using IE = decltype(ie); using IE = decltype(ie);
countdownflowsingle flowsingle{counter}; countdownflowsingle flowsingle{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_flow_receiver(flowsingle)); ie.schedule() | op::submit(mi::make_flow_receiver(flowsingle));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1'000 flow_single entangle", [](nonius::chronometer meter){ BENCHMARK(inline_1000_flow_single_entangle, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_flow_single_entangled{}; auto ie = inline_executor_flow_single_entangled{};
using IE = decltype(ie); using IE = decltype(ie);
countdownflowsingle flowsingle{counter}; countdownflowsingle flowsingle{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_flow_receiver(flowsingle)); ie.schedule() | op::submit(mi::make_flow_receiver(flowsingle));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1'000 flow_single ignore cancellation", [](nonius::chronometer meter){ BENCHMARK(inline_1000_flow_single_ignore_cancellation, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_flow_single_ignore{}; auto ie = inline_executor_flow_single_ignore{};
using IE = decltype(ie); using IE = decltype(ie);
countdownflowsingle flowsingle{counter}; countdownflowsingle flowsingle{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_flow_receiver(flowsingle)); ie.schedule() | op::submit(mi::make_flow_receiver(flowsingle));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1'000 flow_many", [](nonius::chronometer meter){ BENCHMARK(inline_1000_flow_many, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_flow_many{}; auto ie = inline_executor_flow_many{};
using IE = decltype(ie); using IE = decltype(ie);
countdownflowmany flowmany{counter}; countdownflowmany flowmany{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_flow_receiver(flowmany)); ie.schedule() | op::submit(mi::make_flow_receiver(flowmany));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1 flow_many with 1'000 values pull 1", [](nonius::chronometer meter){ BENCHMARK(inline_1_flow_many_with_1000_values_pull_1, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_flow_many{counter}; auto ie = inline_executor_flow_many{counter};
using IE = decltype(ie); using IE = decltype(ie);
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::for_each(mi::make_receiver()); ie.schedule() | op::for_each(mi::make_receiver());
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1 flow_many with 1'000 values pull 1'000", [](nonius::chronometer meter){ BENCHMARK(inline_1_flow_many_with_1000_values_pull_1000, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_flow_many{counter}; auto ie = inline_executor_flow_many{counter};
using IE = decltype(ie); using IE = decltype(ie);
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_flow_receiver(mi::ignoreNF{}, mi::abortEF{}, mi::ignoreDF{}, [](auto up){ ie.schedule()
mi::set_value(up, 1'000); | op::submit(
})); mi::make_flow_receiver(
mi::ignoreNF{},
mi::abortEF{},
mi::ignoreDF{},
[](auto up){
mi::set_value(up, 1'000);
}
)
);
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("inline 1'000 flow_many ignore cancellation", [](nonius::chronometer meter){ BENCHMARK(inline_1000_flow_many_ignore_cancellation, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto ie = inline_executor_flow_many_ignore{}; auto ie = inline_executor_flow_many_ignore{};
using IE = decltype(ie); using IE = decltype(ie);
countdownflowmany flowmany{counter}; countdownflowmany flowmany{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
ie | op::submit(mi::make_flow_receiver(flowmany)); ie.schedule() | op::submit(mi::make_flow_receiver(flowmany));
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("trampoline 1'000 single get (blocking_submit)", [](nonius::chronometer meter){ BENCHMARK(trampoline_1000_single_get_blocking_submit, n) {
int counter{0}; int counter{0};
auto tr = mi::trampoline(); auto tr = mi::trampoline();
using TR = decltype(tr); using TR = decltype(tr);
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter = 1'000; counter = 1'000;
while (--counter >=0) { while (--counter >= 0) {
auto fortyTwo = tr | op::transform([](auto){return 42;}) | op::get<int>; auto fortyTwo = tr.schedule()
| op::transform([](auto){return 42;})
| op::get<int>;
(void) fortyTwo;
} }
return counter; (void) counter;
}); }
}) }
NONIUS_BENCHMARK("trampoline static derecursion 1'000", [](nonius::chronometer meter){ BENCHMARK(trampoline_static_derecursion_1000, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto tr = mi::trampoline(); auto tr = mi::trampoline();
using TR = decltype(tr); using TR = decltype(tr);
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
tr | op::submit(single); tr.schedule() | op::submit(single);
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("trampoline virtual derecursion 1'000", [](nonius::chronometer meter){ BENCHMARK(trampoline_virtual_derecursion_1000, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto tr = mi::trampoline(); auto tr = mi::trampoline();
using TR = decltype(tr); using TR = decltype(tr);
auto single = countdownsingle{counter}; auto single = countdownsingle{counter};
std::function<void(mi::any_executor_ref<>)> recurse{ std::function<void(mi::any_executor_ref<>)> recurse{
[&](auto exec) { ::folly::pushmi::set_value(single, exec); }}; [&](auto exec) { mi::set_value(single, exec); }};
meter.measure([&]{
FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
tr | op::submit([&](auto exec) { recurse(exec); }); tr.schedule() | op::submit([&](auto exec) { recurse(exec); });
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("trampoline flow_many_sender 1'000", [](nonius::chronometer meter){ BENCHMARK(trampoline_flow_many_sender_1000, n) {
std::atomic<int> counter{0}; std::atomic<int> counter{0};
auto tr = mi::trampoline(); auto tr = mi::trampoline();
using TR = decltype(tr); using TR = decltype(tr);
std::vector<int> values(1'000); std::vector<int> values(1'000);
std::iota(values.begin(), values.end(), 1); BENCHMARK_SUSPEND {
std::iota(values.begin(), values.end(), 1);
}
auto f = op::flow_from(values, tr) | op::tap([&](int){ auto f = op::flow_from(values, tr) | op::tap([&](int){
--counter; --counter;
}); });
meter.measure([&]{ FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
f | op::for_each(mi::make_receiver()); f | op::for_each(mi::make_receiver());
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("pool{1} submit 1'000", [](nonius::chronometer meter){ BENCHMARK(pool_1_submit_1000, n) {
mi::pool pl{std::max(1u,std::thread::hardware_concurrency())}; folly::Optional<mi::pool> pl;
auto pe = pl.executor(); mi::pool_executor pe;
using PE = decltype(pe); BENCHMARK_SUSPEND {
pl.emplace(1u);
pe = pl->executor();
}
std::atomic<int> counter{0}; std::atomic<int> counter{0};
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{ FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
pe | op::submit(single); pe.schedule() | op::submit(single);
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("pool{hardware_concurrency} submit 1'000", [](nonius::chronometer meter){ BENCHMARK(pool_hardware_concurrency_submit_1000, n) {
mi::pool pl{std::min(1u,std::thread::hardware_concurrency())}; folly::Optional<mi::pool> pl;
auto pe = pl.executor(); mi::pool_executor pe;
using PE = decltype(pe); BENCHMARK_SUSPEND {
pl.emplace(std::max(1u, std::thread::hardware_concurrency()));
pe = pl->executor();
}
std::atomic<int> counter{0}; std::atomic<int> counter{0};
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{ FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
pe | op::submit(single); pe.schedule() | op::submit(single);
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("new thread submit 1'000", [](nonius::chronometer meter){ BENCHMARK(new_thread_submit_1000, n) {
auto nt = mi::new_thread(); auto nt = mi::new_thread();
using NT = decltype(nt); using NT = decltype(nt);
std::atomic<int> counter{0}; std::atomic<int> counter{0};
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{ FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
nt | op::submit(single); nt.schedule() | op::submit(single);
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("new thread blocking_submit 1'000", [](nonius::chronometer meter){ BENCHMARK(new_thread_blocking_submit_1000, n) {
auto nt = mi::new_thread(); auto nt = mi::new_thread();
using NT = decltype(nt);
std::atomic<int> counter{0}; std::atomic<int> counter{0};
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{ FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
nt | op::blocking_submit(single); nt.schedule() | op::blocking_submit(single);
return counter.load(); }
}); }
})
NONIUS_BENCHMARK("new thread + time submit 1'000", [](nonius::chronometer meter){ BENCHMARK(new_thread_and_time_submit_1000, n) {
auto nt = mi::new_thread(); auto nt = mi::new_thread();
using NT = decltype(nt);
auto time = mi::time_source<>{}; auto time = mi::time_source<>{};
auto tnt = time.make(mi::systemNowF{}, [nt](){ return nt; })(); auto strands = time.make(mi::systemNowF{}, nt);
using TNT = decltype(tnt); auto tnt = mi::make_strand(strands);
std::atomic<int> counter{0}; std::atomic<int> counter{0};
countdownsingle single{counter}; countdownsingle single{counter};
meter.measure([&]{ FOR_EACH_RANGE (i, 0, n) {
counter.store(1'000); counter.store(1'000);
tnt | op::submit(single); tnt.schedule() | op::submit(single);
while(counter.load() > 0); while(counter.load() > 0);
return counter.load(); }
}); BENCHMARK_SUSPEND {
time.join(); time.join();
}) }
}
int main() {
folly::runBenchmarks();
}
/*
* Copyright 2018-present Facebook, Inc.
*
* 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
#include <folly/experimental/pushmi/properties.h>
#include <folly/experimental/pushmi/concepts.h>
#include <folly/Executor.h>
#include <folly/executors/CPUThreadPoolExecutor.h>
namespace folly {
namespace pushmi {
class pool;
class pool_executor {
struct task;
Executor::KeepAlive<CPUThreadPoolExecutor> exec_ {};
public:
using properties =
property_set<is_executor<>, is_concurrent_sequence<>>;
pool_executor() = default;
explicit pool_executor(pool &e);
task schedule();
};
struct pool_executor::task {
using properties =
property_set<
is_sender<>, is_never_blocking<>, is_single<>>;
explicit task(pool_executor e)
: pool_ex_(std::move(e))
{}
PUSHMI_TEMPLATE(class Out)
(requires ReceiveValue<Out, pool_executor&>)
void submit(Out out) && {
pool_ex_.exec_->add([e = pool_ex_, out = std::move(out)]() mutable {
set_value(out, e);
set_done(out);
});
}
private:
pool_executor pool_ex_;
};
class pool {
friend pool_executor;
CPUThreadPoolExecutor pool_;
public:
explicit pool(std::size_t threads) : pool_(threads) {}
auto executor() {
return pool_executor{*this};
}
void stop() {
pool_.stop();
}
void wait() {
pool_.join();
}
};
inline pool_executor::pool_executor(pool &e)
: exec_(Executor::getKeepAliveToken(e.pool_))
{}
inline pool_executor::task pool_executor::schedule() {
return task{*this};
}
} // namespace pushmi
} // namespace folly
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment