Skip to content

Commit

Permalink
make the cache size of the buffers configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
Viatorus committed Oct 7, 2023
1 parent a583ffa commit 5e2a498
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 67 deletions.
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

0 comments on commit 5e2a498

Please sign in to comment.