Skip to content
forked from fmtlib/fmt

Commit

Permalink
Merge pull request #129 from fmtlib/master
Browse files Browse the repository at this point in the history
Sync Fork from Upstream Repo
  • Loading branch information
sthagen authored Jul 1, 2020
2 parents b1960bf + cbddab2 commit 6616837
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 18 deletions.
80 changes: 76 additions & 4 deletions ChangeLog.rst
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/fmtlib/fmt/issues/1452>`_).
For example, on macOS with clang the compile time dropped from 2.3s to 0.3s
with ``-O2`` and 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 <https://github.com/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

Expand Down Expand Up @@ -43,13 +74,53 @@
.L.str.1:
.asciz "answer"
* Implemented compile-time checks for dynamic width and precision
(`#1614 <https://github.com/fmtlib/fmt/issues/1614>`_). For example

.. code:: c++

#include <fmt/format.h>

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<int, FMT_COMPILE_STRING, 0>' requested here
check_format_string<Args...>(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 <https://github.com/fmtlib/fmt/issues/1624>`_).

* Made ``fmt::printf`` not compute string length when using precision to allow
passing non-nul-terminated strings
(`#1595 <https://github.com/fmtlib/fmt/issues/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 <https://github.com/fmtlib/fmt/pull/1643>`_).
Thanks `@senior7515 (Alexander Gallego) <https://github.com/senior7515>`_,
Thanks `@senior7515 (Alexander Gallego) <https://github.com/senior7515>`_.

* Fixed various warnings and compilation issues
(`#1616 <https://github.com/fmtlib/fmt/pull/1616>`_,
Expand All @@ -58,7 +129,8 @@
`#1628 <https://github.com/fmtlib/fmt/issues/1628>`_,
`#1629 <https://github.com/fmtlib/fmt/pull/1629>`_
`#1631 <https://github.com/fmtlib/fmt/issues/1631>`_,
`#1633 <https://github.com/fmtlib/fmt/pull/1633>`_).
`#1633 <https://github.com/fmtlib/fmt/pull/1633>`_,
`#1649 <https://github.com/fmtlib/fmt/pull/1649>`_).
Thanks `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
`@gabime (Gabi Melman) <https://github.com/gabime>`_,
`@johnor (Johan) <https://github.com/johnor>`_,
Expand Down Expand Up @@ -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 <https://github.com/fmtlib/fmt/issues/1476>`_,
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Format objects of user-defined types via a simple `extension API

.. code:: c++

#include "fmt/format.h"
#include <fmt/format.h>

struct date {
int year, month, day;
Expand Down
27 changes: 21 additions & 6 deletions include/fmt/format-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,27 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
}

template <typename T>
const char basic_data<T>::digits[] =
"0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839"
"4041424344454647484950515253545556575859"
"6061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899";
const typename basic_data<T>::digit_pair basic_data<T>::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 <typename T>
const char basic_data<T>::hex_digits[] = "0123456789abcdef";
Expand Down
12 changes: 7 additions & 5 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,9 @@ template <typename T = void> 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[];
Expand Down Expand Up @@ -903,15 +905,15 @@ inline format_decimal_result<Char*> 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<unsigned>((value % 100) * 2));
copy2(out, data::digits[value % 100]);
value /= 100;
}
if (value < 10) {
*--out = static_cast<Char>('0' + value);
return {out, end};
}
out -= 2;
copy2(out, data::digits + static_cast<unsigned>(value * 2));
copy2(out, data::digits[value]);
return {out, end};
}

Expand Down Expand Up @@ -1085,12 +1087,12 @@ template <typename Char, typename It> It write_exponent(int exp, It it) {
*it++ = static_cast<Char>('+');
}
if (exp >= 100) {
const char* top = data::digits + (exp / 100) * 2;
const char* top = data::digits[exp / 100];
if (exp >= 1000) *it++ = static_cast<Char>(top[0]);
*it++ = static_cast<Char>(top[1]);
exp %= 100;
}
const char* d = data::digits + exp * 2;
const char* d = data::digits[exp];
*it++ = static_cast<Char>(d[0]);
*it++ = static_cast<Char>(d[1]);
return it;
Expand Down
60 changes: 59 additions & 1 deletion include/fmt/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -341,6 +342,63 @@ class file {

// Returns the memory page size.
long getpagesize();

class direct_buffered_file;

template <typename S, typename... Args>
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 <typename S, typename... Args>
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<int>(remaining_size);
}
};
#endif // FMT_USE_FCNTL

#ifdef FMT_LOCALE
Expand Down
3 changes: 2 additions & 1 deletion test/gtest-extra.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
20 changes: 20 additions & 0 deletions test/os-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down

0 comments on commit 6616837

Please sign in to comment.