ifcparse: inline the tokenizer's hot helpers and the paged cursor check

callgrind on the tokenizer showed SWAR::has_special_char and eq_mask
compiled as calls, one per eight bytes; paged_file_impl::size() out of line
behind every eof() and remaining(); and the cursor's page-cache check not
inlined into peek() because it shared a function with the page fetch. The
SWAR helpers are forced inline, size() is defined in the class, and
cached_() is split into an inline check and an out-of-line refresh.

TXG (58 MB), single thread: tokenizer 196 -> 204 MB/s in memory and
136 -> 193 MB/s through 64 KB pages; strict parse through pages
1.15 -> 0.97 s against 0.93 s in memory; lazy index pass over pages
218 -> 311 MB/s; lazy open 0.52 -> 0.43 s.

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:
Dion Moult
2026-09-13 19:40:17 +10:00
parent d647050bcf
commit a026fc698c
3 changed files with 49 additions and 18 deletions
-2
View File
@@ -109,8 +109,6 @@ paged_file_impl::~paged_file_impl() {
fp_ = nullptr; fp_ = nullptr;
} }
size_t paged_file_impl::size() const { return file_size_; }
char paged_file_impl::get(size_t pos) const { char paged_file_impl::get(size_t pos) const {
if (pos >= file_size_) { if (pos >= file_size_) {
throw std::out_of_range("get out of range"); throw std::out_of_range("get out of range");
+35 -10
View File
@@ -47,6 +47,14 @@
#include <boost/iostreams/device/mapped_file.hpp> #include <boost/iostreams/device/mapped_file.hpp>
#endif #endif
// The cursor accessors sit on the tokenizer's innermost loop, one call per
// byte; left to the compiler's heuristics some of them end up as calls.
#if defined(_MSC_VER)
#define IFC_READER_INLINE __forceinline
#else
#define IFC_READER_INLINE inline __attribute__((always_inline))
#endif
namespace ifcopenshell { namespace ifcopenshell {
struct file_reader_page { struct file_reader_page {
@@ -134,9 +142,9 @@ public:
size_t tell() const { return cursor_; } size_t tell() const { return cursor_; }
size_t size() const { return impl_->size(); } size_t size() const { return impl_->size(); }
size_t remaining() const { return size() - cursor_; } IFC_READER_INLINE size_t remaining() const { return size() - cursor_; }
char peek() const { IFC_READER_INLINE char peek() const {
if (cursor_ >= size()) { if (cursor_ >= size()) {
throw std::out_of_range("peek at EOF"); throw std::out_of_range("peek at EOF");
} }
@@ -146,7 +154,7 @@ public:
return impl_->get(cursor_); return impl_->get(cursor_);
} }
uint64_t peek_u64() const { IFC_READER_INLINE uint64_t peek_u64() const {
if (remaining() < sizeof(uint64_t)) { if (remaining() < sizeof(uint64_t)) {
throw std::out_of_range("peek_u64 at EOF"); throw std::out_of_range("peek_u64 at EOF");
} }
@@ -158,7 +166,7 @@ public:
return impl_->get_u64(cursor_); return impl_->get_u64(cursor_);
} }
uint32_t peek_u32() const { IFC_READER_INLINE uint32_t peek_u32() const {
if (remaining() < sizeof(uint32_t)) { if (remaining() < sizeof(uint32_t)) {
throw std::out_of_range("peek_u32 at EOF"); throw std::out_of_range("peek_u32 at EOF");
} }
@@ -170,7 +178,7 @@ public:
return impl_->get_u32(cursor_); return impl_->get_u32(cursor_);
} }
void increment(size_t count = 1) { IFC_READER_INLINE void increment(size_t count = 1) {
if (cursor_ + count > size()) { if (cursor_ + count > size()) {
throw std::out_of_range("increment past EOF"); throw std::out_of_range("increment past EOF");
} }
@@ -189,17 +197,17 @@ public:
impl_->drop_pages(up_to_position); impl_->drop_pages(up_to_position);
} }
bool eof() const { IFC_READER_INLINE bool eof() const {
return cursor_ >= size(); return cursor_ >= size();
} }
char read() { IFC_READER_INLINE char read() {
auto c = peek(); auto c = peek();
increment(1); increment(1);
return c; return c;
} }
char get(size_t position) const { IFC_READER_INLINE char get(size_t position) const {
if (const char* p = cached_(position, 1)) { if (const char* p = cached_(position, 1)) {
return *p; return *p;
} }
@@ -221,11 +229,28 @@ private:
// A pointer to `count` bytes at `position` if they lie in one page, // A pointer to `count` bytes at `position` if they lie in one page,
// else nullptr. Always nullptr for a contiguous implementation, whose // else nullptr. Always nullptr for a contiguous implementation, whose
// get() is already direct. // get() is already direct.
const char* cached_(size_t position, size_t count) const { IFC_READER_INLINE const char* cached_(size_t position, size_t count) const {
if constexpr (std::is_same_v<Impl, paged_file_impl>) { 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()) { if (cached_data_ != nullptr && position >= cached_begin_ && position + count <= cached_end_ && cached_evictions_ == impl_->evictions()) {
return cached_data_ + (position - cached_begin_); return cached_data_ + (position - cached_begin_);
} }
return cached_refresh_(position, count);
} else {
(void)position;
(void)count;
return nullptr;
}
}
// The slow half of cached_(): fetches the page and re-points the cache.
// Kept out of line so the check above inlines into every peek.
#if defined(_MSC_VER)
__declspec(noinline)
#else
__attribute__((noinline))
#endif
const char* cached_refresh_(size_t position, size_t count) const {
if constexpr (std::is_same_v<Impl, paged_file_impl>) {
const size_t page_size = impl_->page_size(); const size_t page_size = impl_->page_size();
const size_t index = position / page_size; const size_t index = position / page_size;
const auto page = impl_->page(index); const auto page = impl_->page(index);
@@ -305,7 +330,7 @@ public:
// Incremented whenever a page leaves the cache, so a pointer into a // Incremented whenever a page leaves the cache, so a pointer into a
// page can be checked for validity cheaply. // page can be checked for validity cheaply.
size_t evictions() const { return evictions_; } size_t evictions() const { return evictions_; }
size_t size() const; size_t size() const { return file_size_; }
char get(size_t position) const; char get(size_t position) const;
uint32_t get_u32(size_t position) const; uint32_t get_u32(size_t position) const;
uint64_t get_u64(size_t position) const; uint64_t get_u64(size_t position) const;
+14 -6
View File
@@ -179,6 +179,14 @@ bool parse_num_(const char* pStart, size_t size, T& val) {
} // namespace } // namespace
// These helpers sit on the tokenizer's innermost loop; left to the
// compiler's heuristics they end up as calls, one per eight bytes.
#if defined(_MSC_VER)
#define IFC_SWAR_INLINE __forceinline
#else
#define IFC_SWAR_INLINE inline __attribute__((always_inline))
#endif
namespace SWAR { namespace SWAR {
constexpr uint32_t ONES32 = 0x01010101u; constexpr uint32_t ONES32 = 0x01010101u;
constexpr uint32_t HIGHS32 = 0x80808080u; constexpr uint32_t HIGHS32 = 0x80808080u;
@@ -189,19 +197,19 @@ constexpr uint64_t splat(unsigned char c) {
return ONES * c; return ONES * c;
} }
inline uint32_t has_zero_byte(uint32_t x) { IFC_SWAR_INLINE uint32_t has_zero_byte(uint32_t x) {
return (x - ONES32) & ~x & HIGHS32; return (x - ONES32) & ~x & HIGHS32;
} }
inline uint64_t has_zero_byte(uint64_t x) { IFC_SWAR_INLINE uint64_t has_zero_byte(uint64_t x) {
return (x - ONES) & ~x & HIGHS; return (x - ONES) & ~x & HIGHS;
} }
inline uint32_t eq_mask(uint32_t x, uint32_t c) { IFC_SWAR_INLINE uint32_t eq_mask(uint32_t x, uint32_t c) {
return has_zero_byte(x ^ c); return has_zero_byte(x ^ c);
} }
inline uint64_t eq_mask(uint64_t x, uint64_t c) { IFC_SWAR_INLINE uint64_t eq_mask(uint64_t x, uint64_t c) {
return has_zero_byte(x ^ c); return has_zero_byte(x ^ c);
} }
@@ -223,7 +231,7 @@ constexpr uint64_t dot = splat('.');
} // namespace chars } // namespace chars
template <bool IncludeDot = true> template <bool IncludeDot = true>
inline uint64_t has_special_char(uint64_t x) { IFC_SWAR_INLINE uint64_t has_special_char(uint64_t x) {
return eq_mask(x, chars::lpar) | return eq_mask(x, chars::lpar) |
eq_mask(x, chars::rpar) | eq_mask(x, chars::rpar) |
eq_mask(x, chars::eq) | eq_mask(x, chars::eq) |
@@ -239,7 +247,7 @@ inline uint64_t has_special_char(uint64_t x) {
} }
template <bool IncludeDot = true> template <bool IncludeDot = true>
inline uint32_t has_special_char(uint32_t x) { IFC_SWAR_INLINE uint32_t has_special_char(uint32_t x) {
return eq_mask(x, static_cast<uint32_t>(chars::lpar)) | return eq_mask(x, static_cast<uint32_t>(chars::lpar)) |
eq_mask(x, static_cast<uint32_t>(chars::rpar)) | eq_mask(x, static_cast<uint32_t>(chars::rpar)) |
eq_mask(x, static_cast<uint32_t>(chars::eq)) | eq_mask(x, static_cast<uint32_t>(chars::eq)) |