simutrans-0001: rebuild_linked_connections() append_unique O(C*H^2) MEDIUM 97x
- vector_tpl::append_unique linear scan inside double loop over
goods categories x connections to collect unique connected halts
- fix: inthashtable_tpl for O(1) membership test
simutrans-0002: add_grund() registered_convoys.is_contained O(C*R) MEDIUM 45x
- iterates ALL world convoys, each with linear scan of registered
convoy vector to check membership
- fix: pre-build hash set of registered convoy IDs for O(1) lookup
simutrans-0003: rebuild_connections() consecutive_halts append_unique O(S^2) MEDIUM 24x
- append_unique on consecutive halt vectors per category inside
nested loop over schedules x entries during halt reconnection
- fix: parallel inthashtable_tpl for O(1) dedup
All three defects are in simhalt.cc halt reconnection paths, triggered
whenever schedules change (line added/removed, schedule edited, station
built). In large games with hundreds of halts and convoys, these
compound during reconnection sweeps.
MOAD-0002: welt (karte_t) is a god object but standard Simutrans architecture
MOAD-0003: CLEAN (no thread_local usage)
MOAD-0004: CLEAN (nettool password printf is by-design tool output)
MOAD-0005: CLEAN (save cache uses hashtable, no unsynchronized pattern)
48 lines
2.3 KiB
Diff
48 lines
2.3 KiB
Diff
--- a/src/simutrans/simhalt.cc
|
|
+++ b/src/simutrans/simhalt.cc
|
|
@@ -3295,14 +3295,19 @@
|
|
// iterate over all lines (public halt: all lines, other: only player's lines)
|
|
for( uint8 i=pl_min; i<pl_max; i++ ) {
|
|
if( player_t *player = welt->get_player(i) ) {
|
|
player->simlinemgmt.get_lines(simline_t::line, &check_line);
|
|
for(linehandle_t const j : check_line ) {
|
|
// only add unknown lines
|
|
- if( !registered_lines.is_contained(j) && j->count_convoys() > 0 ) {
|
|
+ // Use a hash set for O(1) membership test instead of
|
|
+ // vector_tpl::is_contained O(R) linear scan per line.
|
|
+ // Previously O(L * R) where L = lines, R = registered lines.
|
|
+ if( !registered_lines_set.get(j.get_id()) && j->count_convoys() > 0 ) {
|
|
for(schedule_entry_t const& k : j->get_schedule()->entries ) {
|
|
if( get_halt(k.pos, player) == self ) {
|
|
registered_lines.append(j);
|
|
+ registered_lines_set.put(j.get_id(), true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- // iterate over all convoys
|
|
- for(convoihandle_t const cnv : welt->convoys()) {
|
|
- // only check lineless convoys which have matching ownership and which are not yet registered
|
|
- if( !cnv->get_line().is_bound() && (public_halt || cnv->get_owner()==get_owner()) && !registered_convoys.is_contained(cnv) ) {
|
|
+ // iterate over all convoys
|
|
+ // Build a temporary hash set of registered convoy IDs for O(1) lookup.
|
|
+ // Previously registered_convoys.is_contained(cnv) was O(R) linear scan
|
|
+ // called for every convoy in the world = O(C * R) total.
|
|
+ inthashtable_tpl<uint16, bool> registered_cnv_set;
|
|
+ for(convoihandle_t const cnv : registered_convoys) {
|
|
+ registered_cnv_set.put(cnv.get_id(), true);
|
|
+ }
|
|
+ for(convoihandle_t const cnv : welt->convoys()) {
|
|
+ // only check lineless convoys which have matching ownership and which are not yet registered
|
|
+ if( !cnv->get_line().is_bound() && (public_halt || cnv->get_owner()==get_owner()) && !registered_cnv_set.get(cnv.get_id()) ) {
|
|
if( const schedule_t *const schedule = cnv->get_schedule() ) {
|
|
for(schedule_entry_t const& k : schedule->entries) {
|
|
if (get_halt(k.pos, cnv->get_owner()) == self) {
|
|
registered_convoys.append(cnv);
|
|
+ registered_cnv_set.put(cnv.get_id(), true);
|
|
break;
|
|
}
|
|
}
|