ifcparse: lazy loading, opt in with file::lazy_loading() / open(lazy=True)

A lazy open reads the DATA section once with the tokenizer's index
policy and builds what indexes the file: a shell per instance (name and
declaration, no attribute array), the complete inverse index with
attribute indices, the GlobalId map and the by-type lists. No attribute
value is decoded. The first time an instance's attributes are touched,
ensure_loaded() seeks the retained paged reader to the instance and runs
the same load_attributes() the full parse runs, with inverse registration
off, then resolves that instance's references from its own slots. A
modified instance is materialised first, so writing works.

There is no scanner of its own: the index pass consumes next<index_tokens>()
and counts parentheses and commas on the operator tokens; a keyword where
an instance should start, or a token the tokenizer rejects, stops the
index and the file is parsed in full. The offset of each instance's
attribute list is kept in one sorted vector that exists only in lazy
mode, so a full parse pays nothing for it. Materialising from several
threads at once is not safe.

TXG 58 MB / 210_King 147 MB / OKgate22 231 MB, single thread: lazy open
0.61 / 1.73 / 2.86 s against the full parse's 1.05 / 2.69 / 4.99 s, at
141 / 374 / 534 MB against 274 / 654 / 1036 MB; reading one attribute of
every instance afterwards costs a further 0.56 / 1.44 / 4.86 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-14 07:23:22 +10:00
parent 91622b97b3
commit a92bebd73e
9 changed files with 506 additions and 7 deletions
+2
View File
@@ -150,6 +150,7 @@ void RocksDbSerializer::write_streaming_() {
std::vector<express::base> simple_type_instances;
data->ensure_loaded();
for (size_t i = 0; i < data->storage_->size(); i++) {
auto val = data->get_attribute_value(i);
val.apply_visitor([&](const auto& t) {
@@ -291,6 +292,7 @@ void RocksDbSerializer::write_streaming_() {
}
if (decl->is(*ifcroot_type)) {
// @nb attribute counts are not coerced, so the attribute may be absent
data->ensure_loaded();
const bool has_guid = data->storage_->size() > 0 && data->get_attribute_value(0).type() == ifcopenshell::Argument_STRING;
if (has_guid) {
size_t v = name;