Benchmarks

How fast each building block is against the tools people would otherwise use, and what each swappable backend costs, measured on genomeblocks 2.0.0. Every comparison first checks that the engines return the same answer; timings are medians of 3–5 runs after a warm-up.

Intel(R) Xeon(R) Gold 6130 CPU @ 2.10GHz · 64 cores · 811 GB RAM · Python 3.12.12 · synthetic hg38-shaped data (seeded) · benchmarks ran one at a time and use at most 8 worker processes

intervals: fastest enginegenomeblocksmerge
bigwig: fastest enginepybigtoolssignal 10,000 x 1 track x 200 bins
tables: fastest enginepolarsLoci.make 1,000,000 peaks (keep=True)
graph: fastest enginegraph-toolbuild the engine's graph
One overlap lookup8.8 µson 100k indexed peaks: 167× faster than pyranges, 607× than bioframe
bigWig → heatmap matrix98×faster than deepTools computeMatrix on one core (13k vs 137 regions/s)
Hi-C pair counting1.6M/sread pairs into 50 kb windows: 2.9× the rate of cooler cload
  1. Backends
  2. Loci
  3. Signal
  4. Atlas
  5. Motifs
  6. Hi-C pairs
  7. Genes
  8. Architecture
  9. HiChIP short-range track
  10. Import cost
  11. Reproduce

Backends

The same call through every installed engine (backend=); every row below returns the same answer as the default, so the choice is only about speed and what is installed.

100 ms 1 s 10 s 100 s ncls: 288 ms · agrees ncls 288 ms genomeblocks: 291 ms · agrees genomeblocks 291 ms bioframe: 966 ms · agrees bioframe 966 ms pyranges: 1.3 s · agrees pyranges 1.3 s cgranges: 1.4 s · agrees cgranges 1.4 s bedtools: 9.3 s · agrees bedtools 9.3 s
intervals · A & B at n = 1,000,000; the default engine in colour.
10 ms 100 ms 1 s 10 s genomeblocks: 86 ms · agrees genomeblocks 86 ms pyranges: 574 ms · agrees pyranges 574 ms bioframe: 661 ms · agrees bioframe 661 ms bedtools: 4.9 s · agrees bedtools 4.9 s
intervals · merge at n = 1,000,000; the default engine in colour.
10 ms 100 ms 1 s 10 s genomeblocks: 30 ms · agrees genomeblocks 30 ms bioframe: 228 ms · agrees bioframe 228 ms pyranges: 392 ms · agrees pyranges 392 ms bedtools: 1.8 s · agrees bedtools 1.8 s
intervals · nearest (distances) at n = 100,000; the default engine in colour.
100 ms 1 s 10 s 100 s 1,000 s pybigtools: 455 ms · agrees pybigtools 455 ms python: 3.1 s · agrees python 3.1 s pybigwig: 4.9 min · agrees pybigwig 4.9 min
bigwig · signal 10,000 x 1 track x 200 bins at n = 10,000; the default engine in colour.
10 ms 100 ms 1 s 10 s lightmotif: 37 ms · agrees lightmotif 37 ms moods: 53 ms · agrees moods 53 ms biopython: 3.3 s · agrees biopython 3.3 s
motifs · scan 1,000 windows x 100 motifs at n = 1,000; the default engine in colour.
1 ms 10 ms 100 ms 1 s 10 s memory: 5.4 ms · agrees memory 5.4 ms genomeblocks: 37 ms · agrees genomeblocks 37 ms pysam: 40 ms · agrees pysam 40 ms pyfaidx: 135 ms · agrees pyfaidx 135 ms biopython: 5.0 s · agrees biopython 5.0 s
fasta · sequences 10,000 x 500 bp at n = 10,000; the default engine in colour.
100 ms 1 s 10 s polars: 462 ms · agrees polars 462 ms pandas: 1.2 s · agrees pandas 1.2 s
tables · Loci.make 1,000,000 peaks (keep=True) at n = 1,000,000; the default engine in colour.
100 ms 1 s 10 s 100 s polars: 550 ms · agrees polars 550 ms pandas: 9.4 s · agrees pandas 9.4 s
tables · Genes.make (GTF) at n = 70,028; the default engine in colour.
1 ms 10 ms 100 ms 1 s graph-tool: 14 ms · agrees graph-tool 14 ms scipy: 18 ms · agrees scipy 18 ms igraph: 59 ms · agrees igraph 59 ms networkx: 447 ms · agrees networkx 447 ms
graph · components (114,367 edges) at n = 114,367; the default engine in colour.
10 ms 100 ms 1 s 10 s scipy: 106 ms · agrees scipy 106 ms graph-tool: 112 ms · agrees graph-tool 112 ms igraph: 148 ms · agrees igraph 148 ms networkx: 686 ms · agrees networkx 686 ms
graph · pagerank (114,367 edges) at n = 114,367; the default engine in colour.
100 µs 1 ms 10 ms 100 ms 1 s graph-tool: 275 µs · agrees graph-tool 275 µs scipy: 6.6 ms · agrees scipy 6.6 ms igraph: 36 ms · agrees igraph 36 ms networkx: 338 ms · agrees networkx 338 ms
graph · build the engine's graph at n = 114,367; the default engine in colour.

