Skip to content

Commit 3858aba

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 3858aba

File tree

11 files changed

+289
-13
lines changed

11 files changed

+289
-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: 251 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

@@ -41,6 +43,7 @@ namespace std {
4143
# include <__config>
4244
# include <__system_error/throw_system_error.h>
4345
# include <__utility/forward.h>
46+
# include <__utility/move.h>
4447
# include <cerrno>
4548
# include <cstdio>
4649
# include <format>
@@ -52,6 +55,9 @@ namespace std {
5255
# pragma GCC system_header
5356
# endif
5457

58+
_LIBCPP_PUSH_MACROS
59+
# include <__undef_macros>
60+
5561
_LIBCPP_BEGIN_NAMESPACE_STD
5662

5763
# ifdef _LIBCPP_WIN32API
@@ -213,6 +219,115 @@ _LIBCPP_HIDE_FROM_ABI inline bool __is_terminal([[maybe_unused]] FILE* __stream)
213219
# endif
214220
}
215221

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

347+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
348+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_nonunicode_buffered(
349+
__print::__file_stream_buffer& __buffer, string_view __fmt, format_args __args, bool __write_nl) {
350+
std::__format::__vformat_to(basic_format_parse_context{__fmt, __args.__size()},
351+
std::__format_context_create(__buffer.__make_output_iterator(), __args));
352+
if (__write_nl)
353+
__buffer.push_back('\n');
354+
}
355+
356+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
357+
_LIBCPP_HIDE_FROM_ABI inline void
358+
__vprint_nonunicode_buffered(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) {
359+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
360+
__print::__file_stream_buffer __buffer(__stream);
361+
362+
__print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl);
363+
364+
std::move(__buffer).__write_internal_buffer();
365+
}
366+
232367
# if _LIBCPP_HAS_UNICODE
233368

234369
// Note these helper functions are mainly used to aid testing.
@@ -246,10 +381,27 @@ __vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bo
246381
__print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
247382
}
248383

384+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
385+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_posix(
386+
FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
387+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
388+
__print::__file_stream_buffer __buffer(__stream);
389+
390+
// TODO PRINT Should flush errors throw too?
391+
if (__is_terminal)
392+
__print::__fflush_unlocked(__stream);
393+
394+
__print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl);
395+
396+
std::move(__buffer).__write_internal_buffer();
397+
}
249398
# if _LIBCPP_HAS_WIDE_CHARACTERS
399+
250400
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
251401
_LIBCPP_HIDE_FROM_ABI inline void
252402
__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
403+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
404+
253405
if (!__is_terminal)
254406
return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
255407

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

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

522+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
523+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered(
524+
[[maybe_unused]] FILE* __stream,
525+
[[maybe_unused]] string_view __fmt,
526+
[[maybe_unused]] format_args __args,
527+
[[maybe_unused]] bool __write_nl) {
528+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
529+
530+
# ifndef _LIBCPP_WIN32API
531+
__print::__vprint_unicode_buffered_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
532+
# elif !defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)
533+
__print::__vprint_unicode_buffered_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
534+
# else
535+
# error "Windows builds with wchar_t disabled are not supported."
536+
# endif
537+
}
538+
327539
# endif // _LIBCPP_HAS_UNICODE
328540

329541
} // namespace __print
330542

331543
template <class... _Args>
332544
_LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
333545
# 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);
546+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
338547
# else // _LIBCPP_HAS_UNICODE
339-
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
548+
constexpr bool __use_unicode = false;
340549
# endif // _LIBCPP_HAS_UNICODE
550+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
551+
552+
if constexpr (__use_unicode) {
553+
if constexpr (__locksafe)
554+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
555+
else
556+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
557+
} else {
558+
if constexpr (__locksafe)
559+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
560+
else
561+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
562+
}
341563
}
342564

343565
template <class... _Args>
@@ -348,16 +570,26 @@ _LIBCPP_HIDE_FROM_ABI void print(format_string<_Args...> __fmt, _Args&&... __arg
348570
template <class... _Args>
349571
_LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
350572
# if _LIBCPP_HAS_UNICODE
573+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
574+
# else // _LIBCPP_HAS_UNICODE
575+
constexpr bool __use_unicode = false;
576+
# endif // _LIBCPP_HAS_UNICODE
577+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
578+
351579
// Note the wording in the Standard is inefficient. The output of
352580
// std::format is a std::string which is then copied. This solution
353581
// 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
582+
if constexpr (__use_unicode) {
583+
if constexpr (__locksafe)
584+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
585+
else
586+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
587+
} else {
588+
if constexpr (__locksafe)
589+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
590+
else
591+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
592+
}
361593
}
362594

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

616+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
617+
_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode_buffered(FILE* __stream, string_view __fmt, format_args __args) {
618+
__print::__vprint_unicode_buffered(__stream, __fmt, __args, false);
619+
}
620+
384621
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
385622
_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(string_view __fmt, format_args __args) {
386623
std::vprint_unicode(stdout, __fmt, __args);
@@ -402,6 +639,8 @@ _LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode(string_view __fmt, format_ar
402639

403640
_LIBCPP_END_NAMESPACE_STD
404641

642+
_LIBCPP_POP_MACROS
643+
405644
#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
406645

407646
#endif // _LIBCPP_PRINT

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

0 commit comments

Comments
 (0)