[PATCH v3 0/4] libstdc++: Add _Streambuf_sink and specialize _Iter_sink for ostreambuf_iterator
Anlai Lu
agicy@qq.com
Wed Aug 12 03:44:57 GMT 2026
Hi Tomasz,
Thanks for the updated patches.
I re-ran the benchmarks on the updated patches (x86_64, -O2, core
pinned, BABA interleaved, vs trunk):
scenario (vs trunk) 4 B 16 B 64 B 256 B 1024 B 4096 B
fmt_to 1.14x 1.83x 4.14x 17.40x 54.26x 118.79x
fmt_to_n (notrunc) 1.12x 1.78x 4.90x 16.13x 54.61x 119.57x
fmt_to_noputarea 1.27x 2.03x 5.78x 19.86x 21.36x 26.49x
fmt_to_filebuf 1.19x 1.98x 5.26x 12.21x 19.29x 20.71x
fmt_to_append 1.19x 1.87x 4.40x 13.37x 32.46x 47.73x
fmt_to_scanner 1.13x 1.09x 1.08x 1.07x 1.08x 1.07x
print (control) 1.01x 0.99x 0.99x 1.00x 0.97x 0.99x
string (control) 1.00x 1.10x 1.00x 0.99x 1.04x 1.01x
Perf stat for the primary fmt_to path:
len instr% cycles% b_IPC a_IPC
4 85.8% 87.9% 2.76 2.69
16 64.5% 54.2% 2.24 2.66
64 32.8% 19.5% 1.60 2.69
256 11.7% 5.7% 1.36 2.76
1024 4.0% 2.3% 1.33 2.37
4096 1.8% 0.9% 1.33 2.65
Compared with the numbers from Jul 16: 4096B is unchanged (121.22x
-> 118.79x, within noise), 256B improved (10.98x -> 17.40x).
> On Aug 11, 2026, at 16:42, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Thu, Jul 16, 2026 at 2:04 PM Anlai Lu <agicy@qq.com <mailto:agicy@qq.com>> wrote:
>> This series replaces per-character sputc() with bulk sputn() and
>> zero-copy put-area writes when formatting to ostreambuf_iterator.
>> Patch 2 adds a _Streambuf_sink that writes directly to a streambuf;
>> patch 3 specializes _Iter_sink to use it; patch 4 uses _Streambuf_sink
>> in std::print; patch 1 adds tests.
>>
>> Performance:
>>
>> fmt_to (ostreambuf_iter) 4 B 16 B 64 B 256 B 1024 B 4096 B
>> v3: 0.97x 1.61x 4.15x 10.98x 51.97x 121.22x
>>
>> print (uses _Streambuf_sink, per [ostream.formatted.print]):
>> 4 B 16 B 64 B 256 B 1024 B 4096 B
>> before (ns/op): 87 114 112 118 190 286
>> after (ns/op): 78 78 77 86 82 134
>> ratio: 1.12x 1.47x 1.46x 1.37x 2.33x 2.14x
>>
>> string / fmt_to_scanner unchanged (controls).
> Hi,
> I have posted an updated version of first 3 patches (see my comments on
> fourth one, explaining why it is non-conforming, and needs a paper to allow
> taht optimization). Would you mind redoing your tests on the updated patches,
> as I do not have access to them?
>>
>> Changes in v3
>> ==============
>> (Items marked with [R] were suggested by Tomasz Kaminski.)
>>
>> - Extracted protected _M_flush(span) from _M_overflow so that both
>> _M_overflow and _M_finish share the same write path, and the
>> _Iter_sink specialization reuses it too. [R]
>> - Added _M_write_failed flag and _M_discarding() override to
>> _Streambuf_sink, so the format engine stops output early after a
>> write failure. [R]
>> - Changed _M_max from __diff_t to size_t with _S_no_limit sentinel. [R]
>> - Error propagation to the iterator moved to _Iter_sink::_M_finish
>> (checked just before returning std::move), so _M_overflow no longer
>> touches _M_out.
>> - Used _Streambuf_sink directly in vprint_nonunicode (patch 4),
>> replacing the _Str_sink + __ostream_write two-phase approach.
>> - Added _M_set_failed() to ostreambuf_iterator for the iterator's
>> error propagation path.
>> - Added try/catch in _M_flush to handle sputn I/O exceptions without
>> letting them propagate as vformat exceptions. Per
>> [ostream.formatted.print]/(4.2), vformat exceptions propagate without
>> badbit; I/O exceptions from sputn are not vformat exceptions and
>> are converted to _M_write_failed, then reported as badbit per (4.4).
>> - _M_finish() now ref-qualified (&&) on all sinks for consistency;
>> the print path uses std::move to call it. [R]
>> - Extracted _M_limit(span) helper in the _Iter_sink specialization for
>> shared counting/truncation between _M_overflow and _M_finish;
>> _M_finish now calls _M_flush directly, skipping unnecessary buffer
>> restoration. [R]
>>
>> Not applied
>> ============
>> 1. Destructor flush [R]
>> Flush in destruction runs after the error check in print, so it
>> cannot help report I/O errors. And no other sink flushes in its
>> destructor.
>>
>> 2. _M_bump override [R]
>> The default advances _M_next within the current span; commit
>> happens via _M_flush. A comment explains this.
>>
>> Anlai Lu (4):
>> libstdc++: Add tests for format_to with ostreambuf_iterator
>> libstdc++: Add _Streambuf_sink for direct streambuf formatting
>> libstdc++: Specialize _Iter_sink for ostreambuf_iterator
>> libstdc++: Optimize std::print using _Streambuf_sink
>>
>> libstdc++-v3/include/bits/ostream_print.h | 12 +-
>> .../include/bits/streambuf_iterator.h | 11 +
>> libstdc++-v3/include/std/format | 199 ++++++++++++++
>> libstdc++-v3/include/std/streambuf | 4 +
>> .../format/functions/format_to_ostreambuf.cc | 244 ++++++++++++++++++
>> 5 files changed, 463 insertions(+), 7 deletions(-)
>> create mode 100644 libstdc++-v3/testsuite/std/format/functions/format_to_ostreambuf.cc
>>
>> base-commit: a0591d86bd2be838e30c132481a0b15a6ba1e112
>>
>> --
>> 2.34.1
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260812/b3d748ff/attachment-0001.htm>
More information about the Libstdc++
mailing list