mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 12:13:43 +00:00
ifcparse: sort inverse records by radix
The inverse index is sorted once after a parse (and after a parallel merge). std::sort on 1.7–5 million 12-byte records was the largest serial phase left after the parse itself. sort_records() now does a stable LSD radix sort on referenced_id, 11 bits per pass and as many passes as the largest id needs, then applies record_less within each run of equal ids, so the order is exactly what std::sort produced. Inputs under 4096 records still use std::sort. Experiment on top of the series; measured in isolation against the previous commit, five models, best of three (strict parse 1 / 12 threads, lazy open 1 / 12 threads): TXG 58 MB −7% / −16% / −10% / −19%, 210_King 147 MB −8% / −17% / −15% / −22%, OKgate22 231 MB −6% / −12% / −11% / −17%, a 107 MB model −8% / −18% / −12% / −19%, a 523 MB model of few large instances −3% / −4% / 0% / −5%. 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:
+46
-1
@@ -467,9 +467,54 @@ namespace ifcopenshell {
|
|||||||
invalidate_materialized();
|
invalidate_materialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sorts records into record_less order. Large inputs go through a
|
||||||
|
// stable LSD radix sort on referenced_id (11 bits per pass, as many
|
||||||
|
// passes as the largest id needs) followed by record_less within
|
||||||
|
// each run of equal ids, which is the same order std::sort gives
|
||||||
|
// and several times faster on millions of records.
|
||||||
|
static void sort_records(std::vector<inverse_record>& records) {
|
||||||
|
if (records.size() < 4096) {
|
||||||
|
std::sort(records.begin(), records.end(), record_less);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t max_id = 0;
|
||||||
|
for (const auto& r : records) {
|
||||||
|
max_id = (std::max)(max_id, r.referenced_id);
|
||||||
|
}
|
||||||
|
std::vector<inverse_record> buffer(records.size());
|
||||||
|
constexpr unsigned bits = 11;
|
||||||
|
std::vector<size_t> counts((size_t)1 << bits);
|
||||||
|
for (unsigned shift = 0; shift < 32 && (max_id >> shift) != 0; shift += bits) {
|
||||||
|
std::fill(counts.begin(), counts.end(), 0);
|
||||||
|
for (const auto& r : records) {
|
||||||
|
++counts[(r.referenced_id >> shift) & ((1u << bits) - 1)];
|
||||||
|
}
|
||||||
|
size_t sum = 0;
|
||||||
|
for (auto& c : counts) {
|
||||||
|
const size_t n = c;
|
||||||
|
c = sum;
|
||||||
|
sum += n;
|
||||||
|
}
|
||||||
|
for (const auto& r : records) {
|
||||||
|
buffer[counts[(r.referenced_id >> shift) & ((1u << bits) - 1)]++] = r;
|
||||||
|
}
|
||||||
|
records.swap(buffer);
|
||||||
|
}
|
||||||
|
for (auto run = records.begin(); run != records.end();) {
|
||||||
|
auto end = run + 1;
|
||||||
|
while (end != records.end() && end->referenced_id == run->referenced_id) {
|
||||||
|
++end;
|
||||||
|
}
|
||||||
|
if (end - run > 1) {
|
||||||
|
std::sort(run, end, record_less);
|
||||||
|
}
|
||||||
|
run = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void sort() const {
|
void sort() const {
|
||||||
if (!sorted_) {
|
if (!sorted_) {
|
||||||
std::sort(base_.begin(), base_.end(), record_less);
|
sort_records(base_);
|
||||||
base_.shrink_to_fit();
|
base_.shrink_to_fit();
|
||||||
sorted_ = true;
|
sorted_ = true;
|
||||||
invalidate_materialized();
|
invalidate_materialized();
|
||||||
|
|||||||
Reference in New Issue
Block a user