- Why can't I understand what coroutines are? - 7 Updates
Mr Flibble <flibbleREMOVETHISBIT@i42.co.uk>: Aug 05 06:32PM +0100 On 05/08/2019 02:32, Sam wrote: > that all those supercomputers are just a bunch of machines with optimized > networking tying hundreds of thousands threads together. Yes, threads. > Gee whiz, how can that possibly happen? epoll(...), dear. /Flibble -- "Snakes didn't evolve, instead talking snakes with legs changed into snakes." - Rick C. Hodgin "You won't burn in hell. But be nice anyway." – Ricky Gervais "I see Atheists are fighting and killing each other again, over who doesn't believe in any God the most. Oh, no..wait.. that never happens." – Ricky Gervais "Suppose it's all true, and you walk up to the pearly gates, and are confronted by God," Bryne asked on his show The Meaning of Life. "What will Stephen Fry say to him, her, or it?" "I'd say, bone cancer in children? What's that about?" Fry replied. "How dare you? How dare you create a world to which there is such misery that is not our fault. It's not right, it's utterly, utterly evil." "Why should I respect a capricious, mean-minded, stupid God who creates a world that is so full of injustice and pain. That's what I would say." |
scott@slp53.sl.home (Scott Lurndal): Aug 05 05:48PM >> networking tying hundreds of thousands threads together. Yes, threads. >> Gee whiz, how can that possibly happen? >epoll(...), dear. How does epoll help with high volume disk I/O? |
Bart <bc@freeuk.com>: Aug 05 08:19PM +0100 On 05/08/2019 09:42, Juha Nieminen wrote: > Is there a simple example of a situation where this is significantly > beneficial compared to a lambda or an explicitly written functor > (ie. a class with member variables and an operator())? I don't get coroutines either. Since I have also implemented languages, I sometimes put in features I've heard of which sound neat, although that's often just for box-ticking as I then never use the feature again. But coroutines, I wouldn't even bother ticking a box for. However.... with 'generators', I do understand how they can be useful, and which are something I've been thinking about adding. And generators, I understand, can be implemented with coroutines. (Although I would just create whatever I can see will be necessary. If I end up inventing something like a coroutine, then that would be cool too, even if I couldn't tell you what they are.) Anyway, 'generators' might be one easy-to-understand application of 'coroutines'. |
Christian Gollwitzer <auriocus@gmx.de>: Aug 05 10:26PM +0200 Am 05.08.19 um 10:42 schrieb Juha Nieminen: > in such a manner that it continues from where it "returned" last time, > rather than always from the beginning (which would happen with lambdas). > Is that correct? Similar to my understanding of it. > Is there a simple example of a situation where this is significantly > beneficial compared to a lambda or an explicitly written functor > (ie. a class with member variables and an operator())? When you yield() from inside a loop. With coroutines, you can implement generators - a functor which returns a value from a set, one at a time - similar to iterators. You can write your code *as if* the yielding would be a simple function call. For example, consider a function which prints the time as hours:minutes from midnight to midnight: void printtime() { for (int hour=0; hour<24; hour++) { for (int min=0; min<60; min++) { std::cout<<hour<<":"<<min<<std::endl; } } } Now you want a functor that returns the formatted string, one at a time, for each call. Coroutine solution:* void printtime() { for (int hour=0; hour<24; hour++) { for (int min=0; min<60; min++) { std::ostringstream os; os<<hour<<":"<<min; co_yield(os.str()); } } } The only significant change is print -> yield. If you had to write this thing manually, you would need to unroll the loops and correctly switch the state when the minutes surpass 60. With two for loops, that is still practically doable, albeit inconvenient - but now consider to implement an iterator for breadth-first traversal of a tree. Trivial algorithm for printing: void bftraverse(Tree* root) { std::cout<<root->content; if (root->left) bftraverse(root->left); if (root->right) bftraverse(root->right); } Now please unroll this into a functor which produces one value at a time. Good luck getting this right at the first time, basically that would be reinventing an iterative algorithm from TAOCP. Coroutine solution: void bftraverse(Tree* root) { co_yield(root->content); if (root->left) bftraverse(root->left); if (root->right) bftraverse(root->right); } voilá. If you're patient, search for videos from Gor Nishanov, he has provided some examples in C++ (tree traversal is one of them). Christian (*) I haven't tried C++ coroutines, so the syntax may be off. I'm using them in Python and Tcl only |
"Alf P. Steinbach" <alf.p.steinbach+usenet@gmail.com>: Aug 05 11:09PM +0200 On 05.08.2019 22:26, Christian Gollwitzer wrote: > if (root->right) bftraverse(root->right); > } > voilá. As I understand it this generates a new coroutine instance, with dynamically allocated state, for each call. It needs to do that (modulo optimization) to represent the call stack for the recursive calls. Thus it's convenient, but possibly/probably inefficient. > Christian > (*) I haven't tried C++ coroutines, so the syntax may be off. I'm using > them in Python and Tcl only Same for me, except I'm only using my head. That's not intelligent, because the C++20 coroutines are modeled on the Visual C++ coroutines, except they've changed the keywords to more unreadable and tried to make the documentation like impenetrable machine code. I wish I was more intelligent, at least enough to just use the VC compiler I have... Anyway, to avoid the dynamic allocation inefficiency I'd probably do something like this: auto bftraverse( Node* root ) -> optional<Data> { stack<Node*> nodes; nodes.push( root ); while( not nodes.empty() ) { const auto p = nodes.top(); nodes.pop(); if( p == nullptr ) { co_return {}; } co_yield p->data; if( p->right ) { nodes.push( p->right ); } if( p>left ) { nodes.push( p->left ); } } } Except that from cppreference's page it seems that the trailing return type syntax isn't supported for coroutines. If so then that's an inconsistency that will probably be fixed later. I guess that at some point someone is going to publish a guideline, "Recursive coroutines considered harmful". For efficiency. Then someone else is going to counter that with ""Recursive coroutines considered harmful" considered harmful", for convenience and development time. And then there will maybe be some discussion. In forums of the mainly willfully ignorant those who post either naturally recursive or unnaturally iterative coroutines will sometimes get blasts of anonymous downvotes. For not following the perceived best practice. And so on. :) Cheers!, - Alf |
"Chris M. Thomasson" <invalid_chris_thomasson_invalid@invalid.com>: Aug 05 02:34PM -0700 On 8/4/2019 11:36 PM, Paavo Helde wrote: > creation is a pretty heavyweight operation. So it's strange that IOCP is > mentioned as an example of "Threads can work fairly well with IO", it's > rather the opposite, at least on Windows. Yeah. IOCP is the way to do things on Windows wrt creating scaleable servers. I am fond of the following function: https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile It works well with IOCP. Also, https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_transmitpackets There is a neat way to wait on IOCP that can dequeue several events in one shot: https://docs.microsoft.com/en-us/windows/win32/fileio/getqueuedcompletionstatusex-func Iirc, certain Windows os's limited the number of TransmitFile functions to a max of two concurrent inflight calls at any time. The server versions of the OS would allow for as many as the system could handle at a time. |
"Chris M. Thomasson" <invalid_chris_thomasson_invalid@invalid.com>: Aug 05 02:44PM -0700 On 8/5/2019 2:51 AM, Martijn van Buul wrote: > a threaded solution would offer no parallelisation, so using threads here > only serves to simplify the implementation of the consumer or producer - > performancewise it's detrimental. [...] Actually, single producer, single consumer queues are pretty nice. The consumer thread can work on things without bothering the procuder thread, and vise versa. The sync can be implemented without using any atomic RMW. |
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment