Double buffering no more

This commit is contained in:
Victor Zverovich 2020-07-10 07:50:37 -07:00
parent 36406509d9
commit a2c4fed981
12 changed files with 109 additions and 112 deletions

View File

@ -101,7 +101,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
-Wold-style-cast -Wundef
-Wredundant-decls -Wwrite-strings -Wpointer-arith
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
-Wcast-align -Wnon-virtual-dtor
-Wcast-align
-Wctor-dtor-privacy -Wdisabled-optimization
-Winvalid-pch -Woverloaded-virtual
-Wconversion -Wswitch-enum

View File

@ -587,8 +587,7 @@ template <typename CompiledFormat, typename... Args,
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
basic_memory_buffer<Char> buffer;
detail::buffer<Char>& base = buffer;
cf.format(std::back_inserter(base), args...);
cf.format(detail::buffer_appender<Char>(buffer), args...);
return to_string(buffer);
}

View File

@ -629,18 +629,18 @@ template <typename T> class buffer {
T* ptr_;
size_t size_;
size_t capacity_;
bool fixed_;
protected:
// Don't initialize ptr_ since it is not accessed to save a few cycles.
FMT_SUPPRESS_MSC_WARNING(26495)
buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz), fixed_(false) {}
buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {}
buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0,
bool fixed = false) FMT_NOEXCEPT : ptr_(p),
size_(sz),
capacity_(cap),
fixed_(fixed) {}
buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT
: ptr_(p),
size_(sz),
capacity_(cap) {}
~buffer() = default;
/** Sets the buffer data and capacity. */
void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
@ -657,7 +657,6 @@ template <typename T> class buffer {
buffer(const buffer&) = delete;
void operator=(const buffer&) = delete;
virtual ~buffer() = default;
T* begin() FMT_NOEXCEPT { return ptr_; }
T* end() FMT_NOEXCEPT { return ptr_ + size_; }
@ -677,24 +676,26 @@ template <typename T> class buffer {
/** Returns a pointer to the buffer data. */
const T* data() const FMT_NOEXCEPT { return ptr_; }
/**
Resizes the buffer. If T is a POD type new elements may not be initialized.
*/
void resize(size_t new_size) {
reserve(new_size);
size_ = new_size;
}
/** Clears this buffer. */
void clear() { size_ = 0; }
/** Reserves space to store at least *capacity* elements. */
void reserve(size_t new_capacity) {
// Tries resizing the buffer to contain *count* elements. If T is a POD type
// the new elements may not be initialized.
void try_resize(size_t count) {
try_reserve(count);
size_ = count <= capacity_ ? count : capacity_;
}
// Tries increasing the buffer capacity to *new_capacity*. It can increase the
// capacity by a smaller amount than requested but guarantees there is space
// for at least one additional element either by increasing the capacity or by
// flushing the buffer if it is full.
void try_reserve(size_t new_capacity) {
if (new_capacity > capacity_) grow(new_capacity);
}
void push_back(const T& value) {
reserve(size_ + 1);
try_reserve(size_ + 1);
ptr_[size_++] = value;
}
@ -707,12 +708,6 @@ template <typename T> class buffer {
}
};
// A fixed capacity buffer.
template <typename T> class fixed_buffer : buffer<T> {
public:
fixed_buffer(T* data, size_t capacity) : buffer<T>(data, 0, capacity, true) {}
};
// A container-backed buffer.
template <typename Container>
class container_buffer : public buffer<typename Container::value_type> {
@ -1815,7 +1810,10 @@ OutputIt vformat_to(
OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto& c = detail::get_container(out);
detail::container_buffer<remove_reference_t<decltype(c)>> buf(c);
using container = remove_reference_t<decltype(c)>;
typename std::conditional<
std::is_same<container, detail::buffer<Char>>::value,
detail::buffer<Char>&, detail::container_buffer<container>>::type buf(c);
detail::vformat_to(buf, to_string_view(format_str), args);
return out;
}

View File

@ -145,7 +145,7 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
// Report error code making sure that the output fits into
// inline_buffer_size to avoid dynamic memory allocation and potential
// bad_alloc.
out.resize(0);
out.try_resize(0);
static const char SEP[] = ": ";
static const char ERROR_STR[] = "error ";
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
@ -156,7 +156,7 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
++error_code_size;
}
error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
auto it = std::back_inserter(out);
auto it = buffer_appender<char>(out);
if (message.size() <= inline_buffer_size - error_code_size)
format_to(it, "{}{}", message, SEP);
format_to(it, "{}{}", ERROR_STR, error_code);
@ -1051,7 +1051,7 @@ void fallback_format(Double d, buffer<char>& buf, int& exp10) {
if (result > 0 || (result == 0 && (digit % 2) != 0))
++data[num_digits - 1];
}
buf.resize(to_unsigned(num_digits));
buf.try_resize(to_unsigned(num_digits));
exp10 -= num_digits - 1;
return;
}
@ -1075,7 +1075,7 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
buf.push_back('0');
return 0;
}
buf.resize(to_unsigned(precision));
buf.try_resize(to_unsigned(precision));
std::uninitialized_fill_n(buf.data(), precision, '0');
return -precision;
}
@ -1113,7 +1113,7 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
fallback_format(value, buf, exp);
return exp;
}
buf.resize(to_unsigned(handler.size));
buf.try_resize(to_unsigned(handler.size));
} else {
if (precision > 17) return snprintf_float(value, precision, specs, buf);
fp normalized = normalize(fp(value));
@ -1131,7 +1131,7 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
++exp;
}
}
buf.resize(to_unsigned(num_digits));
buf.try_resize(to_unsigned(num_digits));
}
return exp - cached_exp10;
}
@ -1182,19 +1182,20 @@ int snprintf_float(T value, int precision, float_specs specs,
? snprintf_ptr(begin, capacity, format, precision, value)
: snprintf_ptr(begin, capacity, format, value);
if (result < 0) {
buf.reserve(buf.capacity() + 1); // The buffer will grow exponentially.
// The buffer will grow exponentially.
buf.try_reserve(buf.capacity() + 1);
continue;
}
auto size = to_unsigned(result);
// Size equal to capacity means that the last character was truncated.
if (size >= capacity) {
buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'.
buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'.
continue;
}
auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
if (specs.format == float_format::fixed) {
if (precision == 0) {
buf.resize(size);
buf.try_resize(size);
return 0;
}
// Find and remove the decimal point.
@ -1204,11 +1205,11 @@ int snprintf_float(T value, int precision, float_specs specs,
} while (is_digit(*p));
int fraction_size = static_cast<int>(end - p - 1);
std::memmove(p, p + 1, to_unsigned(fraction_size));
buf.resize(size - 1);
buf.try_resize(size - 1);
return -fraction_size;
}
if (specs.format == float_format::hex) {
buf.resize(size + offset);
buf.try_resize(size + offset);
return 0;
}
// Find and parse the exponent.
@ -1234,7 +1235,7 @@ int snprintf_float(T value, int precision, float_specs specs,
fraction_size = static_cast<int>(fraction_end - begin - 1);
std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size));
}
buf.resize(to_unsigned(fraction_size) + offset + 1);
buf.try_resize(to_unsigned(fraction_size) + offset + 1);
return exp - fraction_size;
}
}
@ -1373,7 +1374,8 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
int result =
detail::safe_strerror(error_code, system_message, buf.size());
if (result == 0) {
format_to(std::back_inserter(out), "{}: {}", message, system_message);
format_to(detail::buffer_appender<char>(out), "{}: {}", message,
system_message);
return;
}
if (result != ERANGE)

