Skip to content

Commit bfd4e08

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 bfd4e08

File tree

9 files changed

+267
-13
lines changed

9 files changed

+267
-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: 235 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,107 @@ _LIBCPP_HIDE_FROM_ABI inline bool __is_terminal([[maybe_unused]] FILE* __stream)
213213
# endif
214214
}
215215

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

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

234355
// Note these helper functions are mainly used to aid testing.
@@ -246,10 +367,27 @@ __vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bo
246367
__print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
247368
}
248369

370+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
371+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_posix(
372+
FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
373+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
374+
__print::__file_stream_buffer __buffer(__stream);
375+
376+
// TODO PRINT Should flush errors throw too?
377+
if (__is_terminal)
378+
__print::__fflush_unlocked(__stream);
379+
380+
__print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl);
381+
382+
std::move(__buffer).__write_internal_buffer();
383+
}
249384
# if _LIBCPP_HAS_WIDE_CHARACTERS
385+
250386
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
251387
_LIBCPP_HIDE_FROM_ABI inline void
252388
__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
389+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
390+
253391
if (!__is_terminal)
254392
return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
255393

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

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

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

329527
} // namespace __print
330528

331529
template <class... _Args>
332530
_LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
333531
# 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);
532+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
338533
# else // _LIBCPP_HAS_UNICODE
339-
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
534+
constexpr bool __use_unicode = false;
340535
# endif // _LIBCPP_HAS_UNICODE
536+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
537+
538+
if constexpr (__use_unicode) {
539+
if constexpr (__locksafe)
540+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
541+
else
542+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
543+
} else {
544+
if constexpr (__locksafe)
545+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
546+
else
547+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
548+
}
341549
}
342550

343551
template <class... _Args>
@@ -348,16 +556,26 @@ _LIBCPP_HIDE_FROM_ABI void print(format_string<_Args...> __fmt, _Args&&... __arg
348556
template <class... _Args>
349557
_LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
350558
# if _LIBCPP_HAS_UNICODE
559+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
560+
# else // _LIBCPP_HAS_UNICODE
561+
constexpr bool __use_unicode = false;
562+
# endif // _LIBCPP_HAS_UNICODE
563+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
564+
351565
// Note the wording in the Standard is inefficient. The output of
352566
// std::format is a std::string which is then copied. This solution
353567
// 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
568+
if constexpr (__use_unicode) {
569+
if constexpr (__locksafe)
570+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
571+
else
572+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
573+
} else {
574+
if constexpr (__locksafe)
575+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
576+
else
577+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
578+
}
361579
}
362580

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

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

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

libcxx/test/libcxx/transitive_includes/cxx11.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)