78 static constexpr std::size_t
kChunk = 1024;
81 : capacity_(capacity) {}
84 : capacity_(capacity) {
reset(
size, std::move(initial)); }
86 : capacity_(other.capacity_), size_(other.size_), initial_(other.initial_),
87 spilled_(other.spilled_), pages_(other.pages_), backing_(other.backing_) {}
91 *
this = std::move(copy);
96 : capacity_(other.capacity_), size_(other.size_), initial_(
std::move(other.initial_)),
97 spilled_(other.spilled_), pages_(
std::move(other.pages_)),
98 backing_(
std::move(other.backing_)), stats_(other.stats_) {
99 absorb_cache_stats(other.read_cache_stats());
100 other.clear_moved_from();
103 noexcept(std::is_nothrow_move_assignable_v<T>) {
104 if (
this != &other) {
108 cache_failure_ =
nullptr;
109 capacity_ = other.capacity_;
111 initial_ = std::move(other.initial_);
112 spilled_ = other.spilled_;
113 pages_ = std::move(other.pages_);
114 backing_ = std::move(other.backing_);
115 stats_ = other.stats_;
116 absorb_cache_stats(other.read_cache_stats());
117 other.clear_moved_from();
124 if (
size > std::numeric_limits<std::size_t>::max() /
sizeof(T))
126 {{
"resource",
"layout"}},
"window byte extent is not representable");
128 initial_ = std::move(initial);
129 spilled_ = std::is_arithmetic_v<T> && capacity_.pages < page_count();
133 cache_failure_ =
nullptr;
135 if (!spilled_) pages_.resize(page_count());
138 T
read(std::size_t index)
const {
139 const std::size_t page = index /
kChunk, offset = index %
kChunk;
140 if (!spilled_)
return pages_[page] ? pages_[page]->values[offset] : initial_;
141 if constexpr (std::is_arithmetic_v<T>) {
143 if (!backing_)
return initial_;
145 std::array<T, kChunk> transfer;
146 cache_failure_ =
nullptr;
147 const auto status = cache_->get(cache_key(page),
148 {
reinterpret_cast<std::byte*
>(transfer.data()), page_size(page) *
sizeof(T)});
150 std::rethrow_exception(std::exchange(cache_failure_, {}));
151 require_cache_status(
status);
152 return transfer[offset];
158 const std::size_t page = index /
kChunk, offset = index %
kChunk;
160 auto& stored = pages_[page];
163 stored = std::allocate_shared<Page>(allocation, allocation, page_size(page), initial_);
164 }
else if (!stored.unique()) {
166 stored = std::allocate_shared<Page>(allocation, allocation, *stored);
168 stored->values[offset] = std::move(
value);
171 if constexpr (std::is_arithmetic_v<T>) {
175 if (cache_) require_cache_status(cache_->invalidate(cache_key(page)));
176 make_writable_backing();
177 write_backing(*backing_, index, &
value, 1);
184 if (!spilled_) pages_[page].reset();
185 if (cache_) require_cache_status(cache_->invalidate(cache_key(page)));
188 std::size_t
size()
const {
return size_; }
191 const auto bytes =
sizeof(T) *
kChunk;
193 const auto requested = spilled_ ? std::min(capacity_.pages, page_count()) : 0;
196 std::numeric_limits<std::size_t>::max() /
line) : 0;
204 if (!out.visit(*
this))
return;
205 out.logical(out.product(size_,
sizeof(T)));
208 if (out.claim(pages_.data()))
209 out.metadata(out.product(pages_.capacity(),
sizeof(std::shared_ptr<Page>)));
210 for (
const auto& page : pages_) {
211 if (!page || !out.claim(page.get()))
continue;
212 page->allocation.observe_owned_storage(out);
215 if (cache_ && out.claim(&*cache_)) {
219 out.metadata(shared.current_metadata_bytes);
220 out.payload(shared.current_payload_bytes);
222 if (spilled_) out.workspace(
sizeof(T) *
kChunk);
223 if (backing_ && out.claim(backing_.get())) {
224 backing_->allocation.observe_owned_storage(out);
225 backing_->file.observe_owned_storage(out);
230 auto result = stats_;
232 add_counter(result.hits, shared.hits);
233 add_counter(result.misses, shared.misses);
234 add_counter(result.evictions, shared.evictions);
235 add_counter(result.bypass_reads, shared.bypass_reads);
236 add_counter(result.allocation_bypasses, shared.allocation_bypass_count);
237 result.scratch_backed = spilled_;
239 observation.
metadata(
sizeof(*
this));
241 const auto owned = observation.
usage();
242 const auto bounded_size = [](std::uint64_t bytes) {
243 return static_cast<std::size_t
>(std::min<std::uint64_t>(
244 bytes, std::numeric_limits<std::size_t>::max()));
247 result.resident_metadata_bytes = bounded_size(owned.resident_metadata_bytes);
248 result.resident_payload_bytes = bounded_size(owned.resident_payload_bytes);
249 result.backing_bytes = owned.backing_bytes;
250 result.logical_payload_bytes = owned.logical_payload_bytes;
251 result.storage_complete = owned.complete();
258 std::vector<T> values;
260 : allocation(owner), values(
size, initial) {}
262 : allocation(owner), values(other.values) {}
265 OwnedAllocator<Backing> allocation;
269 Backing(
const OwnedAllocator<Backing>& owner,
const ScratchIoPolicy& policy)
270 : allocation(owner) {
272 file.set_scratch_io_policy(policy);
273 file.open(
"pineforge-window-");
276 std::size_t page_count() const noexcept {
return size_ /
kChunk + (size_ %
kChunk != 0); }
277 std::size_t page_size(std::size_t page)
const {
280 void ensure_cache()
const {
283 ReadThroughCacheBacking{
const_cast<WindowValues*
>(
this), &cache_read,
nullptr});
284 require_cache_status(cache_->status());
286 static ReadThroughCacheKey cache_key(std::size_t page)
noexcept {
289 return {1, 0,
static_cast<std::uint64_t
>(
page)};
291 static ReadThroughCacheIoResult cache_read(
void* context, ReadThroughCacheKey
key,
292 ScratchBuffer destination)
noexcept {
294 if (
key.backing_identity != 1 ||
key.generation != 0
295 ||
key.page_number >= owner.page_count() || !destination.data
296 || !destination.size || destination.size %
sizeof(T) != 0
297 || destination.size > owner.page_size(
static_cast<std::size_t
>(
key.page_number)) *
sizeof(T))
300 owner.read_backing(
static_cast<std::size_t
>(
key.page_number) *
kChunk,
301 destination.data, destination.size /
sizeof(T));
306 owner.cache_failure_ = std::current_exception();
313 "window read cache contract failed");
315 static void add_counter(std::uint64_t&
value, std::uint64_t count)
noexcept {
316 value += std::min(count, std::numeric_limits<std::uint64_t>::max() -
value);
318 void absorb_cache_stats(
const ReadThroughCacheStats& shared)
noexcept {
319 add_counter(stats_.hits, shared.hits);
320 add_counter(stats_.misses, shared.misses);
321 add_counter(stats_.evictions, shared.evictions);
322 add_counter(stats_.bypass_reads, shared.bypass_reads);
323 add_counter(stats_.allocation_bypasses, shared.allocation_bypass_count);
325 void clear_moved_from() noexcept {
327 cache_failure_ =
nullptr;
333 void read_backing(std::size_t index,
void* values, std::size_t count)
const {
334 std::lock_guard<std::mutex> lock(backing_->io_mutex);
335 add_counter(stats_.backing_read_attempts, 1);
336 try { backing_->file.read(index *
sizeof(T), values, count *
sizeof(T)); }
337 catch (
const std::system_error&) { add_counter(stats_.io_errors, 1);
throw; }
338 add_counter(stats_.backing_reads, 1);
339 add_counter(stats_.backing_read_bytes, count *
sizeof(T));
341 void write_backing(Backing& backing, std::size_t index,
const T* values,
343 std::lock_guard<std::mutex> lock(backing.io_mutex);
344 add_counter(stats_.backing_write_attempts, 1);
345 try { backing.file.write(index *
sizeof(T), values, count *
sizeof(T)); }
346 catch (
const std::system_error&) { add_counter(stats_.io_errors, 1);
throw; }
347 add_counter(stats_.backing_writes, 1);
348 add_counter(stats_.backing_write_bytes, count *
sizeof(T));
350 void make_writable_backing() {
351 if (backing_ && backing_.unique())
return;
352 OwnedAllocator<Backing> allocation;
353 auto replacement = std::allocate_shared<Backing>(
354 allocation, allocation, capacity_.scratch_io_policy);
355 std::array<T, kChunk> transfer;
356 if (!backing_) transfer.fill(initial_);
357 for (std::size_t page = 0;
page != page_count(); ++
page) {
358 const auto count = page_size(page);
359 if (backing_) read_backing(page *
kChunk, transfer.data(), count);
360 write_backing(*replacement, page *
kChunk, transfer.data(), count);
363 backing_ = std::move(replacement);
366 WindowCacheCapacity capacity_;
367 std::size_t size_ = 0;
368 T initial_ =
na<T>();
369 bool spilled_ =
false;
370 std::vector<std::shared_ptr<Page>> pages_;
371 std::shared_ptr<Backing> backing_;
372 mutable std::optional<ReadThroughCache> cache_;
373 mutable std::exception_ptr cache_failure_;
374 mutable WindowCacheStats stats_;