Skip to content

Commit f3b052a

Browse files
committed
[libc++][format] Implements P3107R5 in <print>.
The followup paper P3235R3 which is voted in as a DR changes the names foo_locking to foo_buffered. These changes have been applied in this patch. Before ------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------- printf 71.3 ns 71.3 ns 9525175 print_string 226 ns 226 ns 3105850 print_stack 232 ns 232 ns 3026498 print_direct 530 ns 530 ns 1318447 After ------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------- printf 70.6 ns 70.6 ns 9789585 print_string 222 ns 222 ns 3147678 print_stack 227 ns 227 ns 3084767 print_direct 474 ns 474 ns 1472786 Note: The performance of libc++'s std::print is still extemely slow compared to printf. Based on P3107R5 std::print should outperform printf. The main culprit is the call to isatty, which is resolved after implementing LWG4044 Confusing requirements for std::print on POSIX platforms Implements - P3107R5 - Permit an efficient implementation of ``std::print`` Implements parts of - P3235R3 std::print more types faster with less memory Fixes: #105435
1 parent 97732a4 commit f3b052a

File tree

10 files changed

+270
-13
lines changed

10 files changed

+270
-13
lines changed

libcxx/docs/ReleaseNotes/21.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Implemented Papers
4040

4141
- N4258: Cleaning-up noexcept in the Library (`Github <https://github.com/llvm/llvm-project/issues/99937>`__)
4242
- P1361R2: Integration of chrono with text formatting (`Github <https://github.com/llvm/llvm-project/issues/100014>`__)
43+
- P3107R5 - Permit an efficient implementation of ``std::print`` (`Github <https://github.com/llvm/llvm-project/issues/105435>`__)
4344

4445
Improvements and New Features
4546
-----------------------------

libcxx/include/__format/buffer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <__algorithm/copy_n.h>
1414
#include <__algorithm/fill_n.h>
15+
#include <__algorithm/for_each.h>
1516
#include <__algorithm/max.h>
1617
#include <__algorithm/min.h>
1718
#include <__algorithm/ranges_copy.h>
@@ -34,11 +35,13 @@
3435
#include <__memory/construct_at.h>
3536
#include <__memory/destroy.h>
3637
#include <__memory/uninitialized_algorithms.h>
38+
#include <__system_error/system_error.h>
3739
#include <__type_traits/add_pointer.h>
3840
#include <__type_traits/conditional.h>
3941
#include <__utility/exception_guard.h>
4042
#include <__utility/move.h>
4143
#include <stdexcept>
44+
#include <stdio.h> // Uses the POSIX/Windows unlocked stream I/O
4245
#include <string_view>
4346

4447
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)

libcxx/include/print

Lines changed: 237 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ namespace std {
2727
2828
void vprint_unicode(string_view fmt, format_args args);
2929
void vprint_unicode(FILE* stream, string_view fmt, format_args args);
30+
void vprint_unicode_buffered(FILE* stream, string_view fmt, format_args args);
3031
3132
void vprint_nonunicode(string_view fmt, format_args args);
3233
void vprint_nonunicode(FILE* stream, string_view fmt, format_args args);
34+
void vprint_nonunicode_buffered(FILE* stream, string_view fmt, format_args args);
3335
}
3436
*/
3537

@@ -213,6 +215,107 @@ _LIBCPP_HIDE_FROM_ABI inline bool __is_terminal([[maybe_unused]] FILE* __stream)
213215
# endif
214216
}
215217

218+
_LIBCPP_HIDE_FROM_ABI inline void __flockfile(FILE* __stream) {
219+
# if defined(_LIBCPP_WIN32API)
220+
::_lock_file(__stream);
221+
# elif __has_include(<unistd.h>)
222+
::flockfile(__stream);
223+
# else
224+
# error "Provide a way to do unlocked stream I/O operations"
225+
# endif
226+
}
227+
_LIBCPP_HIDE_FROM_ABI inline void __funlockfile(FILE* __stream) {
228+
# if defined(_LIBCPP_WIN32API)
229+
::_unlock_file(__stream);
230+
# elif __has_include(<unistd.h>)
231+
::funlockfile(__stream);
232+
# else
233+
# error "Provide a way to do unlocked stream I/O operations"
234+
# endif
235+
}
236+
237+
_LIBCPP_HIDE_FROM_ABI inline int __fflush_unlocked(FILE* __stream) {
238+
# if defined(_LIBCPP_WIN32API)
239+
return ::_fflush_nolock(__stream);
240+
# elif __has_include(<unistd.h>)
241+
return ::fflush_unlocked(__stream);
242+
# else
243+
# error "Provide a way to do unlocked stream I/O operations"
244+
# endif
245+
}
246+
247+
_LIBCPP_HIDE_FROM_ABI inline size_t __fwrite_unlocked(const void* __buffer, size_t __size, size_t __n, FILE* __stream) {
248+
# if defined(_LIBCPP_WIN32API)
249+
return ::_fwrite_nolock(__buffer, __size, __n, __stream);
250+
# elif __has_include(<unistd.h>)
251+
return ::fwrite_unlocked(__buffer, __size, __n, __stream);
252+
# else
253+
# error "Provide a way to do unlocked stream I/O operations"
254+
# endif
255+
}
256+
257+
// This "buffer" is not a typical buffer but an adaptor for FILE*
258+
//
259+
// This adaptor locks the file stream, allowing it to use unlocked I/O.
260+
// This is used by the *_buffered functions in <print>. The print functions have
261+
// no wchar_t support so char is hard-coded. Since the underlaying I/O functions
262+
// encode narrow or wide in their name this avoids some `if constexpr` branches.
263+
//
264+
// The underlying functions for unlocked I/O are not in the C Standard, and
265+
// their names differ between POSIX and Windows, therefore the functions are
266+
// wrapped in this class.
267+
class __file_stream_buffer : public __format::__output_buffer<char> {
268+
public:
269+
using value_type = char;
270+
271+
__file_stream_buffer(const __file_stream_buffer&) = delete;
272+
__file_stream_buffer operator=(const __file_stream_buffer&) = delete;
273+
274+
_LIBCPP_HIDE_FROM_ABI explicit __file_stream_buffer(FILE* __stream)
275+
: __output_buffer<char>{__small_buffer_, __buffer_size, __prepare_write, nullptr}, __stream_(__stream) {
276+
__print::__flockfile(__stream_);
277+
}
278+
279+
_LIBCPP_HIDE_FROM_ABI ~__file_stream_buffer() { __print::__funlockfile(__stream_); }
280+
281+
// In order to ensure all data is written this function needs to be called.
282+
//
283+
// The class wraps C based APIs that never throw. However the Standard
284+
// requires exceptions to be throw when a write operation fails. Therefore
285+
// this function should be called before the class is destroyed.
286+
_LIBCPP_HIDE_FROM_ABI void __write_internal_buffer() && { __write_buffer(); }
287+
288+
private:
289+
FILE* __stream_;
290+
291+
// This class uses a fixed size buffer and appends the elements in
292+
// __buffer_size chunks. An alternative would be to use an allocating buffer
293+
// and append the output in a single write operation. Benchmarking showed no
294+
// performance difference.
295+
static constexpr size_t __buffer_size = 256;
296+
char __small_buffer_[__buffer_size];
297+
298+
_LIBCPP_HIDE_FROM_ABI void __write_buffer() {
299+
size_t __n = this->__size();
300+
size_t __size = __print::__fwrite_unlocked(__small_buffer_, 1, __n, __stream_);
301+
if (__size < __n) {
302+
if (std::feof(__stream_))
303+
std::__throw_system_error(EIO, "EOF while writing the formatted output");
304+
std::__throw_system_error(std::ferror(__stream_), "failed to write formatted output");
305+
}
306+
}
307+
308+
_LIBCPP_HIDE_FROM_ABI void __prepare_write() {
309+
__write_buffer();
310+
this->__buffer_flushed();
311+
}
312+
313+
_LIBCPP_HIDE_FROM_ABI static void
314+
__prepare_write(__output_buffer<char>& __buffer, [[maybe_unused]] size_t __size_hint) {
315+
static_cast<__file_stream_buffer&>(__buffer).__prepare_write();
316+
}
317+
};
318+
216319
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
217320
_LIBCPP_HIDE_FROM_ABI inline void
218321
__vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) {
@@ -229,6 +332,26 @@ __vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool
229332
}
230333
}
231334