Loci

A single overlap query against an indexed set is the cost of every interactive question (“what overlaps this peak?”): 8.8 µs per lookup through the cached point index (8.8 µs with genomeblocks (cgranges), 9.6 µs with genomeblocks (ncls), 14 µs with genomeblocks (numpy point index)), against 1.5 ms for pyranges, which has to slice a dataframe per call.

1 µs 10 µs 100 µs 1 ms 10 ms genomeblocks (cgranges): 8.8 µs · per query, 100k-peak index genomeblocks (cgranges) 8.8 µs genomeblocks (ncls): 9.6 µs · per query, 100k-peak index genomeblocks (ncls) 9.6 µs intervaltree: 10 µs · per query, 100k-peak index intervaltree 10 µs genomeblocks (numpy point index): 14 µs · per query, 100k-peak index genomeblocks (numpy point index) 14 µs pyranges: 1.5 ms · per query, 100k-peak index pyranges 1.5 ms bioframe: 5.4 ms · per query, 100k-peak index bioframe 5.4 ms
One overlap lookup against 100,000 indexed peaks (median per query; log scale).
10 ms 100 ms 1 s 10 s genomeblocks: 87 ms · n = 1,000,000 genomeblocks 87 ms pyranges: 336 ms · n = 1,000,000 pyranges 336 ms bioframe: 1.0 s · n = 1,000,000 bioframe 1.0 s genomeblocks (cgranges): 1.6 s · n = 1,000,000 genomeblocks (cgranges) 1.6 s bedtools (CLI): 1.8 s · n = 1,000,000 bedtools (CLI) 1.8 s
A & B on 1M intervals per set (rows of A overlapping B); every engine returns the same rows.
10 ms 100 ms 1 s 10 s genomeblocks: 135 ms · n = 1,000,000 genomeblocks 135 ms pyranges: 164 ms · n = 1,000,000 pyranges 164 ms bioframe: 699 ms · n = 1,000,000 bioframe 699 ms bedtools (CLI): 5.1 s · n = 1,000,000 bedtools (CLI) 5.1 s
merge on 1M intervals per set (sort + merge of A ∪ B); every engine returns the same rows.

On whole-set operations at 1M intervals the numpy kernel does A & B in 87 ms and merge in 135 ms, against 336 ms and 164 ms for pyranges; the Backends section times the other interval engines behind the same call.

All Loci timings
openginen = 1kn = 10kn = 100kn = 1M
intersectgenomeblocks268 µs901 µs7.4 ms87 ms
intersectgenomeblocks (cgranges)1.1 ms9.8 ms99 ms1.6 s
intersectgenomeblocks (cgranges, warm index)1.1 ms10 ms99 ms1.4 s
intersectpyranges49 ms44 ms62 ms336 ms
intersectbioframe7.3 ms12 ms61 ms1.0 s
intersectbedtools (CLI)16 ms36 ms169 ms1.8 s
intersectintervaltree24 ms464 ms22 s–
intersectnaive double loop36 ms2.2 s––
mergegenomeblocks366 µs2.0 ms18 ms135 ms
mergepyranges28 ms30 ms52 ms164 ms
mergebioframe16 ms23 ms102 ms699 ms
mergebedtools (CLI)27 ms92 ms543 ms5.1 s
makegenomeblocks2.2 ms6.6 ms56 ms394 ms
makepyranges5.7 ms13 ms91 ms928 ms
makepandas.read_csv1.5 ms4.2 ms29 ms253 ms

Signal

Filling a heatmap matrix (200 bins, ±3 kb) is one native call per region and track: 13k regions/s on one core, 98× deepTools computeMatrix and 386× pyBigWig’s binned stats(). Every engine returns the same matrix (deepTools differs only at bin edges, r ≥ 0.99).

10 100 1k 10k 100k genomeblocks · pybigtools (exact): 13k · 5,000 regions genomeblocks · pybigtools (exact) 13k genomeblocks · pybigtools (zoom, exact=False): 13k · 5,000 regions genomeblocks · pybigtools (zoom, exact=False) 13k pybigtools values() + numpy binning: 5.3k · 5,000 regions pybigtools values() + numpy binning 5.3k pyBigWig values() + numpy binning: 3.7k · 5,000 regions pyBigWig values() + numpy binning 3.7k genomeblocks · pure-Python reader: 2.3k · 5,000 regions genomeblocks · pure-Python reader 2.3k deepTools computeMatrix (-p 1): 137 · 5,000 regions deepTools computeMatrix (-p 1) 137 pyBigWig stats(nBins) zoom: 35 · 300 regions pyBigWig stats(nBins) zoom 35 pyBigWig stats(nBins) exact: 35 · 300 regions pyBigWig stats(nBins) exact 35
Regions per second into a 200-bin matrix from one bigWig, one core (higher is better; log scale).
1k 10k 100k 1k 10k 100k regions genomeblocks, exact=False genomeblocks, exact=False · 1k: 11k genomeblocks, exact=False · 5k: 12k genomeblocks, exact=False · 20k: 28k genomeblocks, exact=False · 50k: 45k genomeblocks, pure-Python genomeblocks, pure-Python · 1k: 1.9k genomeblocks, pure-Python · 5k: 2.7k genomeblocks, pure-Python · 20k: 3.3k genomeblocks, pure-Python · 50k: 3.6k pyBigWig values() pyBigWig values() · 1k: 3.4k pyBigWig values() · 5k: 3.9k pyBigWig values() · 20k: 4.2k pyBigWig values() · 50k: 4.2k genomeblocks (default) genomeblocks (default) · 1k: 11k genomeblocks (default) · 5k: 12k genomeblocks (default) · 20k: 28k genomeblocks (default) · 50k: 46k genomeblocks (default) genomeblocks, exact=False pyBigWig values() genomeblocks, pure-Python
Throughput vs number of regions (regions/s, log–log). The native path speeds up with batch size as fixed costs amortise; per-base readers stay flat.

Many tracks scale with processes: 16 bigWigs × 5,000 regions take 5.3 s sequentially and 1.4 s with workers=8. Threads do not help (pybigtools serialises Python threads), which is why signal() uses processes.

100 ms 1 s 10 s 100 s 1,000 s genomeblocks · processes · 8 workers: 1.4 s · 16 tracks × 5,000 regions genomeblocks · processes · 8 workers 1.4 s genomeblocks · processes · 4 workers: 2.1 s · 16 tracks × 5,000 regions genomeblocks · processes · 4 workers 2.1 s genomeblocks · processes · 2 workers: 3.6 s · 16 tracks × 5,000 regions genomeblocks · processes · 2 workers 3.6 s genomeblocks · processes · 1 worker: 5.3 s · 16 tracks × 5,000 regions genomeblocks · processes · 1 worker 5.3 s threads (same chunks) · 2 workers: 19 s · 16 tracks × 5,000 regions threads (same chunks) · 2 workers 19 s threads (same chunks) · 4 workers: 19 s · 16 tracks × 5,000 regions threads (same chunks) · 4 workers 19 s threads (same chunks) · 8 workers: 19 s · 16 tracks × 5,000 regions threads (same chunks) · 8 workers 19 s deepTools computeMatrix · 8 workers: 96 s · 16 tracks × 5,000 regions deepTools computeMatrix · 8 workers 96 s deepTools computeMatrix · 4 workers: 2.8 min · 16 tracks × 5,000 regions deepTools computeMatrix · 4 workers 2.8 min deepTools computeMatrix · 1 worker: 9.8 min · 16 tracks × 5,000 regions deepTools computeMatrix · 1 worker 9.8 min
16 bigWigs × 5,000 regions: worker processes vs threads vs deepTools -p (wall time; log scale).

