Wednesday, September 22, 2021

Digest for comp.lang.c++@googlegroups.com - 25 updates in 3 topics

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: