mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 14:23:53 +00:00
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:
@@ -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
@@ -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
@@ -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)) |
|
||||||
|
|||||||
Reference in New Issue
Block a user