- "C++20 Coroutines" by Martin Bond - 5 Updates
- rational numbers - 19 Updates
- condvar-implementation - 1 Update
| Juha Nieminen <nospam@thanks.invalid>: Sep 22 05:22AM > Coroutines should have stayed in the microsoft world, not added to the C++ > standard. They're a solution looking for a problem. Coroutines do solve certain problems. It's just that very few people have ever heard of them, and they are, for some reason, generally very poorly explained and somewhat hard to understand. One of the major applications of coroutines is, essentially, being able to return from a function at any given point (ie. at any point in a function you can put a command to "return to the caller") and then the calling code can then tell the function to continue from where it left off. Coroutines help in, essentially, storing the entire state of the function, so that it can continue from that exact point, using the exact same state. Ie. execution can continue from that point forward as if the function hadn't been exited at all. Coroutines are compared to cooperative multitasking, and in this sense they indeed are very similar: In cooperative multitasking at certain points you can "yield" the execution to the OS, which then later can return the execution back to that yield point, which will then continue as if nothing had happened. The entire state of the process is automatically preserved so that it can continue normally. You *can* achieve the same effect as coroutines without them, but it requires you to manually create the data containers where all the necessary variables are stored so that execution can continue from the point where it left, and you have to manually create jump statements to wherever there are these yield points. (The more yield points there are in your "coroutine", the more conditional gotos are required.) One practical example of where a coroutine is useful is a library that decompresses compressed data from a file into a buffer of fixed size. Whenever the buffer gets full, execution is returned to the caller for it to consume the contents of the buffer, after which the execution is returned to the decompressor, which continues from where it left off. However, since the buffer can typically get full at several points in the decompressor code, even in the middle of eg. expanding a block of data, the decompressor needs to somehow store its exact state in order to know how to continue with the decompression once execution is resumed. There may be several different places in the decompression code where stuff is written to the buffer, and at any moment the buffer may get full. Coroutines can make this whole thing a lot simpler, as at any point you can just add a yield, with essentially no extra work to store the state of the decompressor. |
| HorseyWorsey@the_stables.com: Sep 22 09:26AM On Wed, 22 Sep 2021 05:22:59 -0000 (UTC) >requires you to manually create the data containers where all the >necessary variables are stored so that execution can continue from >the point where it left, and you have to manually create jump statements Isn't that the point of class methods and variables? Ie: Black boxing state. >to wherever there are these yield points. (The more yield points there >are in your "coroutine", the more conditional gotos are required.) Having multiple yield points sounds like asking for sphagetti code. And thats before you have to consider recursion in the co-routine and the effect of threading on it (I have no idea what effects they are but I suspect its messy). >any moment the buffer may get full. Coroutines can make this whole >thing a lot simpler, as at any point you can just add a yield, with >essentially no extra work to store the state of the decompressor. Hmm, not convinced thats any simpler than using object or global state. |
| "Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Sep 22 11:31AM +0200 On 22 Sep 2021 07:22, Juha Nieminen wrote: > Coroutines are compared to cooperative multitasking This is fast becoming the adopted terminology, but really it should be Continuations are compared to coroutines - Alf |
| Bonita Montero <Bonita.Montero@gmail.com>: Sep 22 04:40PM +0200 >> to wherever there are these yield points. (The more yield points there >> are in your "coroutine", the more conditional gotos are required.) > Having multiple yield points sounds like asking for sphagetti code. ... There is no easy solution. Have you considered the altetrnative of writing your own state-machine ? That's much worse. |
| HorseyWorsey@the_stables.com: Sep 22 03:01PM On Wed, 22 Sep 2021 16:40:31 +0200 >> Having multiple yield points sounds like asking for sphagetti code. ... >There is no easy solution. Have you considered the altetrnative >of writing your own state-machine ? That's much worse. I find state machines very clear and easy to follow as well as being explicit rather than having some stack in an unknown (from the programmers POV) state. |
| Juha Nieminen <nospam@thanks.invalid>: Sep 22 04:58AM > Having to overload "<<", as I assume you meant, as you seemed to imply > that was better/easier than doing whatever it was to obj to allow its > use in function syntax. So what's the alternative that you suggest? > complex object. > However, if the object is that complex, it will already be using > equivalent amounts of memory anyway. The string will be constructed *in addition* to whatever memory the object may be taking, and it will be constructed solely for the printing process, and then immediately destroyed. This even though there's no reason to construct such a string into RAM, as each element could just be printed to the output individually by the object, without having to construct any strings. |
| Juha Nieminen <nospam@thanks.invalid>: Sep 22 05:06AM > Now you're saying that we need more templates on top of that to make the > syntax palatable. > It's not surprising that people complain about C++ being slower to compile! So you want to trade runtime efficiency for compilation efficiency, essentially? > My print statements also come as the function-like sprint()/sfprint() > that just return a string anyway. It seems to me that you come from a world of programming language design that pays little to no attention to the efficiency of the resulting program. This is very typical of eg. scripting languages and many other similar languages (usually interpreted, sometimes compiled). Dynamically allocated strings and objects are liberally created at every turn, things are converted into strings and from strings all the time, and so on and so forth, with exactly zero regard to how costly that may or may not be. A rampant "as long as it works" mentality, with zero regard to efficiency. That's not C++ (nor C for that matter). If you don't like the design of the language, then don't use it. It's that simple. |
| David Brown <david.brown@hesbynett.no>: Sep 22 11:09AM +0200 On 21/09/2021 20:51, Keith Thompson wrote: >> "auto fred = std::cout;" will make "fred" a copy of the value of >> "std::cout". > No it won't. There's no assignment operator for std::basic_ostream. Sorry. I had assumed that Bart had compiled the code he posted. (I don't use iostreams in my small-systems embedded code, so I don't know all the details of code that I wouldn't write even if I did use them.) >> } > Right. And if you want to change fred later, you can use a pointer > (smart or otherwise). Yes. |
| HorseyWorsey@the_stables.com: Sep 22 09:21AM On Tue, 21 Sep 2021 16:21:16 GMT >Diffie, Helman, Rivest, Shamir, Adelman, Aho, Ullman, the exceptional Leslie >Lamport >and thousands of others. And many who weren't. Dennis Richie and Stroustrup both worked at Bell labs, Unix was created by AT&T, SQL by IBM, Java by Sun, Bill Gates was a Harvard dropout etc. |
| Bart <bc@freeuk.com>: Sep 22 10:51AM +0100 On 22/09/2021 06:06, Juha Nieminen wrote: > It seems to me that you come from a world of programming language design > that pays little to no attention to the efficiency of the resulting > program. Not at all. I used to write compilers and applications for 8-bit computers; I know how to be efficient! But I develop two languages, one for systems programming, one scripting. I understand when it is appropriate to use scripting techniques, and when it isn't. If you need to use sprintf() C, then that's when you might also consider using sprint() elsewhere. > allocated strings and objects are liberally created at every turn, things > are converted into strings and from strings all the time, and so on and > so forth, with exactly zero regard to how costly that may or may not be. No; obviously you don't know how they work. Because they are slower, you have even more regard for efficiency, and avoid gratuitous string operations. > A rampant "as long as it works" mentality, with zero regard to efficiency. > That's not C++ (nor C for that matter). If you don't like the design of > the language, then don't use it. It's that simple. But if you are going to use strings, since sometimes you will need to synthesise text files (eg. textual output of a compiler), have a look at this little test in C++ which stringifies the numbers from 1 to 10M into one string: #include <iostream> int main() { std::string s=""; char t[100]; for (int i=1; i<=10000000; ++i) { s += itoa(i,t,10); s += ' '; } std::cout << "S.size = " << s.size() << "\n"; } Compiled as g++ -O2, this runs in 1.3 seconds on my machine. My script language might take only twice as long, but is much simpler and quicker to write. |
| Bart <bc@freeuk.com>: Sep 22 11:26AM +0100 On 22/09/2021 05:58, Juha Nieminen wrote: >> that was better/easier than doing whatever it was to obj to allow its >> use in function syntax. > So what's the alternative that you suggest? Overloading whatever 'tostring' operations that are implicitly used when printing. > This even though there's no reason to construct such a string into RAM, > as each element could just be printed to the output individually by the > object, without having to construct any strings. OK, let's try it. Here's a program that writes 1 million lines of the same 3 variables: #include <iostream> #include <fstream> using namespace std; int main () { ofstream myfile; long long int a = 0x7FFFFFFFFFFFFFFF; double b = 3.14159265359; const char* c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; myfile.open ("output"); for (int i=0; i<1000000; ++i) { myfile << a << " " << b << " " << c << "\n"; } myfile.close(); return 0; } On my machine, that took 4 seconds. But when I tried my script language, where each element has to be converted to a string object before being printed, it took only 3 seconds: a := int64.max b := pi c := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" f:=createfile("output") to 1 million do println @f, a,b,c od closefile(f) But now look at this version, which makes use of that sprint() routine that you derided in another post; this one takes only 2.1 seconds: a := int64.max b := pi c := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" s ::= "" to 1 million do s +:= sprint(a,b,c,"\n") od writestrfile("output",s) (It seems that a sprintln() version would be useful to avoid that explicit "\n" argument, and make it even faster.) Perhaps you shouldn't write off these pesky scripting languages so quickly... [Timings shown for my language is for when the interpreter is transpiled to C and compiled with gcc-O3. That's only available with an older version. The new version doesn't have a C option and will be 30% slower until a C target is reinstated. C++ timings use g++-O2] |
| HorseyWorsey@the_stables.com: Sep 22 10:54AM On Wed, 22 Sep 2021 10:51:23 +0100 > } > std::cout << "S.size = " << s.size() << "\n"; > } You learn something new every day. I'd never heard of itoa(). Apparently its an ancient K&R function which didn't get included into the ANSI C standard. |
| Bart <bc@freeuk.com>: Sep 22 11:59AM +0100 >> } > You learn something new every day. I'd never heard of itoa(). Apparently its > an ancient K&R function which didn't get included into the ANSI C standard. So what would be the shiny new alternative in C++? I wanted to avoid sprintf which has extra overheads that would likely dominate the timing. |
| HorseyWorsey@the_stables.com: Sep 22 11:04AM On Wed, 22 Sep 2021 11:59:46 +0100 >> You learn something new every day. I'd never heard of itoa(). Apparently its >> an ancient K&R function which didn't get included into the ANSI C standard. >So what would be the shiny new alternative in C++? stringstream which has always struck me as horrible inefficient and I've never understood why std::string didn't have a built in number to string conversion. >I wanted to avoid sprintf which has extra overheads that would likely >dominate the timing. Probably fewer overheads than stringstream. However there's always strtol(). |
| Ian Collins <ian-news@hotmail.com>: Sep 22 11:13PM +1200 On 22/09/2021 22:26, Bart wrote: > return 0; > } > On my machine, that took 4 seconds. This will take roughly the time it takes to perform the file writing (0.4S on my machine). The time spend formatting the output is inconsequential in comparison. -- Ian. |
| "Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Sep 22 02:17PM +0200 On 22 Sep 2021 12:59, Bart wrote: > So what would be the shiny new alternative in C++? > I wanted to avoid sprintf which has extra overheads that would likely > dominate the timing. #include <stddef.h> // ptrdiff_t #include <stdio.h> // printf, for the final output using Size = ptrdiff_t; #include <charconv> // std::to_chars #include <limits> // std::numeric_limits #include <string> // std::string #include <stdexcept> // std::runtime_error using namespace std::string_literals; void append_to( std::string& s, const int v ) { static const int radix = 10; static const int max_int_digits = std::numeric_limits<int>::digits10 + 1; const Size original_size = s.size(); s.resize( original_size + max_int_digits + 1 ); // 1 for possible sign. using P_char = char*; const P_char buffer_start = s.data() + original_size; const P_char buffer_end = s.data() + s.size(); const std::to_chars_result tcr = std::to_chars( buffer_start, buffer_end, v, radix ); if( tcr.ec == std::errc() ) { s.resize( tcr.ptr - s.data() ); } else { throw std::runtime_error( ""s + __func__ + " - std::to_chars failed." ); } } auto main() -> int { const int n = 10'000'000; std::string text; text.reserve( 10*n ); // Avoid (most of the) repeated dynamic allocations. for( int i = 1; i <= n; ++i ) { append_to( text, i ); text += ' '; } printf( "text.size = %d\n", int( text.size() ) ); } Building and running it in Powershell (using that abomination b/c it's got timing commands, and too lazy to enable dev mode in this Windows): PS D:\temp> g++ -std=c++17 -O2 .\x.cpp PS D:\temp> (measure-command { .\a.exe | out-default }).TotalMilliseconds text.size = 78888897 264.2677 PS D:\temp> (measure-command { .\a.exe | out-default }).TotalMilliseconds text.size = 78888897 219.7058 PS D:\temp> (measure-command { .\a.exe | out-default }).TotalMilliseconds text.size = 78888897 204.7605 Apparently it runs faster each time. However, if anyone's particularly interested in that, or how to report processor time instead of wall clock time in Windows, then hey, you got a nice project to engage in. - Alf |
| Paavo Helde <myfirstname@osa.pri.ee>: Sep 22 03:18PM +0300 22.09.2021 13:59 Bart kirjutas: >> an ancient K&R function which didn't get included into the ANSI C >> standard. > So what would be the shiny new alternative in C++? std::to_string() https://en.cppreference.com/w/cpp/string/basic_string/to_string |
| Paavo Helde <myfirstname@osa.pri.ee>: Sep 22 03:29PM +0300 22.09.2021 13:26 Bart kirjutas: > On my machine, that took 4 seconds. But when I tried my script language, > where each element has to be converted to a string object before being > printed, it took only 3 seconds: A better comparison would be to measure the time of converting and appending a million numbers to a std::string, then outputting the huge string in one go. The C++ iostreams can be very slow and the timings depend on the implementation. In my experiments I saw that ostream << can be either 10x slower than huge string building (MSVC++ 2019), or then 2x faster (g++ 8.3). I streamed to an in-memory ostringstream, so there was no disk slowdown involved. |
| Bart <bc@freeuk.com>: Sep 22 01:45PM +0100 On 22/09/2021 12:13, Ian Collins wrote: > This will take roughly the time it takes to perform the file writing > (0.4S on my machine). The time spend formatting the output is > inconsequential in comparison. But you don't know that? On my machine, timing went from 4.3 seconds to 5.3 seconds if I changed the output file from "output" to "nul" (Windows' version of a null device, not a very effective one!). I don't know how to isolate the file i/o from the conversion in C++. If I switch to C *printf functions, then going from fprintf to sprintf, changes the timing from 4.3 seconds to 0.8 seconds; so more than 80% is writing via the file system. I wanted to determine the overheads of using "<<" on each print item, especially as there are 3 extra arguments of " ", " " and "\n". These latter overheads are irrelevant when using the format string of sprintf. The question posed was whether turning into individual print items into a string object, before sending that string to the output, was a significant overhead. I showed my /dynamic/ code wasn't significantly slower than C++ (actually it was faster) despite doing those conversions, plus a whole bunch of other overheads that C++ will not have. |
| Bart <bc@freeuk.com>: Sep 22 02:32PM +0100 On 22/09/2021 13:17, Alf P. Steinbach wrote: > } > printf( "text.size = %d\n", int( text.size() ) ); > } This fails rextester.com (doesn't know charconv); and my g++/mingw/10.3.0, doesn't like converting const char to P_char. If I use '-fpermissive' to get around that, then I get a timing of 0.56; more than twice as fast was my earlier time. I think about 5 times as fast as my dynamic code. However, if you are going to write custom code, then I can do the same... This is my script: s ::= "" # ::= makes a mutable copy for i to 10 million do s +:= tostr(i) s +:= ' ' od println s.len (It uses 64-bit ints which impact the int-to-text conversion, which is not optimised for base-10.) |
| scott@slp53.sl.home (Scott Lurndal): Sep 22 02:04PM >> } >You learn something new every day. I'd never heard of itoa(). Apparently its >an ancient K&R function which didn't get included into the ANSI C standard. Because strtoul et al are far more useful than itoa/atoi for error checking. |
| "Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Sep 22 04:16PM +0200 On 22 Sep 2021 14:18, Paavo Helde wrote: >> So what would be the shiny new alternative in C++? > std::to_string() > https://en.cppreference.com/w/cpp/string/basic_string/to_string Not fast and not locale-independent (it uses the C library's locale). Better use C++17 `to_chars`, <url: https://en.cppreference.com/w/cpp/utility/to_chars>. I gave a more or less full example in reply to the same posting you replied to. - Alf |
| Keith Thompson <Keith.S.Thompson+u@gmail.com>: Sep 22 07:18AM -0700 Bart <bc@freeuk.com> writes: [...] > Compiled as g++ -O2, this runs in 1.3 seconds on my machine. My script > language might take only twice as long, but is much simpler and > quicker to write. I'm a little surprised that compiles. It doesn't on my Linux system, but it does under Cygwin (but fails with "g++ -std=c++11 -pedantic"). itoa() is non-standard, and apparently it's provided by newlib (Cygwin) but not by GNU libc (most Linux systems). std::to_string() (introduced in C++11) is the C++ equivalent. -- Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com Working, but not speaking, for Philips void Void(void) { Void(); } /* The recursive call of the void */ |
| "Alf P. Steinbach" <alf.p.steinbach@gmail.com>: Sep 22 04:23PM +0200 On 22 Sep 2021 15:32, Bart wrote: >> } > This fails rextester.com (doesn't know charconv); and my > g++/mingw/10.3.0, doesn't like converting const char to P_char. There's a good chance both problems are due to compiling for a too old C++ standard. Namely, `to_chars` was introduced in C++17, and the non-`const` `basic_string::data()`, producing a `char*`, was introduced in C++17; see <url: https://en.cppreference.com/w/cpp/string/basic_string/data>. So, specify `-std=c++17` or later. > If I use '-fpermissive' to get around that, then I get a timing of 0.56; > more than twice as fast was my earlier time. I think about 5 times as > fast as my dynamic code. Not sure how you manage to compile this without `-std=c++17` though. Now that begins to look like a mystery! :-o > println s.len > (It uses 64-bit ints which impact the int-to-text conversion, which is > not optimised for base-10.) I didn't present custom code for the conversion. The added code was for a reasonable wrapping of the `std::to_chars` call. - Alf |
| "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com>: Sep 21 04:33PM -0700 On 9/21/2021 4:24 AM, Bonita Montero wrote: >> Creating a working condvar can be very tricky. Have you >> tried to run your implementation through a race detector? ... > Can you read ? I haven't built a condvar but a monitor-object. A monitor object in C++ seems strange to me. |
| 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