Parallel HAdd (phadd)
A small, dependency-free CLI that merges ROOT files in parallel using hadd
from ROOT. Instead of adding all files in a single pass,
files are merged in chunks across multiple worker processes, then the chunks
are merged again until one file remains — a tree reduction. This keeps memory
usage low and makes use of all your CPU cores.
Requirements
- Python 3.10+
- ROOT's
haddavailable on yourPATH
Development and CI run on Linux and macOS; Windows is untested.
Installation
From PyPI:
pip install parallel-hadd
With an optional rich progress bar and colored logs:
pip install "parallel-hadd[rich]"
From source:
git clone https://github.com/MohamedElashri/hadd-parallel
cd hadd-parallel
pip install .
Usage
phadd out.root *.root
where out.root is the merged output file containing all input files.
For more options see the help page:
phadd -h
Help page
usage: phadd [-h] [-j NUM_JOBS] [-n NUM_FILES] [-t TMPDIR] [-f] [-s] [--no-progress]
[--hadd-args ARGS] [-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-V]
output_file input_file [input_file ...]
Merge ROOT files in parallel using hadd.
positional arguments:
output_file path of the merged output file
input_file two or more input files (wildcards are expanded)
options:
-h, --help show this help message and exit
-j, --jobs NUM_JOBS number of parallel hadd processes [default: number of CPUs]
-n, --num-files NUM_FILES
number of files to merge per chunk, minimum 2 [default: auto
(~cuberoot of input count, minimum 10)]
-t, --tmpdir TMPDIR base directory for intermediate files [default: system temp]
-f, --force-overwrite
overwrite the output file if it exists
-s, --save-tmp keep intermediate files instead of deleting them
--no-progress disable the progress bar (automatic when not attached to a
terminal)
--hadd-args ARGS extra flags forwarded verbatim to each inner hadd call; use "="
when they start with a dash, e.g. --hadd-args="-k -v"
-l, --log {DEBUG,INFO,WARNING,ERROR,CRITICAL}
log level [default: WARNING]
-V, --version show program's version number and exit
Tip: place intermediates on fast local disk with --tmpdir when /tmp is RAM-backed or
slow. See the README for tuning guidance.
Inner hadd invocations always run with -f (never interactive) and are
quieted; their output is shown with --log DEBUG or when a merge step fails.
How it works
Given N input files and a chunk size of -n:
- Files are grouped into chunks of at most
nfiles. - Each chunk is merged by its own
haddprocess; up to-jprocesses run concurrently. - The resulting chunk outputs become the inputs of the next round.
- Rounds repeat until a single file remains, which is moved to the output path.
Intermediate files live in a temporary directory (configurable with -t) and
are deleted afterwards unless -s is given.
The final output is written atomically (staged next to the destination, then
renamed into place), so an interrupted run can never leave a half-written
output file behind. If the output path is picked up by shell globbing (e.g.
re-running phadd out.root *.root after out.root already exists), it is
excluded from the input list automatically.
Tuning & performance
Understanding the trade-offs helps you pick the right flags:
I/O amplification. A tree reduction rewrites the surviving data every
round: roughly log_n(N) full passes over the dataset for N files at chunk
size n. Sequential merging writes the data once but cannot be parallelized —
phadd trades extra I/O for wall-clock speed.
Chunk size (-n). By default phadd picks it automatically — roughly the
cube root of the input count (minimum 10) — which holds the merge at about
three rounds no matter the scale. Override it when your storage calls for
something different:
- Fast local SSD / NVMe: defaults are fine; extra passes cost little.
- Network storage (NFS/Lustre/dCache): increase
-n(e.g.-n 50) to cut the number of rounds and reduce metadata churn; parallelism then comes from fewer but larger workers. - Many small files: each inner
haddprocess pays ~0.5–2 s of ROOT startup, which dominates when chunks merge in milliseconds. Raise-nto amortize it.
Memory. Peak usage scales with jobs × chunk size: every concurrent hadd
holds its chunk's objects in memory. Lower -j or -n if you hit RAM limits.
Temporary directory. On many systems /tmp is tmpfs (RAM-backed) or slow.
For large merges place intermediates on fast local disk with -t /scratch.
Tail effect. The last rounds have few chunks (the final round is a single
task), so some cores idle near the end. This is inherent to tree reduction;
larger -n shortens that tail.
Exit codes. 0 success, 1 error, 127 hadd not found on PATH,
128+N terminated by signal N (intermediate files are cleaned up either way).
phadd vs native hadd -j
Since ROOT 6.24, plain hadd -j J merges with multiple threads. For modern
ROOT installations, try it first — it avoids phadd's multi-pass I/O entirely.
phadd is still useful when you:
- run ROOT older than 6.24, where
haddis single-threaded; - want process isolation: a corrupted input file crashes one chunk worker, not the whole merge;
- need to scale past thread contention observed by single-process threaded merging on very high-core machines or shared/login nodes;
- prefer explicit control over parallelism, temporary storage placement, and
resumable-by-inspection intermediate trees (
-s).
Known limitations
- Input file lists are validated up front; inputs added mid-run will not be seen.
- On Linux/macOS, interrupted merges clean up completely: workers and their
haddchildren run in dedicated process groups that are killed on interruption. On other platforms (e.g. Windows, untested) orphaned child processes may be left behind. - Windows is untested.
Development
git clone https://github.com/MohamedElashri/hadd-parallel
cd hadd-parallel
pip install -e '.[dev]'
pytest
ruff check . && ruff format --check .
Tests use a stub hadd executable, so no ROOT installation is required.
License
Metadata
Release files for parallel-hadd 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| parallel_hadd-2.0.0.tar.gz | 16.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| parallel_hadd-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.3 kB
Release files / parallel_hadd-2.0.0.tar.gz
| Download URL | parallel_hadd-2.0.0.tar.gz |
|---|---|
| Size | 16.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
747506c2ebfd6dd6d7d493c3caee2eb10b5f980247a7edde075abd1413e04693
|
|
BLAKE2b-256 checksum How to use checksums |
59e958ba52d79b8141c23077d1f93b9cc2140c5c61bf4a63f8d94b2ffc0fe9b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency logRelease files / parallel_hadd-2.0.0-py3-none-any.whl
| Download URL | parallel_hadd-2.0.0-py3-none-any.whl |
|---|---|
| Size | 13.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2f197213ccdd3d9630f573bb76e08f15eb19139f6e734a8028b244e399b3fbf1
|
|
BLAKE2b-256 checksum How to use checksums |
786ca20484123a970b1bb22d2a1d8a7b02a69b15eae3dc8b3a5d3180095f92f3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.
Transparency log