Atlas

Testing a 20,000-peak query against 500 peak files is one sparse row-sum plus a vectorised Fisher test: 11 ms per query, including building the result table.

1 ms 10 ms 100 ms 1 s 10 s 100 s Atlas sparse row-sum only: 3.7 ms · 500 tracks Atlas sparse row-sum only 3.7 ms Atlas.search (with Fisher + table): 11 ms · 500 tracks Atlas.search (with Fisher + table) 11 ms per-track overlap_any loop: 442 ms · 500 tracks per-track overlap_any loop 442 ms pyranges overlap(), per track: 21 s · 500 tracks pyranges overlap(), per track 21 s
One query against 500 peak files (wall time; log scale). GIGGLE was not run (it is built from source; set GIGGLE= to include it).
Index build, load, accuracy
stepbin sizeworkerstimeindex size
build200 bp310 s190 MB
build1,000 bp17.8 s68 MB
build1,000 bp33.5 s68 MB
build5,000 bp32.8 s43 MB
load (Atlas.load (npz))266 ms
load (fresh python: import + load + search)4.2 s
accuracy vs exact overlaps200 bpSpearman 0.999top-25 shared 21
accuracy vs exact overlaps1,000 bpSpearman 0.999top-25 shared 20
accuracy vs exact overlaps5,000 bpSpearman 0.993top-25 shared 18
bootstrap (10 shuffles)8.7 ms per shuffle
bootstrap (100 shuffles)7.5 ms per shuffle

Motifs

Counting hits of 100 JASPAR motifs in 1,000 windows of 500 bp: 58 ms with genomeblocks on MOODS (the windows joined into one block, the library scanned in one pass), 131× faster than FIMO. The 993 hits are identical to those of lightmotif per window, MOODS, numpy and Biopython; FIMO reports 996.

10 ms 100 ms 1 s 10 s 100 s genomeblocks · lightmotif: 42 ms · 1,000 windows × 100 motifs genomeblocks · lightmotif 42 ms genomeblocks · MOODS (default): 58 ms · 1,000 windows × 100 motifs genomeblocks · MOODS (default) 58 ms MOODS (C++): 69 ms · 1,000 windows × 100 motifs MOODS (C++) 69 ms lightmotif, re-striped per motif: 289 ms · 1,000 windows × 100 motifs lightmotif, re-striped per motif 289 ms numpy sliding window: 5.4 s · 1,000 windows × 100 motifs numpy sliding window 5.4 s MEME FIMO (CLI): 7.6 s · 1,000 windows × 100 motifs MEME FIMO (CLI) 7.6 s Biopython PSSM.search: 11 s · 1,000 windows × 100 motifs Biopython PSSM.search 11 s
Motif scanning engines on identical PSSMs and windows (wall time; log scale).
100 ms 1 s 10 s 100 s 1,000 s genomeblocks · lightmotif · 1k windows: 368 ms · 1,000 windows × 1019 motifs genomeblocks · lightmotif · 1k windows 368 ms genomeblocks · MOODS (default) · 1k windows: 565 ms · 1,000 windows × 1019 motifs genomeblocks · MOODS (default) · 1k windows 565 ms MOODS (C++) · 1k windows: 721 ms · 1,000 windows × 1019 motifs MOODS (C++) · 1k windows 721 ms genomeblocks · lightmotif · 5k windows: 1.4 s · 5,000 windows × 1019 motifs genomeblocks · lightmotif · 5k windows 1.4 s genomeblocks · MOODS (default) · 5k windows: 1.4 s · 5,000 windows × 1019 motifs genomeblocks · MOODS (default) · 5k windows 1.4 s MOODS (C++) · 5k windows: 2.2 s · 5,000 windows × 1019 motifs MOODS (C++) · 5k windows 2.2 s MEME FIMO (CLI) · 1k windows: 78 s · 1,000 windows × 1019 motifs MEME FIMO (CLI) · 1k windows 78 s
The whole JASPAR library (1,019 motifs) on one core (wall time; log scale). Against MOODS: genomeblocks is 1.3× faster at 1,000 windows, genomeblocks is 1.6× faster at 5,000 windows. FIMO was timed at 1,000 windows only.
Worker processes
workerstimespeed-up
11.4 s1.0×
2872 ms1.6×
4641 ms2.2×
8530 ms2.7×

