Summary
Four allocation/syscall inefficiencies on the Python bindings' batch
path, found during the #1238 review. None is a correctness bug; all are
per-file costs on entry points now documented for large-tree use
(analyze_paths(root, skip_generated=False)).
Findings
- Double
stat per file — analyze_path
(big-code-analysis-py/src/analysis.rs) runs path.is_dir()
immediately before read_file_with_eol, whose first line is
fs::metadata(path). Two stat(2) per file, every file, both entry
points. The directory probe only matters when the read cannot
produce Ok(Some), so it can move off the hot path — but the
EISDIR-masking comment in analyze_path documents subtle platform
behaviour, so the restructure needs care rather than a drive-by.
FuncSpace.name allocated before the gates — the owned path
String is built ahead of both the read gate and the generated
filter and dropped unused for every skipped file. The non-UTF-8
check must stay ahead of the read (error-ordering contract); the
allocation need not.
attach_or_keep clones the whole metrics JSON per file per
injector (big-code-analysis-py/src/batch.rs) to have a fallback,
even when the injector is a no-op (untracked file). With
vcs=True, vcs_per_function=True that is two full-JSON clones per
file. An injector signature of PyResult<Option<String>>
(None = unchanged) removes the clone on both the no-op and success
paths.
VcsRepoCache::resolve_root allocates two PathBufs per file on
a cache hit — one to build the lookup key, one to clone the value.
Arc<Path> values (refcount bump on hit) or a borrowed-key lookup
removes both.
Non-findings (checked during the same review)
Summary
Four allocation/syscall inefficiencies on the Python bindings' batch
path, found during the #1238 review. None is a correctness bug; all are
per-file costs on entry points now documented for large-tree use
(
analyze_paths(root, skip_generated=False)).Findings
statper file —analyze_path(
big-code-analysis-py/src/analysis.rs) runspath.is_dir()immediately before
read_file_with_eol, whose first line isfs::metadata(path). Twostat(2)per file, every file, both entrypoints. The directory probe only matters when the read cannot
produce
Ok(Some), so it can move off the hot path — but theEISDIR-masking comment in
analyze_pathdocuments subtle platformbehaviour, so the restructure needs care rather than a drive-by.
FuncSpace.nameallocated before the gates — the owned pathStringis built ahead of both the read gate and the generatedfilter and dropped unused for every skipped file. The non-UTF-8
check must stay ahead of the read (error-ordering contract); the
allocation need not.
attach_or_keepclones the whole metrics JSON per file perinjector (
big-code-analysis-py/src/batch.rs) to have a fallback,even when the injector is a no-op (untracked file). With
vcs=True, vcs_per_function=Truethat is two full-JSON clones perfile. An injector signature of
PyResult<Option<String>>(
None= unchanged) removes the clone on both the no-op and successpaths.
VcsRepoCache::resolve_rootallocates twoPathBufs per file ona cache hit — one to build the lookup key, one to clone the value.
Arc<Path>values (refcount bump on hit) or a borrowed-key lookupremoves both.
Non-findings (checked during the same review)
py.None()per fix(py): batch docs promise 1:1 results under skip_generated=False, but tiny/binary files still drop slots #1238 placeholder is a refcount bump on an immortalsingleton — nothing to hoist.
one metadata call, one open, and at most a 64-byte probe read.
analyze_batch'spaths.len()preallocation is exact underskip_generated=falsesince fix(py): batch docs promise 1:1 results under skip_generated=False, but tiny/binary files still drop slots #1238;analyze_paths' now accounts formissing seeds too.