View File

@ -374,7 +374,7 @@ reserve(std::back_insert_iterator<Container> it, size_t n) {
template <typename T>
inline buffer_appender<T> reserve(buffer_appender<T> it, size_t n) {
buffer<T>& buf = get_container(it);
buf.reserve(buf.size() + n);
buf.try_reserve(buf.size() + n);
return it;
}
@ -572,7 +572,7 @@ template <typename T>
template <typename U>
void buffer<T>::append(const U* begin, const U* end) {
size_t new_size = size_ + to_unsigned(end - begin);
reserve(new_size);
try_reserve(new_size);
std::uninitialized_copy(begin, end,
make_checked(ptr_ + size_, capacity_ - size_));
size_ = new_size;
@ -628,7 +628,7 @@ class basic_memory_buffer : public detail::buffer<T> {
}
protected:
void grow(size_t size) FMT_OVERRIDE;
void grow(size_t size) final;
public:
using value_type = T;
@ -638,7 +638,7 @@ class basic_memory_buffer : public detail::buffer<T> {
: alloc_(alloc) {
this->set(store_, SIZE);
}
~basic_memory_buffer() FMT_OVERRIDE { deallocate(); }
~basic_memory_buffer() { deallocate(); }
private:
// Move data from other to this buffer.
@ -682,6 +682,15 @@ class basic_memory_buffer : public detail::buffer<T> {
// Returns a copy of the allocator associated with this buffer.
Allocator get_allocator() const { return alloc_; }
/**
Resizes the buffer to contain *count* elements. If T is a POD type new
elements may not be initialized.
*/
void resize(size_t count) { this->try_resize(count); }
/** Increases the buffer capacity to *new_capacity*. */
void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }
};
template <typename T, size_t SIZE, typename Allocator>

View File

@ -349,25 +349,26 @@ 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 {
class direct_buffered_file : private detail::buffer<char> {
private:
file file_;
enum { buffer_size = 4096 };
char buffer_[buffer_size];
int pos_;
char buffer_[BUFSIZ];
void flush() {
if (pos_ == 0) return;
file_.write(buffer_, pos_);
pos_ = 0;
if (size() == 0) return;
file_.write(buffer_, size());
clear();
}
int free_capacity() const { return buffer_size - pos_; }
int free_capacity() const { return static_cast<int>(BUFSIZ - size()); }
protected:
void grow(size_t) final;
public:
direct_buffered_file(cstring_view path, int oflag)
: file_(path, oflag), pos_(0) {}
direct_buffered_file(const char* path, int oflag)
: buffer<char>(buffer_, 0, BUFSIZ), file_(path, oflag) {}
~direct_buffered_file() { flush(); }
@ -379,21 +380,7 @@ class direct_buffered_file {
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);
fmt::format_to(detail::buffer_appender<char>(f), format_str, args...);
}
};
#endif // FMT_USE_FCNTL

View File

@ -103,7 +103,7 @@ void format_value(buffer<Char>& buf, const T& value,
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.resize(buf.size());
buf.try_resize(buf.size());
}
// Formats an object of type T that has an overloaded ostream operator<<.

View File

@ -181,7 +181,7 @@ template <typename Char> class printf_width_handler {
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format();
Context(buffer_appender<Char>(buf), format, args).format();
}
} // namespace detail
@ -598,7 +598,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
template <typename Char>
using basic_printf_context_t =
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
basic_printf_context<detail::buffer_appender<Char>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;

View File

@ -124,7 +124,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
if (result != 0) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
return;
}
break;
@ -313,5 +313,9 @@ long getpagesize() {
return size;
# endif
}
void direct_buffered_file::grow(size_t) {
if (this->size() == BUFSIZ) flush();
}
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