Hi-C pairs

Streaming 5M read pairs into 50 kb windows × partner chromosome runs at 1.6M pairs/s, close to the speed of just parsing the file, and 2.9× the rate of cooler cload.

10k 100k 1M 10M parse only (I/O floor): 2.7M · 5,000,000 pairs parse only (I/O floor) 2.7M count_pairs · one partner: 1.6M · 5,000,000 pairs count_pairs · one partner 1.6M count_pairs · 50 kb × partner: 1.6M · 5,000,000 pairs count_pairs · 50 kb × partner 1.6M count_pairs_2d · 500 kb: 1.5M · 5,000,000 pairs count_pairs_2d · 500 kb 1.5M cooler cload · 500 kb (CLI): 542k · 5,000,000 pairs cooler cload · 500 kb (CLI) 542k per-pair Python loop: 37k · 200,000 pairs per-pair Python loop 37k
Read pairs per second (higher is better; log scale). The naive loop was timed on the first 200k pairs.

Genes

A GENCODE-shaped GTF with 20,000 genes, 70,028 transcripts and 876,947 lines.

1 ms 10 ms 100 ms 1 s 10 s 100 s nearest_genes · 10,000: 16 ms nearest_genes · 10,000 16 ms annotations (genomeblocks) · 10,000: 17 ms annotations (genomeblocks) · 10,000 17 ms select_isoforms (peaks): 40 ms select_isoforms (peaks) 40 ms annotations (ncls) · 10,000: 52 ms annotations (ncls) · 10,000 52 ms nearest_genes · 100,000: 92 ms nearest_genes · 100,000 92 ms annotations (genomeblocks) · 100,000: 98 ms annotations (genomeblocks) · 100,000 98 ms annot index build (prom/exon/UTR merge): 103 ms annot index build (prom/exon/UTR merge) 103 ms annotations (ncls) · 100,000: 149 ms annotations (ncls) · 100,000 149 ms annotations (cgranges) · 10,000: 168 ms annotations (cgranges) · 10,000 168 ms annotations (bioframe) · 10,000: 194 ms annotations (bioframe) · 10,000 194 ms select_isoforms (peaks + bigWig): 292 ms select_isoforms (peaks + bigWig) 292 ms annotations (bioframe) · 100,000: 416 ms annotations (bioframe) · 100,000 416 ms annotations (pyranges) · 10,000: 426 ms annotations (pyranges) · 10,000 426 ms annotations (cgranges) · 100,000: 461 ms annotations (cgranges) · 100,000 461 ms Genes.make (polars parser): 598 ms Genes.make (polars parser) 598 ms Genes.make (GTF parse): 609 ms Genes.make (GTF parse) 609 ms annotations (pyranges) · 100,000: 649 ms annotations (pyranges) · 100,000 649 ms select_isoforms (bigWig only): 1.3 s select_isoforms (bigWig only) 1.3 s annotations (bedtools) · 10,000: 1.4 s annotations (bedtools) · 10,000 1.4 s annotations (bedtools) · 100,000: 2.6 s annotations (bedtools) · 100,000 2.6 s Genes.make (pandas parser): 9.3 s Genes.make (pandas parser) 9.3 s
Gene-model steps (wall time; log scale). Labelling uses the cached annotation index; the counts are CREs labelled.

Architecture

The pipeline on 100k CREs and 50k loops (114,367 edges), step by step, then the graph algorithms on every installed graph engine and the parquet round trip:

