From: "Dietmar Kühl" <dietmar_kuehl@yahoo.com>
To: Jonathan Wakely <jwakely.gcc@gmail.com>
Cc: Lewis Hyatt <lhyatt@gmail.com>, libstdc++ <libstdc++@gcc.gnu.org>
Subject: Re: ostream::operator<<() and sputn()
Date: Wed, 14 Jul 2021 22:54:52 +0100 [thread overview]
Message-ID: <D781A8BD-CF31-4AC1-B3A2-599EEECEA375@yahoo.com> (raw)
In-Reply-To: <CAH6eHdQ1X8DYrEmjSqxTqWWkd5Ev2a12+PhM7TEUi-fCmA3i3A@mail.gmail.com>
Hello,
Of course, you could detect whether the user did specialize basic_streambuf: the default implementation can use a special member name, let’s say, __not_specialized, which the user cannot write in a portable program and, hence, in the user’s specialization. Detecting that could opt into an enhanced code path. I’m not recommending to use such an implementation. When I implemented my own version of IOStreams I used a few special approaches, notably a segmented iterator optimization for std::[io]streambuf_iterator with a bit more direct access to the streambuf’s buffer. For actually buffered streams these things can yield a substantial performance boost. There is some penalty for non-buffered (well, single character buffered) stream buffers, though.
Making changes to the streams while maintaining ABI compatibility is probably not quite worth the trouble, though.
Thanks,
Dietmar
> On 14 Jul 2021, at 22:31, Jonathan Wakely via Libstdc++ <libstdc++@gcc.gnu.org> wrote:
>
> On Wed, 14 Jul 2021 at 22:26, Lewis Hyatt via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
>>
>> Hello-
>>
>> I noticed that libstdc++'s implementation of ostream::operator<<() prefers
>> to call sputn() on the underlying streambuf for all char, char*, and string
>> output operations, including single characters, rather than manipulate the
>> buffer directly. I am curious why it works this way, it feels perhaps
>> suboptimal to me because sputn() is mandated to call the virtual function
>> xsputn() on every call, while e.g. sputc() simply manipulates the buffer and
>> only needs a virtual call when the buffer is full. I always thought that the
>> buffer abstraction and the resulting avoidance of virtual calls for the
>> majority of operations was the main point of streambuf's design, and that
>> sputn() was meant for cases when the output would be large enough to
>> overflow the buffer anyway, if it may be possible to skip the buffer and
>> flush directly instead?
>>
>> It seems to me that for most typical use cases, xsputn() is still going to
>> want to use the buffer if the output fits into it; libstdc++ does this in
>> basic_filebuf, for example. So then it would seem to be beneficial to try
>> the buffer prior to making the virtual function call, instead of after --
>> especially because the typical char instantiation of __ostream_insert that
>> makes this call for operator<<() is hidden inside the .so, and is not
>> inlined or eligible for devirtualization optimizations.
>>
>> FWIW, here is a small test case.
>>
>> ---------
>> #include <ostream>
>> #include <iostream>
>> #include <fstream>
>> #include <sstream>
>> #include <chrono>
>> #include <random>
>> using namespace std;
>>
>> int main() {
>> constexpr size_t N = 500000000;
>> string s(N, 'x');
>>
>> ofstream of{"/dev/null"};
>> ostringstream os;
>> ostream* streams[] = {&of, &os};
>> mt19937 rng{random_device{}()};
>>
>> const auto timed_run = [&](const char* label, auto&& callback) {
>> const auto t1 = chrono::steady_clock::now();
>> for(char c: s) callback(*streams[rng() % 2], c);
>> const auto t2 = chrono::steady_clock::now();
>> cout << label << " took: "
>> << chrono::duration<double>(t2-t1).count()
>> << " seconds" << endl;
>> };
>>
>> timed_run("insert with put()", [](ostream& o, char c) {o.put(c);});
>> timed_run("insert with op<< ", [](ostream& o, char c) {o << c;});
>> }
>> ---------
>>
>> This is what I get with the current trunk:
>> ---------
>> insert with put() took: 6.12152 seconds
>> insert with op<< took: 13.4437 seconds
>> ---------
>>
>> And this is what I get with the attached patch:
>> ---------
>> insert with put() took: 6.08313 seconds
>> insert with op<< took: 8.24565 seconds
>> ---------
>>
>> So the overhead of calling operator<< vs calling put() was reduced by more
>> than 3X.
>>
>> The prototype patch calls an internal alternate to sputn(), which tries the
>> buffer prior to calling xsputn().
>
> This won't work if a user provides an explicit specialization of
> basic_streambuf<char, MyTraits>. std::basic_ostream<char, MyTraits>
> will still try to call your new function, but it won't be present in
> the user's specialization, so will fail to compile. The basic_ostream
> primary template can only use the standard API of basic_streambuf. The
> std::basic_ostream<char> specialization can use non-standard members
> of std::basic_streambuf<char> because we know users can't specialize
> that.
prev parent reply other threads:[~2021-07-14 21:54 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-07-14 21:26 Lewis Hyatt
2021-07-14 21:30 ` Jonathan Wakely
2021-07-14 21:45 ` Lewis Hyatt
2021-07-15 17:11 ` François Dumont
2022-01-10 16:07 ` Jonathan Wakely
2022-01-10 23:18 ` Lewis Hyatt
2022-01-11 1:09 ` Jonathan Wakely
2021-07-14 21:54 ` Dietmar Kühl [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=D781A8BD-CF31-4AC1-B3A2-599EEECEA375@yahoo.com \
--to=dietmar_kuehl@yahoo.com \
--cc=jwakely.gcc@gmail.com \
--cc=lhyatt@gmail.com \
--cc=libstdc++@gcc.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).