Home DEVELOPER C++ Programming Language: Creating Station with std::execute

C++ Programming Language: Creating Station with std::execute

0


Follow my explanations about running inclusive scans asynchronously std::execution I am now dedicating myself to composing stations.

Advertisement







Rainer Grimm has been working as a software architect, team and training leader for many years. He enjoys writing articles on the programming languages ​​C++, Python, and Haskell, but also frequently speaks at expert conferences. On his blog Modern C++ he discusses his passion C++ in depth.

I’ll start with a simple example of composition using the pipe operator. instead of nested function calls

call1(call2(input))

can alternatively be written

call1 | call2(input)

or even:

input | call1 | call2

This example is of course very simple. Let’s make it a little more complicated. Proposal P2300R10 Calling a nested function is compared to calling a function using a temporary object and calling composition using the pipe operator.

Compute all three function compositions 610 = (123*5)-5 Using thread pools and CUDA. Pay special attention to the lambda in the following code examples (){ return 123; })This lambda has not been evaluated.

auto snd = execution::then(
             execution::continues_on(
               execution::then(
                 execution::continues_on(
                   execution::then(
                     execution::schedule(thread_pool.scheduler())
                     (){ return 123; }),
                   cuda::new_stream_scheduler()),
                 ()(int i){ return 123 * 5; }),
               thread_pool.scheduler()),
             ()(int i){ return i - 5; });
auto (result) = this_thread::sync_wait(snd).value();
// result == 610

It is not easy to understand this nesting of function calls and see which function bodies belong together, or to understand why the lambda is not executing. Debugging or changing this creation is also no fun.

auto snd0 = execution::schedule(thread_pool.scheduler());
auto snd1 = execution::then(snd0, (){ return 123; });
auto snd2 = execution::continues_on(snd1, cuda::new_stream_scheduler());
auto snd3 = execution::then(snd2, ()(int i){ return 123 * 5; })
auto snd4 = execution::continues_on(snd3, thread_pool.scheduler())
auto snd5 = execution::then(snd4, ()(int i){ return i - 5; });
auto (result) = *this_thread::sync_wait(snd4);
// result == 610

The use of temporal variables can be very helpful in understanding the structure of a composition. Now it is easy to see the sequence of function calls. It also becomes clear why lambda functions work (){ return 123; } is not executed. There is no such channel subscriber this_thread::sync_wait(snd4)Like transmitter adapter then And continue_on Are “lazy”. They create value only when asked to do so.

From a readability point of view, I like this solution, but it has one serious drawback: it creates a lot of temporary objects.

auto snd = execution::schedule(thread_pool.scheduler())
         | execution::then((){ return 123; })
         | execution::continues_on(cuda::new_stream_scheduler())
         | execution::then(()(int i){ return 123 * 5; })
         | execution:: Double quote continues_on(thread_pool.scheduler())
         | execution::then(()(int i){ return i - 5; });
auto (result) = this_thread::sync_wait(snd).value();
// result == 610

Function composition with the pipe operator solves both problems. Firstly, it is readable and secondly, does not require any unnecessary temporary variables.

The following transmitter adapters are not Pipe-enabled.

  • execution::when_all And execution::when_all_with_variant: Both transmitter adapters accept any number of transmitters. Therefore it will not be clear which channel should be optimized.
  • execution::starts_on: This dispatcher adapter changes the execution resource on which the dispatcher runs. It does not adjust the channel.

For functional structure, it is important to make the layout readable. So don’t make this a “smart” one-liner:

auto snd = execution::schedule(thread_pool.scheduler()) | execution::then((){ return 123; }) | execution::continues_on(cuda::new_stream_scheduler()) | execution::then(()(int i){ return 123 * 5; }) | execution::continues_on(thread_pool.scheduler()) | execution::then(()(int i){ return i - 5; }); 

std::execution Some transmitter factories, typically multiple transmitters, provide a way to model the workflow. I will introduce them in my next post.


(map)

NO COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Exit mobile version