13#include <initializer_list>
50 static_assert(std::is_trivially_copyable_v<Row>);
51 static_assert(std::is_nothrow_default_constructible_v<Row>);
52 static_assert(
sizeof(Row) <= 4096);
53 static constexpr std::size_t kRows = 4096 /
sizeof(Row);
67 *
this = std::move(copy);
72 : reader_(std::move(other.reader_)),
73 capacity_bytes_(other.capacity_bytes_), stats_(other.stats_) {
76 stats_.resident_bytes = 0;
79 other.read_failure_ =
nullptr;
80 other.stats_.resident_bytes = 0;
85 read_failure_ =
nullptr;
86 reader_ = std::move(other.reader_);
87 capacity_bytes_ = other.capacity_bytes_;
88 stats_ = other.stats_;
89 stats_.resident_bytes = 0;
92 other.read_failure_ =
nullptr;
93 other.stats_.resident_bytes = 0;
106 std::size_t
size() const noexcept
override {
return reader_ ? reader_->size() : 0; }
110 if (
empty())
throw std::out_of_range(
"feed projection back");
111 return this->
at(
size() - 1);
123 const std::shared_ptr<const FeedReader<Row>>&
reader() const noexcept {
return reader_; }
129 read_failure_ =
nullptr;
130 stats_.resident_bytes = 0;
131 capacity_bytes_ = bytes;
135 void read(std::size_t first, Row* destination,
136 std::size_t count)
const override {
137 if (first >
size() || count >
size() - first)
138 throw std::out_of_range(
"feed projection range");
139 if (count && !destination)
140 throw std::invalid_argument(
"feed projection destination");
141 if (count == 0)
return;
142 if (count > std::numeric_limits<std::size_t>::max() /
sizeof(Row))
144 increment(stats_.read_calls);
147 std::array<Row, kRows> page;
149 const auto page_first = first - first % kRows;
150 const auto rows = std::min(kRows,
size() - page_first);
151 const auto before = cache_->stats();
152 read_failure_ =
nullptr;
155 const auto status = cache_->get(
156 {0, 0,
static_cast<std::uint64_t
>(first / kRows)},
157 {
reinterpret_cast<std::byte*
>(page.data()), rows *
sizeof(Row)});
158 record_cache_delta(before);
161 std::rethrow_exception(std::exchange(read_failure_, {}));
162 throw std::logic_error(
"feed projection cache contract");
164 const auto copied = std::min(count, kRows - (first - page_first));
165 std::memcpy(destination, page.data() + (first - page_first),
166 copied *
sizeof(Row));
167 destination += copied;
173 static void increment(std::uint64_t& counter, std::uint64_t amount = 1) noexcept {
174 counter += std::min(amount, std::numeric_limits<std::uint64_t>::max() - counter);
176 void initialize_cache() noexcept {
177 if (!reader_ ||
size() == 0)
return;
178 const auto useful =
size() / kRows + (
size() % kRows != 0);
179 const auto slots = std::min(capacity_bytes_ /
page_bytes(), useful);
181 cache_.emplace(ReadThroughCachePolicy{
185 ReadThroughCacheBacking{
this,
read_page,
nullptr});
187 static ReadThroughCacheIoResult
read_page(
188 void* context, ReadThroughCacheKey
key, ScratchBuffer destination)
noexcept {
190 if (
key.backing_identity ||
key.generation
191 ||
key.page_number > std::numeric_limits<std::size_t>::max() / kRows
192 || destination.size %
sizeof(Row) != 0)
197 self.read_backing(
static_cast<std::size_t
>(
key.page_number) * kRows,
198 reinterpret_cast<Row*
>(destination.data),
199 destination.size /
sizeof(Row));
206 self.read_failure_ = std::current_exception();
210 void record_cache_delta(
const ReadThroughCacheStats& before)
const noexcept {
211 const auto after = cache_->stats();
212 increment(stats_.hits, after.hits - before.hits);
213 increment(stats_.misses, after.misses - before.misses);
214 increment(stats_.evictions, after.evictions - before.evictions);
215 increment(stats_.bypass_reads, after.bypass_reads - before.bypass_reads);
216 increment(stats_.allocation_bypasses,
217 after.allocation_bypass_count - before.allocation_bypass_count);
218 stats_.resident_bytes =
static_cast<std::size_t
>(after.current_bytes);
219 stats_.peak_resident_bytes = std::max(
220 stats_.peak_resident_bytes,
static_cast<std::size_t
>(after.peak_bytes));
222 void read_backing(std::size_t first, Row* destination, std::size_t count)
const {
223 increment(stats_.backing_reads);
225 reader_->read(first, destination, count);
227 increment(stats_.backing_errors);
230 increment(stats_.backing_bytes,
static_cast<std::uint64_t
>(count) *
sizeof(Row));
232 std::shared_ptr<const FeedReader<Row>> reader_;
233 mutable std::optional<ReadThroughCache> cache_;
234 mutable std::exception_ptr read_failure_;
236 mutable FeedProjectionStats stats_;
243 static_assert(std::is_trivially_copyable_v<Row>);
251 std::size_t
size() const noexcept
override {
return count_; }
252 void read(std::size_t first, Row* destination,
253 std::size_t count)
const override {
254 if (first > count_ || count > count_ - first)
255 throw std::out_of_range(
"scratch feed range");
256 if (count && !destination)
257 throw std::invalid_argument(
"scratch feed destination");
258 if (count == 0)
return;
259 if (count > std::numeric_limits<std::size_t>::max() /
sizeof(Row))
262 file_.read(byte_offset(first), destination, count *
sizeof(Row));
263 read_bytes_ +=
static_cast<std::uint64_t
>(count) *
sizeof(Row);
265 std::uint64_t
read_calls() const noexcept {
return read_calls_; }
266 std::uint64_t
read_bytes() const noexcept {
return read_bytes_; }
268 static std::uint64_t byte_offset(std::size_t index) {
269 if (index > std::numeric_limits<std::uint64_t>::max() /
sizeof(Row))
271 return static_cast<std::uint64_t
>(index) *
sizeof(Row);
274 std::size_t count_ = 0;
275 mutable std::uint64_t read_calls_ = 0;
276 mutable std::uint64_t read_bytes_ = 0;
283 static_assert(std::is_trivially_copyable_v<Row>);
284 static_assert(std::is_nothrow_default_constructible_v<Row>);
285 static_assert(
sizeof(Row) <= 4096);
286 static constexpr std::size_t kRows = 4096 /
sizeof(Row);
290 throw std::invalid_argument(
"resident feed projection extent");
292 std::memcpy(rows_.data(), rows, count *
sizeof(Row));
294 std::size_t
size() const noexcept
override {
return count_; }
295 void read(std::size_t first, Row* destination,
296 std::size_t count)
const override {
297 if (first > count_ || count > count_ - first)
298 throw std::out_of_range(
"resident feed range");
299 if (count && !destination)
300 throw std::invalid_argument(
"resident feed destination");
301 if (count == 0)
return;
302 std::memcpy(destination, rows_.data() + first, count *
sizeof(Row));
305 std::array<Row, kRows> rows_{};
306 std::size_t count_ = 0;
315 static_assert(std::is_trivially_copyable_v<Row>);
316 static_assert(
sizeof(Row) <= 4096);
317 static constexpr std::size_t kRows = 4096 /
sizeof(Row);
320 bool resident_if_single_page =
false)
321 : directory_(directory),
322 resident_if_single_page_(resident_if_single_page) {
323 if (!resident_if_single_page_)
330 if (published_)
throw std::logic_error(
"feed projection already published");
331 if (resident_if_single_page_ && !reader_ && buffered_ == kRows)
333 if (buffered_ == kRows) flush();
335 if (reader_->count_ > std::numeric_limits<std::size_t>::max()
339 ScratchFeedReader<Row>::byte_offset(reader_->count_ + buffered_);
343 std::memcpy(&buffer_[buffered_++], &row,
sizeof(Row));
346 if (published_)
throw std::logic_error(
"feed projection already published");
347 std::shared_ptr<const FeedReader<Row>> provider;
352 provider = std::make_shared<ResidentFeedReader<Row>>(buffer_.data(), buffered_);
360 void spill_to_scratch() {
361 reader_ = std::make_shared<ScratchFeedReader<Row>>(directory_);
366 if (buffered_ == 0)
return;
367 reader_->file_.open(
"pineforge-feed");
368 reader_->file_.write(ScratchFeedReader<Row>::byte_offset(reader_->count_),
369 buffer_.data(), buffered_ *
sizeof(Row));
371 reader_->count_ += buffered_;
374 std::string directory_;
375 bool resident_if_single_page_ =
false;
376 bool published_ =
false;
377 std::shared_ptr<ScratchFeedReader<Row>> reader_;
378 std::array<Row, kRows> buffer_;
379 std::size_t buffered_ = 0;
385 for (
const auto& row : rows) builder.
append(row);
Row at(std::size_t index) const
FeedProjectionBuilder(const std::string &directory={}, bool resident_if_single_page=false)
FeedProjectionBuilder(const FeedProjectionBuilder &)=delete
FeedProjection< Row > finish(std::size_t capacity_bytes=kFeedProjectionCacheBytes)
FeedProjectionBuilder & operator=(const FeedProjectionBuilder &)=delete
void append(const Row &row)
const std::shared_ptr< const FeedReader< Row > > & reader() const noexcept
static std::size_t page_bytes() noexcept
Row operator[](std::size_t index) const
void set_capacity_bytes(std::size_t bytes) noexcept
std::size_t capacity_bytes() const noexcept
const FeedProjectionStats & stats() const noexcept
static constexpr std::size_t page_payload_bytes() noexcept
FeedProjection & operator=(const FeedProjection &other)
static constexpr std::size_t transfer_workspace_bytes() noexcept
ReadThroughCacheStats cache_stats() const noexcept
FeedProjection(const FeedProjection &other)
std::size_t size() const noexcept override
void read(std::size_t first, Row *destination, std::size_t count) const override
FeedProjection(std::shared_ptr< const FeedReader< Row > > reader, std::size_t capacity_bytes=kFeedProjectionCacheBytes)
FeedProjection(FeedProjection &&other) noexcept
ReadThroughCachePolicy cache_policy() const noexcept
static constexpr std::size_t rows_per_page() noexcept
FeedProjection(std::initializer_list< Row > rows)
FeedProjection & operator=(FeedProjection &&other) noexcept
bool empty() const noexcept
static std::size_t line_bytes(std::size_t page_size) noexcept
std::size_t size() const noexcept override
void read(std::size_t first, Row *destination, std::size_t count) const override
ResidentFeedReader(const Row *rows, std::size_t count)
std::uint64_t read_bytes() const noexcept
std::size_t size() const noexcept override
std::uint64_t read_calls() const noexcept
ScratchFeedReader(const std::string &directory={})
void read(std::size_t first, Row *destination, std::size_t count) const override
static void check_extent(std::uint64_t offset, std::size_t length, bool arena=false, IoRaiser raiser=IoRaiser::report_sink)
void set_directory(const std::string &value)
constexpr std::size_t kFeedProjectionCacheBytes
std::uint64_t bypass_reads
std::size_t resident_bytes
std::uint64_t backing_reads
std::uint64_t allocation_bypasses
std::uint64_t backing_bytes
std::uint64_t backing_errors
std::size_t peak_resident_bytes