View File

@ -34,6 +34,7 @@ using fmt::detail::buffer;
using fmt::detail::value;
using testing::_;
using testing::Return;
using testing::StrictMock;
namespace {
@ -84,16 +85,16 @@ template <typename T> struct test_buffer : buffer<T> {
};
template <typename T> struct mock_buffer : buffer<T> {
MOCK_METHOD1(do_grow, void(size_t capacity));
MOCK_METHOD1(do_grow, size_t(size_t capacity));
void grow(size_t capacity) {
this->set(this->data(), capacity);
do_grow(capacity);
void grow(size_t capacity) { this->set(this->data(), do_grow(capacity)); }
mock_buffer(T* data = nullptr, size_t capacity = 0) {
this->set(data, capacity);
ON_CALL(*this, do_grow(_))
.WillByDefault(
testing::Invoke([](size_t capacity) { return capacity; }));
}
mock_buffer() {}
mock_buffer(T* data) { this->set(data, 0); }
mock_buffer(T* data, size_t capacity) { this->set(data, capacity); }
};
TEST(BufferTest, Ctor) {
@ -120,22 +121,9 @@ TEST(BufferTest, Ctor) {
}
}
struct dying_buffer : test_buffer<int> {
MOCK_METHOD0(die, void());
~dying_buffer() { die(); }
private:
virtual void avoid_weak_vtable();
};
void dying_buffer::avoid_weak_vtable() {}
TEST(BufferTest, VirtualDtor) {
typedef StrictMock<dying_buffer> stict_mock_buffer;
stict_mock_buffer* mock_buffer = new stict_mock_buffer();
EXPECT_CALL(*mock_buffer, die());
buffer<int>* buffer = mock_buffer;
delete buffer;
TEST(BufferTest, Indestructible) {
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
"buffer's destructor is protected");
}
TEST(BufferTest, Access) {
@ -149,30 +137,39 @@ TEST(BufferTest, Access) {
EXPECT_EQ(42, const_buffer[3]);
}
TEST(BufferTest, Resize) {
TEST(BufferTest, TryResize) {
char data[123];
mock_buffer<char> buffer(data, sizeof(data));
buffer[10] = 42;
EXPECT_EQ(42, buffer[10]);
buffer.resize(20);
buffer.try_resize(20);
EXPECT_EQ(20u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
EXPECT_EQ(42, buffer[10]);
buffer.resize(5);
buffer.try_resize(5);
EXPECT_EQ(5u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
EXPECT_EQ(42, buffer[10]);
// Check if resize calls grow.
// Check if try_resize calls grow.
EXPECT_CALL(buffer, do_grow(124));
buffer.resize(124);
buffer.try_resize(124);
EXPECT_CALL(buffer, do_grow(200));
buffer.resize(200);
buffer.try_resize(200);
}
TEST(BufferTest, TryResizePartial) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15));
buffer.try_resize(20);
EXPECT_EQ(buffer.capacity(), 15);
EXPECT_EQ(buffer.size(), 15);
}
TEST(BufferTest, Clear) {
test_buffer<char> buffer;
buffer.resize(20);
buffer.resize(0);
buffer.try_resize(20);
buffer.try_resize(0);
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(20u, buffer.capacity());
}
@ -184,7 +181,7 @@ TEST(BufferTest, Append) {
buffer.append(test, test + 5);
EXPECT_STREQ(test, &buffer[0]);
EXPECT_EQ(5u, buffer.size());
buffer.resize(10);
buffer.try_resize(10);
EXPECT_CALL(buffer, do_grow(12));
buffer.append(test, test + 2);
EXPECT_EQ('t', buffer[10]);
@ -196,7 +193,7 @@ TEST(BufferTest, AppendAllocatesEnoughStorage) {
char data[19];
mock_buffer<char> buffer(data, 10);
const char* test = "abcdefgh";
buffer.resize(10);
buffer.try_resize(10);
EXPECT_CALL(buffer, do_grow(19));
buffer.append(test, test + 9);
}
@ -255,7 +252,7 @@ template <typename T> struct mock_visitor {
template <typename U> struct result { typedef test_result type; };
mock_visitor() {
ON_CALL(*this, visit(_)).WillByDefault(testing::Return(test_result()));
ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
}
MOCK_METHOD1_T(visit, test_result(T value));

View File

@ -297,7 +297,7 @@ TEST(MemoryBufferTest, Grow) {
mock_allocator<int> alloc;
struct TestMemoryBuffer : Base {
TestMemoryBuffer(Allocator alloc) : Base(alloc) {}
void grow(size_t size) { Base::grow(size); }
using Base::grow;
} buffer((Allocator(&alloc)));
buffer.resize(7);
using fmt::detail::to_unsigned;

View File

@ -151,7 +151,8 @@ TEST(OStreamTest, WriteToOStreamMaxSize) {
if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return;
struct test_buffer : fmt::detail::buffer<char> {
explicit test_buffer(size_t size) { resize(size); }
explicit test_buffer(size_t size)
: fmt::detail::buffer<char>(nullptr, size, size) {}
void grow(size_t) {}
} buffer(max_size);