67 static constexpr std::size_t
kChunk = 1024;
70 : capacity_(capacity) {}
73 : capacity_(capacity) {
reset(
size, std::move(initial)); }
75 : capacity_(other.capacity_), size_(other.size_), initial_(other.initial_),
76 spilled_(other.spilled_), pages_(other.pages_), backing_(other.backing_) {}
80 *
this = std::move(copy);
85 : capacity_(other.capacity_), size_(other.size_), initial_(
std::move(other.initial_)),
86 spilled_(other.spilled_), pages_(
std::move(other.pages_)),
87 backing_(
std::move(other.backing_)), stats_(other.stats_) {
88 absorb_cache_stats(other.read_cache_stats());
89 other.clear_moved_from();
92 noexcept(std::is_nothrow_move_assignable_v<T>) {
97 cache_failure_ =
nullptr;
98 capacity_ = other.capacity_;
100 initial_ = std::move(other.initial_);
101 spilled_ = other.spilled_;
102 pages_ = std::move(other.pages_);
103 backing_ = std::move(other.backing_);
104 stats_ = other.stats_;
105 absorb_cache_stats(other.read_cache_stats());
106 other.clear_moved_from();
113 if (
size > std::numeric_limits<std::size_t>::max() /
sizeof(T))
115 {{
"resource",
"layout"}},
"window byte extent is not representable");
117 initial_ = std::move(initial);
118 spilled_ = std::is_arithmetic_v<T> && capacity_.pages < page_count();
122 cache_failure_ =
nullptr;
124 if (!spilled_) pages_.resize(page_count());
127 T
read(std::size_t index)
const {
128 const std::size_t page = index /
kChunk, offset = index %
kChunk;
129 if (!spilled_)
return pages_[page] ? pages_[page]->values[offset] : initial_;
130 if constexpr (std::is_arithmetic_v<T>) {
132 if (!backing_)
return initial_;
134 std::array<T, kChunk> transfer;
135 cache_failure_ =
nullptr;
136 const auto status = cache_->get(cache_key(page),
137 {
reinterpret_cast<std::byte*
>(transfer.data()), page_size(page) *
sizeof(T)});
139 std::rethrow_exception(std::exchange(cache_failure_, {}));
140 require_cache_status(
status);
141 return transfer[offset];
147 const std::size_t page = index /
kChunk, offset = index %
kChunk;
149 auto& stored = pages_[page];
150 if (!stored) stored = std::make_shared<Page>(page_size(page), initial_);
151 else if (!stored.unique()) stored = std::make_shared<Page>(*stored);
152 stored->values[offset] = std::move(
value);
155 if constexpr (std::is_arithmetic_v<T>) {
159 if (cache_) require_cache_status(cache_->invalidate(cache_key(page)));
160 make_writable_backing();
161 write_backing(*backing_, index, &
value, 1);
168 if (!spilled_) pages_[page].reset();
169 if (cache_) require_cache_status(cache_->invalidate(cache_key(page)));
172 std::size_t
size()
const {
return size_; }
175 const auto bytes =
sizeof(T) *
kChunk;
177 const auto requested = spilled_ ? std::min(capacity_.pages, page_count()) : 0;
179 const auto slots = line ? std::min(
requested,
180 std::numeric_limits<std::size_t>::max() / line) : 0;
188 auto result = stats_;
190 add_counter(result.hits, shared.hits);
191 add_counter(result.misses, shared.misses);
192 add_counter(result.evictions, shared.evictions);
193 add_counter(result.bypass_reads, shared.bypass_reads);
194 add_counter(result.allocation_bypasses, shared.allocation_bypass_count);
195 result.scratch_backed = spilled_;
196 result.workspace_bytes = spilled_ ?
sizeof(T) *
kChunk : 0;
197 result.resident_metadata_bytes =
sizeof(*this) +
198 pages_.capacity() *
sizeof(std::shared_ptr<Page>) +
199 shared.current_metadata_bytes;
200 result.resident_payload_bytes = shared.current_payload_bytes;
201 for (
const auto& page : pages_) {
203 if constexpr (std::is_same_v<T, bool>) {
204 constexpr auto bits_per_byte = std::numeric_limits<unsigned char>::digits;
205 result.resident_payload_bytes += page->values.capacity() / bits_per_byte +
206 (page->values.capacity() % bits_per_byte != 0);
208 result.resident_payload_bytes += page->values.capacity() *
sizeof(T);
210 result.resident_metadata_bytes +=
sizeof(Page);
212 if (backing_) result.resident_metadata_bytes +=
sizeof(Backing);
218 std::vector<T> values;
219 Page(std::size_t
size,
const T& initial) : values(
size, initial) {}
227 file.open(
"pineforge-window-");
230 std::size_t page_count() const noexcept {
return size_ /
kChunk + (size_ %
kChunk != 0); }
231 std::size_t page_size(std::size_t page)
const {
234 void ensure_cache()
const {
237 ReadThroughCacheBacking{
const_cast<WindowValues*
>(
this), &cache_read,
nullptr});
238 require_cache_status(cache_->status());
240 static ReadThroughCacheKey cache_key(std::size_t page)
noexcept {
243 return {1, 0,
static_cast<std::uint64_t
>(
page)};
245 static ReadThroughCacheIoResult cache_read(
void* context, ReadThroughCacheKey
key,
246 ScratchBuffer destination)
noexcept {
248 if (
key.backing_identity != 1 ||
key.generation != 0
249 ||
key.page_number >= owner.page_count() || !destination.data
250 || !destination.size || destination.size %
sizeof(T) != 0
251 || destination.size > owner.page_size(
static_cast<std::size_t
>(
key.page_number)) *
sizeof(T))
254 owner.read_backing(
static_cast<std::size_t
>(
key.page_number) *
kChunk,
255 destination.data, destination.size /
sizeof(T));
260 owner.cache_failure_ = std::current_exception();
267 "window read cache contract failed");
269 static void add_counter(std::uint64_t&
value, std::uint64_t count)
noexcept {
270 value += std::min(count, std::numeric_limits<std::uint64_t>::max() -
value);
272 void absorb_cache_stats(
const ReadThroughCacheStats& shared)
noexcept {
273 add_counter(stats_.hits, shared.hits);
274 add_counter(stats_.misses, shared.misses);
275 add_counter(stats_.evictions, shared.evictions);
276 add_counter(stats_.bypass_reads, shared.bypass_reads);
277 add_counter(stats_.allocation_bypasses, shared.allocation_bypass_count);
279 void clear_moved_from() noexcept {
281 cache_failure_ =
nullptr;
287 void read_backing(std::size_t index,
void* values, std::size_t count)
const {
288 std::lock_guard<std::mutex> lock(backing_->io_mutex);
289 add_counter(stats_.backing_read_attempts, 1);
290 try { backing_->file.read(index *
sizeof(T), values, count *
sizeof(T)); }
291 catch (
const std::system_error&) { add_counter(stats_.io_errors, 1);
throw; }
292 add_counter(stats_.backing_reads, 1);
293 add_counter(stats_.backing_read_bytes, count *
sizeof(T));
295 void write_backing(Backing& backing, std::size_t index,
const T* values,
297 std::lock_guard<std::mutex> lock(backing.io_mutex);
298 add_counter(stats_.backing_write_attempts, 1);
299 try { backing.file.write(index *
sizeof(T), values, count *
sizeof(T)); }
300 catch (
const std::system_error&) { add_counter(stats_.io_errors, 1);
throw; }
301 add_counter(stats_.backing_writes, 1);
302 add_counter(stats_.backing_write_bytes, count *
sizeof(T));
304 void make_writable_backing() {
305 if (backing_ && backing_.unique())
return;
306 auto replacement = std::make_shared<Backing>();
307 std::array<T, kChunk> transfer;
308 if (!backing_) transfer.fill(initial_);
309 for (std::size_t page = 0;
page != page_count(); ++
page) {
310 const auto count = page_size(page);
311 if (backing_) read_backing(page *
kChunk, transfer.data(), count);
312 write_backing(*replacement, page *
kChunk, transfer.data(), count);
315 backing_ = std::move(replacement);
318 WindowCacheCapacity capacity_;
319 std::size_t size_ = 0;
320 T initial_ =
na<T>();
321 bool spilled_ =
false;
322 std::vector<std::shared_ptr<Page>> pages_;
323 std::shared_ptr<Backing> backing_;
324 mutable std::optional<ReadThroughCache> cache_;
325 mutable std::exception_ptr cache_failure_;
326 mutable WindowCacheStats stats_;