perf(within-transform): drop the redundant full-frame copy in within_transform#567
Conversation
…transform within_transform -- the two-way fixed-effects demeaning helper shared by TwoWayFixedEffects, SunAbraham, WooldridgeDiD, and BaconDecomposition -- copied the entire input frame defensively before pd.concat-ing the demeaned columns onto it. But the demean is read-only and concat does not mutate its inputs, so that copy is pure overhead. Compute the demeaned columns first, then attach them as a single consolidated block via pd.concat with no defensive pre-copy (under pandas copy-on-write the original columns are shared, not copied). Peak RSS of a wide TwoWayFixedEffects(vcov_type="hc1") fit drops ~8% (964 -> 886 MB at 400k units x 6 covariates); the win scales with panel width. Bit-identical: proven at atol=0 for TWFE (incl. the survey replicate-weight path), SunAbraham, Wooldridge, and Bacon -- frame assembly only, the demean arithmetic is unchanged. The inplace/suffix params are decoupled (inplace = in-place attach vs concat; suffix = column naming), matching demean_by_group. New unit tests lock the modes + the no-fragmentation many-column path. PR-C of the memory-scaling work. The ImputationDiD/TwoStageDiD _iterative_demean vectorization is deferred (TODO) -- it is non-bit-identical on pandas 3.0 and analytical-path-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Overall Assessment ✅ Looks good — no unmitigated P0/P1 findings. Executive Summary
Methodology No findings. Severity: None Code Quality P3 informational: Performance No findings. Severity: None Maintainability No findings. Severity: None Tech Debt P3 informational: The related Security No findings. Severity: None Documentation/Tests P3 informational: The new tests cover non-mutating default behavior, |
…ng work Adds benchmarks/speed_review/bench_memory_scaling.py -- a subprocess-isolated peak-RSS sweep (ru_maxrss, median of repeats) for the bootstrap-chunking and within-transform memory wins (B1 #561 / B2 #563 / C #567) -- plus a "Memory scaling" section in docs/performance-plan.md with the honest before/after table. Measured pre-#561 (un-optimized) vs current main, 999 bootstrap reps: - CallawaySantAnna bootstrap: 500k 12.9->2.1 GB (-84%), 1M 13.5->3.0 GB (-78%) - EfficientDiD bootstrap 500k: 8.3->1.6 GB (-81%) - HeterogeneousAdoptionDiD cband 500k: 7.7->1.2 GB (-84%) - TWFE within-transform fit: 500k 1.0->0.93 GB (-8%), 1M 1.7->1.5 GB (-9%) Closes the 3-PR memory-scaling initiative. The baseline is re-measured against the pre-#561 tree (not a monkeypatch of current code, which would be contaminated by the same-line optimizations). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng work Adds benchmarks/speed_review/bench_memory_scaling.py -- a subprocess-isolated peak-RSS sweep (ru_maxrss, median of repeats) for the bootstrap-chunking and within-transform memory wins (B1 #561 / B2 #563 / C #567) -- plus a "Memory scaling" section in docs/performance-plan.md with the honest before/after table. Measured pre-#561 (un-optimized) vs current main, 999 bootstrap reps: - CallawaySantAnna bootstrap: 500k 12.9->2.1 GB (-84%), 1M 13.5->3.0 GB (-78%) - EfficientDiD bootstrap 500k: 8.3->1.6 GB (-81%) - HeterogeneousAdoptionDiD cband 500k: 7.7->1.2 GB (-84%) - TWFE within-transform fit: 500k 1.0->0.93 GB (-8%), 1M 1.7->1.5 GB (-9%) Closes the 3-PR memory-scaling initiative. The baseline is re-measured against the pre-#561 tree (not a monkeypatch of current code, which would be contaminated by the same-line optimizations). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
within_transformdrops its redundant defensive full-frame copy. The two-way fixed-effects demeaning helper — shared byTwoWayFixedEffects,SunAbraham,WooldridgeDiD, andBaconDecomposition— copied the entire input frame beforepd.concat-ing the demeaned columns onto it. But the demean is read-only andconcatdoes not mutate its inputs, so that copy was pure overhead. It now computes the demeaned columns first and attaches them as a single consolidated block viapd.concatwith no pre-copy (under pandas copy-on-write the original columns are shared, not copied).TwoWayFixedEffects(vcov_type="hc1")fit (964 → 886 MB at 400k units × 6 covariates); the win scales with panel width.inplace/suffixare decoupled (inplace = attach in place vs concat; suffix = column naming), matchingdemean_by_group. The non-inplace path drops a colliding target name before the concat sosuffix=""/ re-demeaning a frame that already carries the suffix overwrite cleanly (one label, no duplicate)._iterative_demeanvectorization is deferred (TODO.md) — itsnp.bincountform is non-bit-identical on pandas 3.0 (~5.8e-11, the same order as the demean'stol=1e-10) and it is analytical-path-only.Methodology references
docs/methodology/REGISTRY.mdTWFE within-transform (FWL) section. Frame-assembly-only change; the demeaning arithmetic and weighted alternating-projection warning path are untouched.Validation
tests/test_within_transform.py(8 tests — the inplace/suffix modes, overwrite-without-duplicate on both the inplace and concat paths, weighted parity, and the no-PerformanceWarningmany-column path).atol=0for TWFE (including the survey replicate-weight path), SunAbraham, Wooldridge, and Bacon via a pre/post estimate capture; full TWFE / SunAbraham / Wooldridge / Bacon / survey / Conley regression green (879 python + 383 rust). Peak RSS 964 → 886 MB on a wide TWFE fit.Security / privacy
🤖 Generated with Claude Code