Skip to content

Replace O(n) window list scan with O(1) open-addressing hash table - #50

Open
jEsuSdA wants to merge 1 commit into
tycho-kirchner:masterfrom
jEsuSdA:fix/hash-table-o1-window-lookup
Open

Replace O(n) window list scan with O(1) open-addressing hash table#50
jEsuSdA wants to merge 1 commit into
tycho-kirchner:masterfrom
jEsuSdA:fix/hash-table-o1-window-lookup

Conversation

@jEsuSdA

@jEsuSdA jEsuSdA commented Jul 2, 2026

Copy link
Copy Markdown

Summary

Replaces the O(n) linear window list scan with an O(1) open-addressing hash table, providing the single biggest performance improvement for long-running sessions with many windows.

Problem

find_win() scans the entire window list linearly to locate a window by ID. For each frame, paint_all() calls find_win() for every event window lookup. On a typical desktop with 10–50 windows, this adds up to hundreds of O(n) scans per second. As the window count grows (e.g. after hours of browsing with many tabs), this becomes a significant bottleneck.

Solution

Open-addressing hash table

  • Power-of-2 size — enables fast modulo via bitwise AND
  • Linear probing — simple and cache-friendly
  • Tombstones — for deletion without breaking probe chains. Auto-rehash when tombstones exceed live entries to prevent O(n) degradation over long sessions
  • 75% load factor trigger — automatic resize
  • OOM resilience — resize failure does not abort; insertion guards against infinite loops with a probe limit

Changes

  • find_win()win_hash_lookup() (O(1) amortized, filters out destroyed entries)
  • find_win_any_state() — new, for lookups that include destroyed windows (needed for finish_destroy_win and anti-duplicate guard)
  • add_win() — inserts into the hash table + anti-duplicate guard via find_win_any_state() (prevents duplicate win* for the same Window ID, which caused use-after-free crashes)
  • finish_destroy_win() — removes from the hash table and now accepts win* directly instead of Window ID. This fixes the zombie window leak where find_win() filtered out destroyed entries and finish_destroy_win() could never find them again

Files changed

  • cm-window.c — hash table implementation
  • cm-window.h — declarations for find_win_any_state(), win_hash_insert(), win_hash_remove()
  • fastcompmgr.c — updated add_win(), finish_destroy_win(), destroy_win(), destroy_callback()

Scope

  • make clean && make produces zero warnings, zero errors

Impact

Single biggest performance improvement for long-running sessions. On a desktop with 50 windows, find_win() goes from ~25 pointer comparisons per call to ~1–2 probes. The effect compounds across hundreds of calls per frame.

Currently, find_win() scans the entire window list linearly to locate a
window by ID. For each frame, paint_all() calls find_win() for every
event window lookup, and on a typical desktop with 10-50 windows this
adds up to hundreds of O(n) scans per second. As the window count grows,
this becomes a significant bottleneck.

Replace the linear scan with an open-addressing hash table (power-of-2
size, linear probing, tombstones for deletion, automatic rehash at 75%
load or when tombstones exceed live entries).

Changes:

- find_win() now uses hash table lookup (O(1) amortized)
- find_win_any_state() added for lookups that include destroyed windows
- add_win() inserts into the hash table (with anti-duplicate guard via
  find_win_any_state to prevent duplicate win* for the same Window ID)
- finish_destroy_win() removes from the hash table and now accepts win*
  directly instead of Window ID, fixing the zombie window leak where
  find_win() filters out destroyed entries and finish_destroy_win() could
  never find them again
- The hash table handles OOM gracefully (resize failure does not abort,
  but guards against infinite loops with a probe limit)

This is the single biggest performance improvement for long-running
sessions with many windows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant