perf(host): write the log in batches, a few writes a second instead of one per line #1675
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1675
Loading…
Reference in a new issue
No description provided.
Delete branch "perf/log-batch"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The problem
stderris unbuffered (hos_main.c), so every line the log writer takes is a write of its own. In play that is 30–90 writes a second, each one extendinghookshot_log.txtin the game folder.With the game on a slow disk (a SATA SSD, 90% full, on my PC) those small appends keep the disk busy:
hookshot.exe, the game's files) waits just as long.The fix
log_writejoins the lines it is given into onefwrite. On the text-mode stream that is oneWriteFile, or a few for a large batch.log_mainwaits up toHOS_LOG_BATCH_MSso the lines of that time go out together:0behaves as before.log_queuewakes it at each line.log_flushat exit, inhos_fataland at the window's close, andlog_crash_filter), and the writer holds no lock while it waits.TerminateProcess) could lose up to that much. Before, it could lose whatever the writer had not reached yet.Measured
Test setup:
HOS_LOG_BATCH_MS=0)* That 367 ms frame overlapped a page-file read on the other disk (memory pressure), not the game disk.
The effect is intermittent: one of the three unbatched runs was clean. In the second run, a cluster of 40–111 ms frames lined up with game-disk reads of 20–53 ms and with 800–2,000 pages read in by hard faults in each of those frames (
hookshot.exeis on that disk). The average stays at 60 fps (capped).Tests
tests/test_log_batch.pyextracts the Windows log block ofhost/hos_thread.cunmodified, withfputs,fwriteandfflushas spies. It runs on real threads, SRW locks and condition variables, and checks five cases:0writes a line at once.log_flushwrites what waits, and the writer's wait then ends with nothing written twice.It is Windows only;
run_all.pyskips it on Linux. GCC's C front end warns thatlog_main(afor (;;)thread) has no return statement, so the fixture turns-Wreturn-typeoff around the extracted block only; clang, which builds the game, does not warn.Results:
main's writer failsbatch,defaultandflush.Sleepin place of the condition-variable wait failsburst.Also checked:
host/hos_thread.ccompiles with-Wall -Wextrawith the same single warning asmain(an unused parameter at the file's end).tests/run_all.py: 225 checks, 33 skipped.test_log_batch.pypasses. Two checks fail for reasons of this PC, as onmain:test_precompile_compute_guard.pyneedswindows.hfor an MSVC target.test_nup_paths.pyneeds the symlink privilege.🤖 Generated with Claude Code
Read through the writer changes and I don't see a problem.
Nit: the Python source checks are red on both OSes. I couldn't pull the logs (the run was still going), but it looks like the known main breakage from #1764. A merge of main (#1780 is in) should clear it. Worth checking that the Windows job runs test_log_batch.py and passes, since that's the only place it runs.