Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(buffer): make the cache size of buffers configurable #64

Merged
merged 1 commit into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 28 additions & 24 deletions include/emio/buffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

namespace emio {

/// The default cache size of buffers with an internal cache.
inline constexpr size_t default_cache_size{128};

/**
* This class provides the basic API and functionality for receiving a contiguous memory region of chars to write into.
* @note Use a specific subclass for a concrete instantiation.
Expand Down Expand Up @@ -119,9 +122,9 @@ class buffer {

/**
* This class fulfills the buffer API by providing an endless growing buffer.
* @tparam StorageSize The size of the inlined storage for small buffer optimization.
* @tparam StorageSize The size of the internal storage used for small buffer optimization.
*/
template <size_t StorageSize = 128>
template <size_t StorageSize = default_cache_size>
class memory_buffer final : public buffer {
public:
/**
Expand Down Expand Up @@ -224,7 +227,7 @@ class span_buffer : public buffer {
* @tparam StorageSize The size of the storage.
*/
template <size_t StorageSize>
class static_buffer : private std::array<char, StorageSize>, public span_buffer {
class static_buffer final : private std::array<char, StorageSize>, public span_buffer {
public:
/**
* Constructs and initializes the buffer with the storage.
Expand All @@ -243,8 +246,6 @@ class static_buffer : private std::array<char, StorageSize>, public span_buffer

namespace detail {

inline constexpr size_t internal_buffer_size{128};

// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
Container& get_container(std::back_insert_iterator<Container> it) noexcept {
Expand Down Expand Up @@ -288,17 +289,18 @@ constexpr auto copy_str(InputIt it, InputIt end, OutputIt out) -> OutputIt {
/**
* This class template is used to create a buffer around different iterator types.
*/
template <typename Iterator>
template <typename Iterator, size_t CacheSize = default_cache_size>
class iterator_buffer;

/**
* This class fulfills the buffer API by using an output iterator and an internal cache.
* @tparam Iterator The output iterator type.
* @tparam CacheSize The size of the internal cache.
*/
template <typename Iterator>
template <typename Iterator, size_t CacheSize>
requires(std::input_or_output_iterator<Iterator> &&
std::output_iterator<Iterator, detail::get_value_type_t<Iterator>>)
class iterator_buffer<Iterator> final : public buffer {
class iterator_buffer<Iterator, CacheSize> final : public buffer {
public:
/**
* Constructs and initializes the buffer with the given output iterator.
Expand Down Expand Up @@ -347,7 +349,7 @@ class iterator_buffer<Iterator> final : public buffer {

private:
Iterator it_;
std::array<char, detail::internal_buffer_size> cache_;
std::array<char, CacheSize> cache_;
};

/**
Expand Down Expand Up @@ -397,18 +399,19 @@ class iterator_buffer<OutputPtr*> final : public buffer {
/**
* This class fulfills the buffer API by using the container of an contiguous back-insert iterator.
* @tparam Container The container type of the back-insert iterator.
* @tparam Capacity The minimum initial requested capacity of the container.
*/
template <typename Container>
template <typename Container, size_t Capacity>
requires std::contiguous_iterator<typename Container::iterator>
class iterator_buffer<std::back_insert_iterator<Container>> final : public buffer {
class iterator_buffer<std::back_insert_iterator<Container>, Capacity> final : public buffer {
public:
/**
* Constructs and initializes the buffer with the given back-insert iterator.
* @param it The back-insert iterator.
*/
constexpr explicit iterator_buffer(std::back_insert_iterator<Container> it) noexcept
: container_{detail::get_container(it)} {
static_cast<void>(request_write_area(0, std::min(container_.capacity(), detail::internal_buffer_size)));
static_cast<void>(request_write_area(0, std::min(container_.capacity(), Capacity)));
}

iterator_buffer(const iterator_buffer&) = delete;
Expand Down Expand Up @@ -455,8 +458,10 @@ iterator_buffer(Iterator&&) -> iterator_buffer<std::decay_t<Iterator>>;

/**
* This class fulfills the buffer API by using a file stream and an internal cache.
* @tparam CacheSize The size of the internal cache.
*/
class file_buffer : public buffer {
template <size_t CacheSize = default_cache_size>
class file_buffer final : public buffer {
public:
/**
* Constructs and initializes the buffer with the given file stream.
Expand Down Expand Up @@ -499,14 +504,16 @@ class file_buffer : public buffer {

private:
std::FILE* file_;
std::array<char, detail::internal_buffer_size> cache_;
std::array<char, CacheSize> cache_;
};

/**
* This class fulfills the buffer API by using a primary buffer and an internal cache.
* Only a limited amount of characters is written to the primary buffer. The remaining characters are truncated.
* @tparam CacheSize The size of the internal cache.
*/
class truncating_buffer : public buffer {
template <size_t CacheSize = default_cache_size>
class truncating_buffer final : public buffer {
public:
/**
* Constructs and initializes the buffer with the given primary buffer and limit.
Expand All @@ -522,7 +529,7 @@ class truncating_buffer : public buffer {
truncating_buffer(truncating_buffer&&) = delete;
truncating_buffer& operator=(const truncating_buffer&) = delete;
truncating_buffer& operator=(truncating_buffer&&) = delete;
constexpr ~truncating_buffer() noexcept override;
constexpr ~truncating_buffer() noexcept override = default;

/**
* Returns the count of the total (not truncated) written characters.
Expand Down Expand Up @@ -564,18 +571,17 @@ class truncating_buffer : public buffer {
size_t limit_;
size_t written_{};
size_t used_{};
std::array<char, detail::internal_buffer_size> cache_;
std::array<char, CacheSize> cache_;
};

// Explicit out-of-class definition because of GCC bug: <destructor> used before its definition.
constexpr truncating_buffer::~truncating_buffer() noexcept = default;

namespace detail {

/**
* A buffer that counts the number of characters written. Discards the output.
* @tparam CacheSize The size of the internal cache.
*/
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized.
template <size_t CacheSize = default_cache_size>
class counting_buffer final : public buffer {
public:
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized.
Expand All @@ -584,7 +590,7 @@ class counting_buffer final : public buffer {
constexpr counting_buffer(counting_buffer&&) noexcept = delete;
constexpr counting_buffer& operator=(const counting_buffer&) = delete;
constexpr counting_buffer& operator=(counting_buffer&&) noexcept = delete;
constexpr ~counting_buffer() override;
constexpr ~counting_buffer() noexcept override = default;

/**
* Calculates the number of Char's that were written.
Expand All @@ -607,11 +613,9 @@ class counting_buffer final : public buffer {

private:
size_t used_{};
std::array<char, detail::internal_buffer_size> cache_;
std::array<char, CacheSize> cache_;
};

constexpr counting_buffer::~counting_buffer() = default;

} // namespace detail

} // namespace emio
68 changes: 32 additions & 36 deletions test/unit_test/test_buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ TEST_CASE("memory_buffer", "[buffer]") {

constexpr size_t first_size{15};
constexpr size_t second_size{55};
constexpr size_t third_size{emio::detail::internal_buffer_size};
constexpr size_t third_size{emio::default_cache_size};

const std::string expected_str_part_1(first_size, 'x');
const std::string expected_str_part_2(second_size, 'y');
Expand Down Expand Up @@ -144,7 +144,7 @@ TEST_CASE("span_buffer", "[buffer]") {

constexpr size_t first_size{15};
constexpr size_t second_size{55};
constexpr size_t third_size{emio::detail::internal_buffer_size};
constexpr size_t third_size{emio::default_cache_size};

std::array<char, first_size + second_size + third_size> storage{};
emio::span_buffer buf{storage};
Expand Down Expand Up @@ -254,9 +254,9 @@ TEST_CASE("counting_buffer", "[buffer]") {

constexpr size_t first_size{15};
constexpr size_t second_size{55};
constexpr size_t third_size{emio::detail::internal_buffer_size};
constexpr size_t third_size{emio::default_cache_size};

using emio::detail::internal_buffer_size;
using emio::default_cache_size;

emio::detail::counting_buffer buf;
CHECK(buf.count() == 0);
Expand All @@ -279,13 +279,13 @@ TEST_CASE("counting_buffer", "[buffer]") {

CHECK(buf.count() == (first_size + second_size + third_size));

area = buf.get_write_area_of(internal_buffer_size);
area = buf.get_write_area_of(default_cache_size);
REQUIRE(area);
REQUIRE(area->size() == internal_buffer_size);
REQUIRE(area->size() == default_cache_size);

CHECK(buf.count() == (first_size + second_size + third_size + internal_buffer_size));
CHECK(buf.count() == (first_size + second_size + third_size + default_cache_size));

area = buf.get_write_area_of(internal_buffer_size + 1);
area = buf.get_write_area_of(default_cache_size + 1);
CHECK(!area);
CHECK(buf.count() == (first_size + second_size + 2 * third_size));
}
Expand Down Expand Up @@ -328,11 +328,9 @@ TEST_CASE("iterator_buffer<iterator>", "[buffer]") {
// * Write different data lengths into the buffer to test the internal buffer and flush mechanism.
// Expected: At the end (final flush/destruction), everything is written into the string.

using emio::detail::internal_buffer_size;

const std::string expected_str_part_1(internal_buffer_size, 'x');
const std::string expected_str_part_1(emio::default_cache_size, 'x');
const std::string expected_str_part_2(2, 'y');
const std::string expected_str_part_3(internal_buffer_size, 'z');
const std::string expected_str_part_3(emio::default_cache_size, 'z');
const std::string expected_str_part_4(3, 'x');
const std::string expected_str_part_5(2, 'y');
const std::string expected_str_part_6(42, 'z');
Expand All @@ -345,11 +343,11 @@ TEST_CASE("iterator_buffer<iterator>", "[buffer]") {

CHECK(it_buf.out() == s.begin());
CHECK(it_buf.get_write_area_of(std::numeric_limits<size_t>::max()) == emio::err::eof);
CHECK(it_buf.get_write_area_of(internal_buffer_size + 1) == emio::err::eof);
CHECK(it_buf.get_write_area_of(emio::default_cache_size + 1) == emio::err::eof);

emio::result<std::span<char>> area = it_buf.get_write_area_of(internal_buffer_size);
emio::result<std::span<char>> area = it_buf.get_write_area_of(emio::default_cache_size);
REQUIRE(area);
REQUIRE(area->size() == internal_buffer_size);
REQUIRE(area->size() == emio::default_cache_size);
fill(area, 'x');

// No flush yet.
Expand All @@ -363,9 +361,9 @@ TEST_CASE("iterator_buffer<iterator>", "[buffer]") {
// 1. flush.
CHECK(s.starts_with(expected_str_part_1));

area = it_buf.get_write_area_of(internal_buffer_size);
area = it_buf.get_write_area_of(emio::default_cache_size);
REQUIRE(area);
REQUIRE(area->size() == internal_buffer_size);
REQUIRE(area->size() == emio::default_cache_size);
fill(area, 'z');

// 2. flush.
Expand Down Expand Up @@ -465,17 +463,15 @@ TEST_CASE("file_buffer", "[buffer]") {
// * Write into the buffer, flush (or not) and read out again.
// Expected: Everything is written to the file stream after flush.

using emio::detail::internal_buffer_size;

// Open a temporary file.
std::FILE* tmpf = std::tmpfile();
REQUIRE(tmpf);

emio::file_buffer file_buf{tmpf};
std::array<char, 2 * internal_buffer_size> read_out_buf{};
std::array<char, 2 * emio::default_cache_size> read_out_buf{};

// Write area is limited.
CHECK(file_buf.get_write_area_of(internal_buffer_size + 1) == emio::err::eof);
CHECK(file_buf.get_write_area_of(emio::default_cache_size + 1) == emio::err::eof);

// Write into.
emio::result<std::span<char>> area = file_buf.get_write_area_of(2);
Expand Down Expand Up @@ -514,11 +510,11 @@ TEST_CASE("file_buffer", "[buffer]") {
CHECK(std::fgets(read_out_buf.data(), read_out_buf.size(), tmpf));
CHECK(std::string_view{read_out_buf.data(), 6} == "yyzzzz");

const std::string expected_long_str_part(internal_buffer_size, 'x');
const std::string expected_long_str_part(emio::default_cache_size, 'x');

area = file_buf.get_write_area_of(internal_buffer_size);
area = file_buf.get_write_area_of(emio::default_cache_size);
REQUIRE(area);
REQUIRE(area->size() == internal_buffer_size);
REQUIRE(area->size() == emio::default_cache_size);
fill(area, 'x');

// Internal flush should have happened.
Expand All @@ -529,7 +525,7 @@ TEST_CASE("file_buffer", "[buffer]") {

std::rewind(tmpf);
CHECK(std::fgets(read_out_buf.data(), read_out_buf.size(), tmpf));
CHECK(std::string_view{read_out_buf.data(), 6 + internal_buffer_size} == "yyzzzz" + expected_long_str_part);
CHECK(std::string_view{read_out_buf.data(), 6 + emio::default_cache_size} == "yyzzzz" + expected_long_str_part);
}

TEST_CASE("truncating_buffer", "[buffer]") {
Expand Down Expand Up @@ -570,10 +566,10 @@ TEST_CASE("truncating_buffer", "[buffer]") {
}

SECTION("request more than limit (3)") {
area = buf.get_write_area_of(emio::detail::internal_buffer_size);
area = buf.get_write_area_of(emio::default_cache_size);
REQUIRE(area);
fill(area, 'c');
CHECK(buf.count() == 48 + emio::detail::internal_buffer_size);
CHECK(buf.count() == 48 + emio::default_cache_size);

// not flushed
CHECK(primary_buf.view().size() == 48);
Expand All @@ -585,10 +581,10 @@ TEST_CASE("truncating_buffer", "[buffer]") {
}
}
SECTION("request more than limit (2)") {
area = buf.get_write_area_of(emio::detail::internal_buffer_size);
area = buf.get_write_area_of(emio::default_cache_size);
REQUIRE(area);
fill(area, 'b');
CHECK(buf.count() == 40 + emio::detail::internal_buffer_size);
CHECK(buf.count() == 40 + emio::default_cache_size);

// not flushed
CHECK(primary_buf.view().size() == 40);
Expand All @@ -603,10 +599,10 @@ TEST_CASE("truncating_buffer", "[buffer]") {
const std::string expected_string = std::string(48, 'a');
emio::truncating_buffer buf{primary_buf, 48};

emio::result<std::span<char>> area = buf.get_write_area_of(emio::detail::internal_buffer_size);
emio::result<std::span<char>> area = buf.get_write_area_of(emio::default_cache_size);
REQUIRE(area);
fill(area, 'a');
CHECK(buf.count() == emio::detail::internal_buffer_size);
CHECK(buf.count() == emio::default_cache_size);

// not flushed
CHECK(primary_buf.view().size() == 0);
Expand All @@ -618,8 +614,8 @@ TEST_CASE("truncating_buffer", "[buffer]") {
}

SECTION("request more than limit (2)") {
CHECK(buf.get_write_area_of(emio::detail::internal_buffer_size));
CHECK(buf.count() == 2 * emio::detail::internal_buffer_size);
CHECK(buf.get_write_area_of(emio::default_cache_size));
CHECK(buf.count() == 2 * emio::default_cache_size);

// not flushed
CHECK(primary_buf.view().size() == 48);
Expand Down Expand Up @@ -657,7 +653,7 @@ TEST_CASE("truncating_buffer", "[buffer]") {
CHECK(buf.count() == 68);

// Requesting more will fail because primary buffer is too small.
CHECK_FALSE(buf.get_write_area_of(emio::detail::internal_buffer_size));
CHECK_FALSE(buf.get_write_area_of(emio::default_cache_size));

CHECK(primary_buf.view().size() == 64);
CHECK(primary_buf.view() == expected_string);
Expand All @@ -684,9 +680,9 @@ TEST_CASE("truncating_buffer", "[buffer]") {
const std::string expected_string = std::string(64, 'a');
emio::truncating_buffer buf{primary_buf, 64};

emio::result<std::span<char>> area = buf.get_write_area_of_max(emio::detail::internal_buffer_size + 10);
emio::result<std::span<char>> area = buf.get_write_area_of_max(emio::default_cache_size + 10);
REQUIRE(area);
CHECK(area->size() == emio::detail::internal_buffer_size);
CHECK(area->size() == emio::default_cache_size);
fill(area, 'a');

// not flushed
Expand Down
Loading