mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 11:33:38 +00:00
ifcparse: cache the current page in the paged file reader
Every peek(), get() and SWAR word read on a paged reader went through the
page cache's hash map and LRU list, so tokenizing through pages ran at a
fifth of the speed of the in-memory buffer. The reader now remembers the
page its cursor was last on and serves reads that fall inside it from the
pointer, revalidated against an eviction counter on the implementation
so a page that left the cache is never read through a stale pointer.
Reads that straddle a page boundary take the existing paths.
The whole tokenizer over each file, 64 KB pages, 64 cached (4 MB):
in-memory buffer paged before paged after
TXG 58 MB 200 MB/s 42 MB/s 142 MB/s
210_King 148 MB 181 MB/s 34 MB/s 118 MB/s
OKgate22 232 MB 197 MB/s 38 MB/s 128 MB/s
This is the step that makes reading in pages a candidate for the default
path rather than a fallback; the remaining gap is the page fetch itself.
This commit was written by an AI coding tool and has not been verified by
a human.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wcN7XquTfUi4vsKQ4KchL
This commit is contained in:
@@ -209,6 +209,7 @@ void paged_file_impl::evict_() const {
|
||||
const size_t victim = lru_.back();
|
||||
lru_.pop_back();
|
||||
map_.erase(victim);
|
||||
++evictions_;
|
||||
}
|
||||
|
||||
#ifdef USE_MMAP
|
||||
|
||||
@@ -140,6 +140,9 @@ public:
|
||||
if (cursor_ >= size()) {
|
||||
throw std::out_of_range("peek at EOF");
|
||||
}
|
||||
if (const char* p = cached_(cursor_, 1)) {
|
||||
return *p;
|
||||
}
|
||||
return impl_->get(cursor_);
|
||||
}
|
||||
|
||||
@@ -147,6 +150,11 @@ public:
|
||||
if (remaining() < sizeof(uint64_t)) {
|
||||
throw std::out_of_range("peek_u64 at EOF");
|
||||
}
|
||||
if (const char* p = cached_(cursor_, sizeof(uint64_t))) {
|
||||
uint64_t value;
|
||||
std::memcpy(&value, p, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
return impl_->get_u64(cursor_);
|
||||
}
|
||||
|
||||
@@ -154,6 +162,11 @@ public:
|
||||
if (remaining() < sizeof(uint32_t)) {
|
||||
throw std::out_of_range("peek_u32 at EOF");
|
||||
}
|
||||
if (const char* p = cached_(cursor_, sizeof(uint32_t))) {
|
||||
uint32_t value;
|
||||
std::memcpy(&value, p, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
return impl_->get_u32(cursor_);
|
||||
}
|
||||
|
||||
@@ -187,12 +200,49 @@ public:
|
||||
}
|
||||
|
||||
char get(size_t position) const {
|
||||
if (const char* p = cached_(position, 1)) {
|
||||
return *p;
|
||||
}
|
||||
return impl_->get(position);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Impl> impl_;
|
||||
size_t cursor_ = 0;
|
||||
|
||||
// For the paged implementation: the page the cursor was last on, so
|
||||
// consecutive reads don't each go through the page cache. The pointer
|
||||
// is revalidated against the cache's eviction count.
|
||||
mutable const char* cached_data_ = nullptr;
|
||||
mutable size_t cached_begin_ = 0;
|
||||
mutable size_t cached_end_ = 0;
|
||||
mutable size_t cached_evictions_ = 0;
|
||||
|
||||
// A pointer to `count` bytes at `position` if they lie in one page,
|
||||
// else nullptr. Always nullptr for a contiguous implementation, whose
|
||||
// get() is already direct.
|
||||
const char* cached_(size_t position, size_t count) const {
|
||||
if constexpr (std::is_same_v<Impl, paged_file_impl>) {
|
||||
if (cached_data_ != nullptr && position >= cached_begin_ && position + count <= cached_end_ && cached_evictions_ == impl_->evictions()) {
|
||||
return cached_data_ + (position - cached_begin_);
|
||||
}
|
||||
const size_t page_size = impl_->page_size();
|
||||
const size_t index = position / page_size;
|
||||
const auto page = impl_->page(index);
|
||||
cached_data_ = page.first;
|
||||
cached_begin_ = index * page_size;
|
||||
cached_end_ = cached_begin_ + page.second;
|
||||
cached_evictions_ = impl_->evictions();
|
||||
if (position + count <= cached_end_) {
|
||||
return cached_data_ + (position - cached_begin_);
|
||||
}
|
||||
return nullptr;
|
||||
} else {
|
||||
(void)position;
|
||||
(void)count;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class IFC_PARSE_API full_buffer_impl {
|
||||
@@ -243,6 +293,18 @@ public:
|
||||
paged_file_impl(const std::string& path, size_t page_size, size_t page_capacity);
|
||||
~paged_file_impl();
|
||||
|
||||
// One page's bytes; the page stays valid until capacity() further pages
|
||||
// have been fetched.
|
||||
std::pair<const char*, size_t> page(size_t index) const {
|
||||
const auto& p = fetchPage_(index);
|
||||
return {p.data.data(), p.data.size()};
|
||||
}
|
||||
size_t page_size() const { return page_size_; }
|
||||
size_t capacity() const { return capacity_; }
|
||||
const std::string& path() const { return fn_; }
|
||||
// Incremented whenever a page leaves the cache, so a pointer into a
|
||||
// page can be checked for validity cheaply.
|
||||
size_t evictions() const { return evictions_; }
|
||||
size_t size() const;
|
||||
char get(size_t position) const;
|
||||
uint32_t get_u32(size_t position) const;
|
||||
@@ -262,6 +324,7 @@ private:
|
||||
size_t capacity_ = 8;
|
||||
mutable std::list<size_t> lru_;
|
||||
mutable std::unordered_map<size_t, entry> map_;
|
||||
mutable size_t evictions_ = 0;
|
||||
};
|
||||
|
||||
#ifdef USE_MMAP
|
||||
|
||||
Reference in New Issue
Block a user