ifcparse: inline the file reader accessors and pre-size the inverse index

full_buffer_impl::size(), get(), get_u32() and get_u64() (and their mmap_impl
twins) were defined out of line, so every character the lexer read crossed a
call boundary with its own bounds check. Callgrind put the three at 5.6% of
parse self time; inlining them lets the compiler hoist the checks out of the
scanning loops, which is worth more than their own cost.

Also reserve the streamer's inverse vector from the file size (about one
record per 32 bytes of SPF on real models) and shrink it once the bulk load
is sorted, so the doubling copies and the capacity slack go away.

Parse time, C++ file constructor, 12-core Linux box:
  TXG            58 MB   1.35 s -> 1.15 s
  210_King      148 MB   3.70 s -> 3.09 s
  OKgate22      232 MB   6.18 s -> 5.30 s
Python ifcopenshell.open(): 1.40 -> 1.22, 3.70 -> 3.24, 6.27 -> 5.52 s.
Memory unchanged. The removed exported symbols mean the Python wrapper must
be rebuilt against this library.

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-11 16:06:08 +10:00
parent 23f3874de1
commit e0b6122d17
4 changed files with 51 additions and 62 deletions
+3
View File
@@ -2412,6 +2412,9 @@ void ifcopenshell::impl::in_memory_file_storage::read_from_stream(Reader* s, con
std::vector<std::string> schemas;
instance_streamer<Reader> streamer(s, file, logger_.get());
// One inverse record per ~32 bytes of SPF text is a slight over-estimate on
// real models; reserving avoids the doubling copies and the capacity slack.
streamer.inverses().reserve(s->size() / 32);
streamer.yield_header_instances(false);
if (const auto* header = streamer.header()) {