Skip to content

Commit d05e7f3

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 d05e7f3

File tree

10 files changed

+276
-13
lines changed

10 files changed

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

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

234361
// Note these helper functions are mainly used to aid testing.
@@ -246,10 +373,27 @@ __vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bo
246373
__print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
247374
}
248375

376+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
377+
_LIBCPP_HIDE_FROM_ABI inline void __vprint_unicode_buffered_posix(
378+
FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
379+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
380+
__print::__file_stream_buffer __buffer(__stream);
381+
382+
// TODO PRINT Should flush errors throw too?
383+
if (__is_terminal)
384+
__print::__fflush_unlocked(__stream);
385+
386+
__print::__vprint_nonunicode_buffered(__buffer, __fmt, __args, __write_nl);
387+
388+
std::move(__buffer).__write_internal_buffer();
389+
}
249390
# if _LIBCPP_HAS_WIDE_CHARACTERS
391+
250392
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
251393
_LIBCPP_HIDE_FROM_ABI inline void
252394
__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
395+
_LIBCPP_ASSERT_NON_NULL(__stream, "__stream must be a valid pointer to an output C stream");
396+
253397
if (!__is_terminal)
254398
return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
255399

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

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

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

329533
} // namespace __print
330534

331535
template <class... _Args>
332536
_LIBCPP_HIDE_FROM_ABI void print(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
333537
# 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);
538+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
338539
# else // _LIBCPP_HAS_UNICODE
339-
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
540+
constexpr bool __use_unicode = false;
340541
# endif // _LIBCPP_HAS_UNICODE
542+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
543+
544+
if constexpr (__use_unicode) {
545+
if constexpr (__locksafe)
546+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
547+
else
548+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
549+
} else {
550+
if constexpr (__locksafe)
551+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), false);
552+
else
553+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), false);
554+
}
341555
}
342556

343557
template <class... _Args>
@@ -348,16 +562,26 @@ _LIBCPP_HIDE_FROM_ABI void print(format_string<_Args...> __fmt, _Args&&... __arg
348562
template <class... _Args>
349563
_LIBCPP_HIDE_FROM_ABI void println(FILE* __stream, format_string<_Args...> __fmt, _Args&&... __args) {
350564
# if _LIBCPP_HAS_UNICODE
565+
constexpr bool __use_unicode = __print::__use_unicode_execution_charset;
566+
# else // _LIBCPP_HAS_UNICODE
567+
constexpr bool __use_unicode = false;
568+
# endif // _LIBCPP_HAS_UNICODE
569+
constexpr bool __locksafe = (enable_nonlocking_formatter_optimization<remove_cvref_t<_Args>> && ...);
570+
351571
// Note the wording in the Standard is inefficient. The output of
352572
// std::format is a std::string which is then copied. This solution
353573
// 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
574+
if constexpr (__use_unicode) {
575+
if constexpr (__locksafe)
576+
__print::__vprint_unicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
577+
else
578+
__print::__vprint_unicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
579+
} else {
580+
if constexpr (__locksafe)
581+
__print::__vprint_nonunicode_buffered(__stream, __fmt.get(), std::make_format_args(__args...), true);
582+
else
583+
__print::__vprint_nonunicode(__stream, __fmt.get(), std::make_format_args(__args...), true);
584+
}
361585
}
362586

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

608+
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
609+
_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode_buffered(FILE* __stream, string_view __fmt, format_args __args) {
610+
__print::__vprint_unicode_buffered(__stream, __fmt, __args, false);
611+
}
612+
384613
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
385614
_LIBCPP_HIDE_FROM_ABI inline void vprint_unicode(string_view __fmt, format_args __args) {
386615
std::vprint_unicode(stdout, __fmt, __args);
@@ -402,6 +631,8 @@ _LIBCPP_HIDE_FROM_ABI inline void vprint_nonunicode(string_view __fmt, format_ar
402631

403632
_LIBCPP_END_NAMESPACE_STD
404633

634+
_LIBCPP_POP_MACROS
635+
405636
#endif // __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
406637

407638
#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)