100 µs 1 ms 10 ms 100 ms 1 s 10 s strength (numpy bincount): 994 µs strength (numpy bincount) 994 µs pagerank (O/E weights) · graph-tool: 5.2 ms pagerank (O/E weights) · graph-tool 5.2 ms prime_hubs (elbow on strength): 8.2 ms prime_hubs (elbow on strength) 8.2 ms normalize (power-law O/E): 9.6 ms normalize (power-law O/E) 9.6 ms components · graph-tool: 14 ms components · graph-tool 14 ms support (CREs per TSS ± 5 kb): 17 ms support (CREs per TSS ± 5 kb) 17 ms components · scipy: 18 ms components · scipy 18 ms annotate (labels + genes): 35 ms annotate (labels + genes) 35 ms save (parquet): 70 ms save (parquet) 70 ms pagerank (O/E weights) · igraph: 73 ms pagerank (O/E weights) · igraph 73 ms components · igraph: 74 ms components · igraph 74 ms pagerank (O/E weights) · scipy: 81 ms pagerank (O/E weights) · scipy 81 ms load (parquet): 88 ms load (parquet) 88 ms make (50k loops → graph): 155 ms make (50k loops → graph) 155 ms components · networkx: 488 ms components · networkx 488 ms pagerank (O/E weights) · networkx: 859 ms pagerank (O/E weights) · networkx 859 ms add_mcool (5 kb): 4.1 s add_mcool (5 kb) 4.1 s
Architecture pipeline steps (wall time; log scale). Graph algorithms run once per installed engine, with identical results; the default engine in colour.

HiChIP short-range track

From allValidPairs to a coverage bigWig: 17 s and 1.0 GB peak memory with genomeblocks.hichip, against 2.3 min and 1.9 GB for the awk | sort | bedtools recipe, with the same ends and a byte-identical bedGraph.

Steps
implementationsteptimepeak memory
shellextract ends (awk)6.1 s20 MB
shellfragments + sort + genomecov2.0 min1,903 MB
shellbedGraph -> bigWig8.9 s–
genomeblocksends4.5 s–
genomeblockswrite BED897 ms–
genomeblockscoverage (bedGraph)3.6 s–
genomeblocksbigWig7.8 s–

Import cost

import genomeblocks is lazy, and from genomeblocks import Loci costs 201 ms on top of starting Python (37 ms): matplotlib, pandas, scipy and graph-tool load only when a function needs them.

1 ms 10 ms 100 ms 1 s 10 s import genomeblocks: 13 ms import genomeblocks 13 ms + Locus: 28 ms + Locus 28 ms + Architecture (graph-tool): 155 ms + Architecture (graph-tool) 155 ms + Genes: 178 ms + Genes 178 ms + Loci (pulls signal, motifs, atlas, bedpe): 201 ms + Loci (pulls signal, motifs, atlas, bedpe) 201 ms + browser (matplotlib): 544 ms + browser (matplotlib) 544 ms everything: 627 ms everything 627 ms
Import cost above python -c pass, fresh process each (cumulative statements; log scale).

Reproduce

cd benchmarks
python make_data.py            # synthetic hg38-shaped data, ~1.5 GB
PY=python ./run_all.sh         # every bench, one at a time
python build_site.py           # this page: docs/benchmarks/index.md

External baselines are found on PATH (bedtools, deepTools computeMatrix, cooler) or via FIMO=. The data are synthetic and seeded, laid out on real hg38 chromosome sizes; motifs are the JASPAR CORE vertebrate collection.

Versions: genomeblocks 2.0.0, numpy 2.3.4, scipy 1.16.3, pandas 2.3.3, polars 1.44.2, pyarrow 21.0.0, narwhals 2.26.0, pybigtools 0.2.5, pyBigWig 0.3.24, cgranges installed, ncls installed, pyranges 0.1.4, bioframe 0.8.0, pybedtools 0.12.1, lightmotif 0.10.0, Bio 1.86, MOODS installed, graph_tool 2.98 (commit c96a6bf3, ), igraph 1.0.0, networkx 3.6.1, bedtools v2.31.1


Copyright © 2024–2026 Umut Berkay Altintas. MIT Licensed.

This site uses Just the Docs, a documentation theme for Jekyll.