335+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
336+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_nonunicode_buffered(
337+
__print::__file_stream_buffer& __buffer, string_view __fmt, format_args __args, bool __write_nl) {
338+
std::__format::__vformat_to(basic_format_parse_context{__fmt, __args.__size()},
339+
std::__format_context_create(__buffer.__make_output_iterator(), __args));
340+
if (__write_nl)
341+
__buffer.push_back('\n');
342+
}
343+
344+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
345+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_nonunicode_buffered(
346+
FILE* __stream , string_view __fmt, format_args __args, bool __write_nl) {
347+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
348+
__print::__file_stream_buffer __buffer(__stream);
349+
350+
__print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl);
351+
352+
std::move(__buffer).__write_internal_buffer();
353+
}
354+
232355
# if _LIBCPP_HAS_UNICODE
233356

234357
// Note these helper functions are mainly used to aid testing.
@@ -246,10 +369,27 @@ __vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bo
246369
__print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
247370
}
248371

372+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
373+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_posix(
374+
FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
375+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
376+
__print::__file_stream_buffer __buffer(__stream);
377+
378+
// TODO PRINT Should flush errors throw too?
379+
if (__is_terminal)
380+
__print::__fflush_unlocked(__stream);
381+
382+
__print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl);
383+
384+
std::move(__buffer).__write_internal_buffer();
385+
}
249386
# if _LIBCPP_HAS_WIDE_CHARACTERS
387+
250388
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
251389
_LIBCPP_HIDE_FROM_ABI inline void
252390
__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
391+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
392+
253393
if (!__is_terminal)
254394
return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
255395

@@ -284,6 +424,49 @@ __vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args,
284424
"__write_to_windows_console is not available.");
285425
# endif
286426
}
427+
428+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
429+
_LIBCPP_HIDE_FROM_ABI inline void
430+
__vprint_unicode_buffered_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
431+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
432+
433+
if (!__is_terminal)
434+
return __print::__vprint_nonunicode_buffered(__stream, __fmt, __args, __write_nl);
435+
436+
[[maybe_unused]] __print::__file_stream_buffer __unused(__stream);
437+
438+
// TODO PRINT Should flush errors throw too?
439+
__print::__fflush_unlocked(__stream);
440+
441+
string __str = std::vformat(__fmt, __args);
442+
// UTF-16 uses the same number or less code units than UTF-8.
443+
// However the size of the code unit is 16 bits instead of 8 bits.
444+
//
445+
// The buffer uses the worst-case estimate and should never resize.
446+
// However when the string is large this could lead to OOM. Using a
447+
// smaller size might work, but since the buffer uses a grow factor
448+
// the final size might be larger when the estimate is wrong.
449+
//
450+
// TODO PRINT profile and improve the speed of this code.
451+
__format::__retarget_buffer<wchar_t> __buffer{__str.size()};
452+
__unicode::__transcode(__str.begin(), __str.end(), __buffer.__make_output_iterator());
453+
if (__write_nl)
454+
__buffer.push_back(L'\n');
455+
456+
[[maybe_unused]] wstring_view __view = __buffer.__view();
457+
458+
// The macro _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION is used to change
459+
// the behavior in the test. This is not part of the public API.
460+
# ifdef _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION
461+
_LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION(__stream, __view);
462+
# elif defined(_LIBCPP_WIN32API)
463+
std::__write_to_windows_console(__stream, __view);
464+
# else
465+
std::__throw_runtime_error("No defintion of _LIBCPP_TESTING_PRINT_WRITE_TO_WINDOWS_CONSOLE_FUNCTION and "
466+
"__write_to_windows_console is not available.");
467+
# endif
468+
}
469+
287470
# endif // _LIBCPP_HAS_WIDE_CHARACTERS
288471

289472
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
@@ -324,20 +507,47 @@ __vprint_unicode([[maybe_unused]] FILE* __stream,
324507
# endif
325508
}
326509

