From e753244ab6bb45b5c32b412c09b401ff6d82606e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 26 Jun 2020 20:12:45 -0700 Subject: [PATCH 1/5] Update changelog --- ChangeLog.rst | 80 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index d4fb516b9b6e..41f6e84c40ee 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,9 +1,40 @@ 7.0.0 - TBD +----------- * Reduced the library size. For example, on macOS the stripped binary - statically linked with {fmt} shrinked from ~368k to less than 100k: + statically linked with {fmt} shrank from ~368k to less than 100k: http://www.zverovich.net/2020/05/21/reducing-library-size.html +* Applied extern templates to improve compile times when using the core API + and ``fmt/format.h`` (`#1452 `_). + For example, on macOS with clang the compile time dropped from 2.3s to 0.3s + with ``-O2`` or from 0.6s to 0.3s with the default settings (``-O0``). + + Before (``-O2``):: + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 2.22s user 0.08s system 99% cpu 2.311 total + + After (``-O2``):: + + % time c++ -c test.cc -I include -std=c++17 -O2 + c++ -c test.cc -I include -std=c++17 -O2 0.26s user 0.04s system 98% cpu 0.303 total + + Before (default):: + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.53s user 0.06s system 98% cpu 0.601 total + + After (default):: + + % time c++ -c test.cc -I include -std=c++17 + c++ -c test.cc -I include -std=c++17 0.24s user 0.06s system 98% cpu 0.301 total + + It is still recommended to use ``fmt/core.h`` instead of ``fmt/format.h`` but + the compile time difference is now smaller. + + Thanks `@alex3d `_ for the suggestion. + * Named arguments are now stored on stack (no dynamic memory allocations) and the generated binary code is more compact and efficient. For example @@ -43,13 +74,53 @@ .L.str.1: .asciz "answer" +* Implemented compile-time checks for dynamic width and precision + (`#1614 `_). For example + + .. code:: c++ + + #include + + int main() { + fmt::print(FMT_STRING("{0:{1}}"), 42); + } + + gives a compilation error because argument 1 doesn't exist:: + + In file included from test.cc:1: + include/fmt/format.h:2726:27: error: constexpr variable 'invalid_format' must be + initialized by a constant expression + FMT_CONSTEXPR_DECL bool invalid_format = + ^ + include/fmt/core.h:1727:3: note: in instantiation of function template specialization + 'fmt::v6::internal::check_format_string' requested here + check_format_string(format_str); + ^ + ... + include/fmt/core.h:569:26: note: in call to + '&checker(s, {}).context_->on_error(&"argument not found"[0])' + if (id >= num_args_) on_error("argument not found"); + ^ + * Implemented the ``'L'`` specifier for locale-specific formatting of floating-point numbers to improve compatibility with ``std::format`` (`#1624 `_). +* Made ``fmt::printf`` not compute string length when using precision to allow + passing non-nul-terminated strings + (`#1595 `_): + + .. code:: c++ + + char foo[] = {'H', 'e', 'l', 'l', 'o'}; + fmt::printf("%.5s\n", foo); // This is fine. + +* Removed the deprecated and disabled by default ``fmt`` macro and + ``FMT_STRING_ALIAS``. + * Improved documentation (`#1643 `_). - Thanks `@senior7515 (Alexander Gallego) `_, + Thanks `@senior7515 (Alexander Gallego) `_. * Fixed various warnings and compilation issues (`#1616 `_, @@ -58,7 +129,8 @@ `#1628 `_, `#1629 `_ `#1631 `_, - `#1633 `_). + `#1633 `_, + `#1649 `_). Thanks `@gsjaardema (Greg Sjaardema) `_, `@gabime (Gabi Melman) `_, `@johnor (Johan) `_, @@ -100,7 +172,7 @@ if ``S`` is not formattable. -* Reduced library size by ~10%. +* Reduced the library size by ~10%. * Always print decimal point if ``#`` is specified (`#1476 `_, From a6f8e7d86091a3d9a04e6a2dc3d87efc5d19b77b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 27 Jun 2020 09:35:02 -0700 Subject: [PATCH 2/5] Update changelog --- ChangeLog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.rst b/ChangeLog.rst index 41f6e84c40ee..bf3476294df7 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -8,7 +8,7 @@ * Applied extern templates to improve compile times when using the core API and ``fmt/format.h`` (`#1452 `_). For example, on macOS with clang the compile time dropped from 2.3s to 0.3s - with ``-O2`` or from 0.6s to 0.3s with the default settings (``-O0``). + with ``-O2`` and from 0.6s to 0.3s with the default settings (``-O0``). Before (``-O2``):: From ba363b3a24a569e1c30988f95fa42db4bb67e401 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 27 Jun 2020 12:05:38 -0700 Subject: [PATCH 3/5] Use digit pairs as in unrolledlut --- include/fmt/format-inl.h | 27 +++++++++++++++++++++------ include/fmt/format.h | 12 +++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 95afbebf5bc2..5809adc865a6 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -239,12 +239,27 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { } template -const char basic_data::digits[] = - "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; +const typename basic_data::digit_pair basic_data::digits[] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, + {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, + {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, + {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'}, + {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, + {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, + {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, + {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, + {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'}, + {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, + {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, + {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, + {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, + {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, + {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, + {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, + {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; template const char basic_data::hex_digits[] = "0123456789abcdef"; diff --git a/include/fmt/format.h b/include/fmt/format.h index 24969e7c0d3a..7bd6543c3f78 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -752,7 +752,9 @@ template struct FMT_EXTERN_TEMPLATE_API basic_data { static const uint64_t zero_or_powers_of_10_64[]; static const uint64_t pow10_significands[]; static const int16_t pow10_exponents[]; - static const char digits[]; + // GCC generates slightly better code for pairs than chars. + using digit_pair = char[2]; + static const digit_pair digits[]; static const char hex_digits[]; static const char foreground_color[]; static const char background_color[]; @@ -903,7 +905,7 @@ inline format_decimal_result format_decimal(Char* out, UInt value, // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. out -= 2; - copy2(out, data::digits + static_cast((value % 100) * 2)); + copy2(out, data::digits[value % 100]); value /= 100; } if (value < 10) { @@ -911,7 +913,7 @@ inline format_decimal_result format_decimal(Char* out, UInt value, return {out, end}; } out -= 2; - copy2(out, data::digits + static_cast(value * 2)); + copy2(out, data::digits[value]); return {out, end}; } @@ -1085,12 +1087,12 @@ template It write_exponent(int exp, It it) { *it++ = static_cast('+'); } if (exp >= 100) { - const char* top = data::digits + (exp / 100) * 2; + const char* top = data::digits[exp / 100]; if (exp >= 1000) *it++ = static_cast(top[0]); *it++ = static_cast(top[1]); exp %= 100; } - const char* d = data::digits + exp * 2; + const char* d = data::digits[exp]; *it++ = static_cast(d[0]); *it++ = static_cast(d[1]); return it; From f69b6eaabd027b9ffee2ed630dd64725d3367e2a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 28 Jun 2020 06:34:28 -0700 Subject: [PATCH 4/5] Add a simple buffered stream with no sync --- include/fmt/os.h | 60 +++++++++++++++++++++++++++++++++++++++++++++- test/gtest-extra.h | 3 ++- test/os-test.cc | 20 ++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/include/fmt/os.h b/include/fmt/os.h index 31df8eea9b72..a9517ef80093 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -277,7 +277,8 @@ class file { enum { RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. - RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing. + CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist. }; // Constructs a file object which doesn't represent any file. @@ -341,6 +342,63 @@ class file { // Returns the memory page size. long getpagesize(); + +class direct_buffered_file; + +template +void print(direct_buffered_file& f, const S& format_str, + const Args&... args); + +// A buffered file with a direct buffer access and no synchronization. +class direct_buffered_file { + private: + file file_; + + enum { buffer_size = 4096 }; + char buffer_[buffer_size]; + int pos_; + + void flush() { + if (pos_ == 0) return; + file_.write(buffer_, pos_); + pos_ = 0; + } + + int free_capacity() const { return buffer_size - pos_; } + + public: + direct_buffered_file(cstring_view path, int oflag) + : file_(path, oflag), pos_(0) {} + + ~direct_buffered_file() { + flush(); + } + + void close() { + flush(); + file_.close(); + } + + template + friend void print(direct_buffered_file& f, const S& format_str, + const Args&... args) { + // We could avoid double buffering. + auto buf = fmt::memory_buffer(); + fmt::format_to(std::back_inserter(buf), format_str, args...); + auto remaining_pos = 0; + auto remaining_size = buf.size(); + while (remaining_size > detail::to_unsigned(f.free_capacity())) { + auto size = f.free_capacity(); + memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size); + f.pos_ += size; + f.flush(); + remaining_pos += size; + remaining_size -= size; + } + memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size); + f.pos_ += static_cast(remaining_size); + } +}; #endif // FMT_USE_FCNTL #ifdef FMT_LOCALE diff --git a/test/gtest-extra.h b/test/gtest-extra.h index a24efb4a9edc..3ed8052b3fcb 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -141,7 +141,8 @@ class SuppressAssert { std::string read(fmt::file& f, size_t count); # define EXPECT_READ(file, expected_content) \ - EXPECT_EQ(expected_content, read(file, std::strlen(expected_content))) + EXPECT_EQ(expected_content, \ + read(file, fmt::string_view(expected_content).size())) #else # define EXPECT_WRITE(file, statement, expected_output) SUCCEED() diff --git a/test/os-test.cc b/test/os-test.cc index d1530e5a8d68..186198efac47 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -287,6 +287,26 @@ TEST(BufferedFileTest, Fileno) { EXPECT_READ(copy, FILE_CONTENT); } +TEST(DirectBufferedFileTest, Print) { + fmt::direct_buffered_file out( + "test-file", fmt::file::WRONLY | fmt::file::CREATE); + fmt::print(out, "The answer is {}.\n", 42); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, "The answer is 42.\n"); +} + +TEST(DirectBufferedFileTest, BufferBoundary) { + auto str = std::string(4096, 'x'); + fmt::direct_buffered_file out( + "test-file", fmt::file::WRONLY | fmt::file::CREATE); + fmt::print(out, "{}", str); + fmt::print(out, "{}", str); + out.close(); + file in("test-file", file::RDONLY); + EXPECT_READ(in, str + str); +} + TEST(FileTest, DefaultCtor) { file f; EXPECT_EQ(-1, f.descriptor()); From cbddab2fe21c0051eebf46f2e6f20925a160fc04 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 29 Jun 2020 09:59:25 -0700 Subject: [PATCH 5/5] Use consistent include style --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8a6d86c05217..ffb467dc092e 100644 --- a/README.rst +++ b/README.rst @@ -116,7 +116,7 @@ Format objects of user-defined types via a simple `extension API .. code:: c++ - #include "fmt/format.h" + #include struct date { int year, month, day;