Repository navigation
G2Project.save() launches git and file on every call, which dominates run time for parallel scripted refinements on HPC cluster #364
Description
Summary. With GSASIIscriptable, each G2Project.refine() calls save(), and each save() starts three external processes: git twice (via GitPython) and file once (via platform.architecture()). For a single interactive refinement this costs little, but for many scripted refinements running in parallel on a shared filesystem it dominates: in my case ~90% of all CPU time was kernel overhead and per-node throughput dropped ~9×.
Environment. GSAS-II #5841 via conda (gsas2pkg, briantoby channel), Python 3.11, Linux; EPCC Cirrus, 288-core AMD nodes; conda env and GSAS-II on NFS (/epccfs). Workload: many independent serial Python processes, each running ~89 refine() calls (11-phase Rietveld, 11-step strategy).
Evidence (cluster).
strace -f -e trace=execveof one worker (~89 refines): 3,708 execve — 178 ×git, 89 ×/usr/bin/file, plus ~1,700 failed PATH lookups.- With 288 workers per node: ~50% of wall time in
wait4(waiting on child processes), ~50% inopenat/newfstatatat 6–23 ms per call (NFS). - Slurm accounting, 288 workers x 1 sequence: UserCPU 4:54 h vs SystemCPU 46:29 h.
- Throughput vs workers/node: 72 -->3,013 sequences/h; 144 -->3,013/h; 288 -->1,492/h. With the workaround below, 288 workers: 674 s --> 72 s median per sequence, ~14,000 sequences/node-h, identical refinement results (same Rwp to all digits).
Evidence (laptop-scale reproducer). The attached script builds an empty G2Project and calls save() 100 times, counting child processes via subprocess.Popen. On a git-based install: 300 child processes (200 × git, 100 × file), 11.4 ms/save on a local SSD -entirely process-spawn overhead. After the fix below: 0 child processes, 0.1 ms/save.
Cause (main @ f75620d):
save()→G2fil.get_python_versions()→platform.architecture(), which in CPython runsfile -b <sys.executable>on POSIX on every call [GSASIIscriptable.py:1021 → GSASIIfiles.py:123].save()opens a newRepoand readshead.commiton every call -->onegit cat-fileprocess per save [GSASIIscriptable.py:1025–1028].GSASIIpath.HowIsG2Installed()is documented as cached, but the tworeturn 'github'+revstatements return before settingG2_installed_result[GSASIIpath.py:208, 211]. For GitHub-based installs (the normal conda/git install) the cache is never filled, so every call reopens the repo and readsremote().urls-->onegit remote get-url --allprocess per save.
Workaround currently in use (in the worker script, before refining):
import platform as _platform
_ARCH = _platform.architecture()
_platform.architecture = lambda *a, **k: _ARCH
G2sc.GSASIIpath.G2_installed_result = 'noVCS' # version then from git_verinfo.pySuggested fix (small, behaviour-preserving; saved .gpx metadata unchanged):
- Set
G2_installed_resultbefore the'github'+revreturns inHowIsG2Installed(). - Cache the HEAD commit hash (it cannot change during a running process) instead of creating a new
Repoper save. - Use
f"{struct.calcsize('P')*8}bit"inget_python_versions()— same'64bit'string asplatform.architecture()[0], no subprocess.
Opening a PR - patch is ready and tested.
Activity
Merge pull request #365 from JohannesMeusburger/scriptable-save-no-su…
briantoby commentedon Oct 25, 2026
closed after PR #365 merged