510+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
511+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered(
512+
[[maybe_unused]] FILE* __stream,
513+
[[maybe_unused]] string_view __fmt,
514+
[[maybe_unused]] format_args __args,
515+
[[maybe_unused]] bool __write_nl) {
516+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
517+
518+
# ifndef _LIBCPP_WIN32API
519+
__print::__vprint_unicode_buffered_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
520+
# elif !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)
521+
__print::__vprint_unicode_buffered_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
522+
# else
523+
# error "Windows builds with wchar_t disabled are not supported."
524+
# endif
525+
}
526+
327527
# endif // _LIBCPP_HAS_UNICODE
328528

329529
} // namespace __print
330530

331531
template <class... _Args>
332532
_LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
333533
# if _LIBCPP_HAS_UNICODE
334-
if constexpr (__print::__use_unicode_execution_charset)
335-
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
336-
else
337-
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
534+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
338535
# else // _LIBCPP_HAS_UNICODE
339-
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
536+
constexpr bool __use_unicode = false;
340537
# endif // _LIBCPP_HAS_UNICODE
538+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
539+
540+
if constexpr (__use_unicode) {
541+
if constexpr (__locksafe)
542+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
543+
else
544+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
545+
} else {
546+
if constexpr (__locksafe)
547+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
548+
else
549+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
550+
}
341551
}
342552

343553
template <class... _Args>
@@ -348,16 +558,26 @@ _LIBCPP_HIDE_FROM_ABI void print(format_string<_Args...> __fmt, _Args&&... __arg
348558
template <class... _Args>
349559
_LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
350560
# if _LIBCPP_HAS_UNICODE
561+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
562+
# else // _LIBCPP_HAS_UNICODE
563+
constexpr bool __use_unicode = false;
564+
# endif // _LIBCPP_HAS_UNICODE
565+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
566+
351567
// Note the wording in the Standard is inefficient. The output of
352568
// std::format is a std::string which is then copied. This solution
353569
// just appends a newline at the end of the output.
354-
if constexpr (__print::__use_unicode_execution_charset)
355-
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
356-
else
357-
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
358-
# else // _LIBCPP_HAS_UNICODE
359-
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
360-
# endif // _LIBCPP_HAS_UNICODE
570+
if constexpr (__use_unicode) {
571+
if constexpr (__locksafe)
572+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
573+
else
574+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
575+
} else {
576+
if constexpr (__locksafe)
577+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
578+
else
579+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
580+
}
361581
}
362582

363583
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
@@ -381,6 +601,11 @@ _LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(FILE* __stream, string_view __f
381601
__print::__vprint_unicode(__stream, __fmt, __args, false);
382602
}
383603

604+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
605+
_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode_buffered(FILE* __stream, string_view __fmt, format_args __args) {
606+
__print::__vprint_unicode_buffered(__stream, __fmt, __args, false);
607+
}
608+
384609
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
385610
_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(string_view __fmt, format_args __args) {
386611
std::vprint_unicode(stdout, __fmt, __args);

libcxx/modules/std/print.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export namespace std {
1616
using std::vprint_nonunicode;
1717
# if _LIBCPP_HAS_UNICODE
1818
using std::vprint_unicode;
19+
using std::vprint_unicode_buffered;
1920
# endif // _LIBCPP_HAS_UNICODE
2021
#endif // _LIBCPP_STD_VER >= 23
2122
} // namespace std

libcxx/test/libcxx/transitive_includes/cxx03.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,19 @@ atomic ratio
122122
atomic type_traits
123123
atomic version
124124
barrier atomic
125+
barrier cctype
125126
barrier climits
126127
barrier cmath
127128
barrier compare
128129
barrier concepts
129130
barrier cstddef
130131
barrier cstdint
132+
barrier cstdio
131133
barrier cstdlib
132134
barrier cstring
133135
barrier ctime
136+
barrier cwchar
137+
barrier cwctype
134138
barrier exception
135139
barrier initializer_list
136140
barrier iosfwd
@@ -2024,6 +2028,7 @@ stdexcept new
20242028
stdexcept type_traits
20252029
stdexcept typeinfo
20262030
stdexcept version
2031+
stop_token cstddef
20272032
stop_token iosfwd
20282033
stop_token version
20292034
streambuf algorithm

0 commit comments

Comments
 (0)