|
| 1 | +<?xml version='1.0' encoding='utf-8' standalone='no'?> |
| 2 | +<!DOCTYPE issue SYSTEM "lwg-issue.dtd"> |
| 3 | + |
| 4 | +<issue num="4336" status="New"> |
| 5 | +<title><code>bulk</code> vs. <code>task_scheduler</code></title> |
| 6 | +<section><sref ref="[exec.task.scheduler]"/></section> |
| 7 | +<submitter>Dietmar Kühl</submitter> |
| 8 | +<date>31 Aug 2025</date> |
| 9 | +<priority>99</priority> |
| 10 | + |
| 11 | +<discussion> |
| 12 | +<p> |
| 13 | +Normally, the scheduler type used by an operation can be deduced |
| 14 | +when a sender is <code>connect</code>ed to a receiver from the |
| 15 | +receiver's environment. The body of a coroutine cannot know about |
| 16 | +the receiver the <code>task</code> sender gets <code>connect</code>ed |
| 17 | +to. The implication is that the type of the scheduler used by the |
| 18 | +coroutine needs to be known when the <code>task</code> is created. |
| 19 | +To still allow custom schedulers used when connecting, the type-erased |
| 20 | +scheduler <code>task_scheduler</code> is used. However, that leads |
| 21 | +to surprises when algorithms are customised for a scheduler as is, |
| 22 | +e.g., the case for <code>bulk</code> when used with a |
| 23 | +<code>parallel_scheduler</code>: if <code>bulk</code> is |
| 24 | +<code>co_awaited</code> within a coroutine using |
| 25 | +<code>task_scheduler</code> it will use the default implementation |
| 26 | +of <code>bulk</code> which sequentially executes the work, even if |
| 27 | +the <code>task_scheduler</code> was initialised with a |
| 28 | +<code>parallel_scheduler</code> (the exact invocation may actually |
| 29 | +be slightly different or need to use <code>bulk_chunked</code> or |
| 30 | +<code>bulk_unchunked</code> but that isn't the point being made): |
| 31 | +</p> |
| 32 | + |
| 33 | +<pre> |
| 34 | +struct env { |
| 35 | + auto query(ex::get_scheduler_t) const noexcept { return ex::parallel_scheduler(); } |
| 36 | +}; |
| 37 | +struct work { |
| 38 | + auto operator()(std::size_t s){ /*...*/ }; |
| 39 | +}; |
| 40 | + |
| 41 | +ex::sync_wait( |
| 42 | + ex::write_env(ex::bulk(ex::just(), 16u, work{}), |
| 43 | + env{} |
| 44 | +)); |
| 45 | +ex::sync_wait(ex::write_env( |
| 46 | + []()->ex::task<void, ex::env<>>>{ co_await ex::bulk(ex::just(), 16u, work{}); }(), |
| 47 | + env{} |
| 48 | +)); |
| 49 | +</pre> |
| 50 | + |
| 51 | +<p> |
| 52 | +The two invocations should probably both execute the work in parallel |
| 53 | +but the coroutine version doesnt: it uses the <code>task_scheduler</code> |
| 54 | +which doesnt have a specialised version of <code>bulk</code> to |
| 55 | +potentially delegate in a type-erased form to the underlying |
| 56 | +scheduler. It is straight forward to move the <code>write_env</code> |
| 57 | +wrapper inside the coroutine which fixes the problem in this case |
| 58 | +but this need introduces the potential for a subtle performance |
| 59 | +bug. The problem is sadly not limited to a particular scheduler or |
| 60 | +a particular algorithm: any scheduler/algorithm combination which |
| 61 | +may get specialised can suffer from the specialised algorithm not |
| 62 | +being picked up. |
| 63 | +</p> |
| 64 | + |
| 65 | +<p> |
| 66 | +There are a few ways this problem can be addressed (this list of |
| 67 | +options is almost certainly incomplete): |
| 68 | +</p> |
| 69 | + |
| 70 | +<ul> |
| 71 | +<li>Accept the situation as is and advise users to be careful about |
| 72 | +customised algorithms like bulk when using |
| 73 | +<code>task_scheduler</code>.</li> |
| 74 | +<li>Extend the interface of <code>task_scheduler</code> to deal |
| 75 | +with a set of algorithms for which it provides a type-erased |
| 76 | +interface. The interface would likely be more constrained and it |
| 77 | +would use virtual dispatch at run-time. However, the set of covered |
| 78 | +algorithms would necessarily be limited in some form.</li> |
| 79 | +<li>To avoid the trap, make the use of known algorithms incompatible |
| 80 | +with the use of <code>task_scheduler</code>, i.e., customise these |
| 81 | +algorithms for <code>task_scheduler</code> such that a compile-time |
| 82 | +error is produced.</li> |
| 83 | +</ul> |
| 84 | +<p> |
| 85 | +A user who knows that the main purpose of a coroutine is to executed |
| 86 | +an algorithm customised for a certain scheduler can use <code>task<T, |
| 87 | +E></code> with an environment <code>E</code> specifying exactly |
| 88 | +that scheduler type. However, this use may be nested within some |
| 89 | +sender being <code>co_awaited</code> and users need to be aware |
| 90 | +that the customisation wouldnt be picked up. Any approach I'm |
| 91 | +currently aware of will have the problem that customised versions |
| 92 | +of an algorithm are not used for algorithms we are currently unaware |
| 93 | +of. |
| 94 | +</p> |
| 95 | +</discussion> |
| 96 | + |
| 97 | +<resolution> |
| 98 | +<p> |
| 99 | +</p> |
| 100 | +</resolution> |
| 101 | + |
| 102 | +</issue> |
0 commit comments