diff --git a/src/ifcparse/file_reader.cpp b/src/ifcparse/file_reader.cpp index 549b7de335..fa739cb280 100644 --- a/src/ifcparse/file_reader.cpp +++ b/src/ifcparse/file_reader.cpp @@ -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 diff --git a/src/ifcparse/file_reader.h b/src/ifcparse/file_reader.h index 66748b91f3..50322125be 100644 --- a/src/ifcparse/file_reader.h +++ b/src/ifcparse/file_reader.h @@ -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_; 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) { + 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 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 lru_; mutable std::unordered_map map_; + mutable size_t evictions_ = 0; }; #ifdef USE_MMAP