Repository navigation
The format of this release stores the typed SAM tags in columns of their own, the
mate's position and template length as differences from what the record predicts,
and the read names, qualities and bases in blocks of 10,000 records with the codecs
CRAM 3.1 uses. On chromosome 20 of NA12878 at 30x the file goes from 622.5 MB, with
every tag as text as before #111, to 417.9 MB; CRAM 3.1 takes 302.5 MB. New are
ramstats idxstats and flagstat, ramdump -@ N and several regions in one
ramdump, and a script that reproduces the benchmarks. A reader opens only files
of its own format version, 10: files written by 0.1.0 must be converted again.
Added
bench/ramtools-bench.pyreproduces the benchmarks on any Linux or macOS
machine: it downloads chr20 of NA12878 from EBI and checks it against known
md5 sums, builds two revisions the same way, measures conversion, size, the
round trip, dumps, regions and statistics in alternating order, checks every
output against samtools, and writes a report. Seebench/README.md
(#116). On Linux it runs each command under a small helper it compiles,
because a command started from Python counted Python's 20 to 30 MB in its
peak memory:samtools idxstatsshowed 22 to 30 MB instead of 10 (#117).-fqzcompon both converters compresses lossless quality scores with
fqzcomp, the codec of CRAM 3.1, in blocks of 10,000 records. On 2 million
records of chromosome 11 the file is 8.1% smaller, conversion takes 20 to 30%
longer and a full dump about 30% longer; region counts are unchanged. It is
off by default, and files written with it need this release or later to read
their qualities (#90, #99).ramstats idxstatsprints the records per reference assamtools idxstats
does. The converters count them while writing and store the counts in
METADATA, so it reads no record: 0.2 s and 5 MB for any file size. The format
version becomes 3, and files written before must be converted again (#102).ramstats flagstatprints whatsamtools flagstatprints, in its default,
TSV (-O tsv) and JSON (-O json) formats. It reads only the FLAG,
reference, mate reference and MAPQ columns. On chr20 of NA12878 at 30x
(17.7 million records) it takes 1.3 s of CPU and 20 MB on one thread, against
9.4 s forsamtools flagstaton the BAM and 3.9 s on the CRAM 3.1. The file
format is unchanged (#108).ramdump -@ Nreads and formats the whole file on N threads, each taking
every N-th block of 10,000 records with a reader of its own; the records come
out in order. On chromosome 20 of NA12878 a full read takes 4.6 s on 8
threads and 7.9 s on 4 instead of 16.5 s on one, with 546 MB instead of
107 MB on 8;samtools view -@ 8takes 3.6 s from BAM and 7.7 s from
CRAM 3.1. With a region,-@is ignored (#124).ramdumptakes several regions and reads them in turn from the file kept
open, each region's records in order, assamtools viewdoes without-M;
-ccounts them all. On chromosome 20 of NA12878, 100 regions of 1 kb take
0.73 s in one process instead of 6.55 s in a process each (#125).
Changed
ramdumpwith a region searches for it with only the reference and position
columns open, opens the others at the first record found, and reads without
RNTuple's cluster cache, which loaded every column of each cluster the search
visited and of the next one. On chromosome 20 of NA12878 a region of 1 kb
reads 1.0 MB of the file instead of 53.4 MB and takes 68 ms and 38 MB
instead of 103 ms and 50 MB, or 80 ms instead of 246 ms from a cold cache.
A count with-for-Freads only the flags: 0.31 s instead of 1.76 s in
a 10 Mb region. Library:RAMFile::Open()takes RNTuple's read options;
without the cluster cache, a region of 1 kb in a file kept open takes 6.3 ms
instead of 34.4 ms (#119).ramstats flagstatreads its four columns 65,536 rows at a time with
RNTuple's bulk reads instead of one view call per value, which took four
fifths of its time. On chromosome 20 of NA12878 it takes 0.44 s instead of
1.38 s, against 5.9 s forsamtools flagstaton the BAM and 2.9 s on the
CRAM 3.1 (#120).bamtoramntuple -threads Nfills and compresses the records on N threads, as
samtoramntupledoes: the BAM is read in blocks of 64 MB, each filled on a
thread of its own and committed in input order, and htslib inflates the
input on N threads instead of on every core of the machine. On chromosome 20
of NA12878 a conversion on 8 threads takes 22.8 s instead of 81.2 s and
203 s of CPU instead of 357 s, with 1136 MB instead of 419 MB. The file has a
cluster per block, 115 of about 3.8 MB instead of 6 of about 70 MB, as from
SAM. A read error in the BAM now stops the conversion with a message (#122).ramdumpreads the fifteen numeric fields of a record in bulk, a run of rows
at a time, instead of one view call per value. On chromosome 20 of NA12878 a
full read takes 16.8 s instead of 18.1 s and a 10 Mb region 3.2 s instead of
3.4 s; a small region reads as many pages as before (#123).ramstats flagstatprints counts the converters store in METADATA while
they write the records, asidxstatsdoes, instead of reading four columns of
every record: on chromosome 20 of NA12878 it takes 0.05 s instead of 0.41 s,
and as long on any file.--scancounts them again from the columns. The
format version becomes 9, and files written before must be converted again
(#126).- The converters cap pages at 128 kB before compression instead of 64 kB.
Opening a file reads the page list of every cluster, and a file now has half
the pages. On all of NA12878 converted from BAM, 1.99 million pages instead
of 3.99 million:ramstats idxstatstakes 0.37 s and 157 MB instead of
0.59 s and 268 MB, a region of 1 kb 0.59 s and 209 MB instead of 0.90 s and
345 MB, and the file is 18.16 GB instead of 18.42 GB. On chromosome 20 a
region of 1 kb in a file kept open takes 8.3 ms instead of 6.4 ms, because
its pages are larger, and as long as before in a process of its own. Files
written before read as before (#127). - The bases go into blocks of 10,000 records, in a field
seqblock, compressed
with libdeflate at level 6, instead of into a column that RNTuple compresses.
ROOT runs zstd at twice the level it is given, so the converters' 505 is
zstd's level 10, which on text of four letters is both larger and twice as
slow; the converters' help and the README said level 5. On chromosome 20 of
NA12878 the file is 417.9 MB instead of 429.6 MB, and converting from SAM on
one thread takes 112.5 s instead of 128.8 s. A full read takes 15.1 s instead
of 16.3 s on one thread, and 25.7 s of CPU instead of 26.4 s: the block
readers now decode into the memory of a block they are done with, and rANS
decodes the qualities in place. Reading only the bases takes 2.9 s instead of
3.9 s, but 4.3 s of CPU instead of 3.9 s.
record.seqnow holds only*: a program that reads the columns directly
has to decodeseqblock, withSeqBlockReaderin the library. The format
version becomes 10, and files written before must be converted again.
libdeflate is the system's, or version 1.26 built from source (#128). samtoramntuple -threads Nholds up to N + 1 blocks of 64 MB in memory
instead of 2N + 1. On 2 million records of chromosome 11 the peak goes from
695 to 426 MB on 4 threads and from 1052 to 725 MB on 8, in the same time
(#98).- RAM files record a format version, and a reader opens only files of its own
version: files written by 0.1.0 must be converted again. Until 1.0 the format
may change between minor releases; see "Versioning" in the README (#101). - The SAM header is stored in METADATA instead of a ROOT
TList, so
ramdump -Hand-hno longer start ROOT's interpreter. On 2 million
records of chromosome 11,-Hgoes from 0.86 s and 50 MB to 0.35 s and 6 MB,
a 1 kb region with-hfrom 0.86 s and 90 MB to 0.40 s and 47 MB, and the
whole file with-hfrom 6.2 s and 96 MB to 5.3 s and 55 MB (#101). - Library:
RAMFile::Open()opens a RAM file and keeps its reference names
and METADATA in the object it returns instead of in static members, so
several files can be open in one process.ramntuplescan()and
QualityBlockReadertake that object (#105). - Library: the converters keep the state of the file they write, the
reference tables and the METADATA values, in their own objects instead of in
static members ofRAMNTupleRecord, so several conversions can run in one
process at once. Before, two at once mixed their reference names (#107). - The tags NM, AS, XS, MQ, RG, PG, MD, MC, SA and XA are stored in columns of
their own instead of as text, RG and PG as ids into a dictionary in METADATA.
The other tags stay text, andramdump -hstill reproduces the input byte for
byte. On chr20 of NA12878 at 30x the file is 9.5% smaller (622.5 to
563.1 MB), conversion takes 5 to 13% less CPU, andramdump -h8% less. The
format version becomes 5, and files written before must be converted again
(#111, #112). - PNEXT and TLEN are stored as the difference from what POS, the strands and
the CIGAR predict, in the columnspnext_deltaandtlen_delta, and still
come back exactly. On chr20 of NA12878 at 30x the file goes from 563.1 to
531.0 MB (-5.7%), and conversion and dumps take the same time. A program
that reads the columns directly gets the SAM values with
RAMNTupleRecord::GetPNEXT()andGetTLEN(); see CONTRIBUTING. The format
version becomes 6 (#113). - Read names are compressed with tok3, the name codec of CRAM 3.1, in blocks
of 10,000 records, instead of stored in aqnamecolumn. On chr20 of NA12878
at 30x the file goes from 531.0 to 478.2 MB (-9.9%), andramdump -hstill
reproduces the input byte for byte. Small region queries,ramdump -c,
flagstatandidxstatstake the same time, and so does conversion. Reading
every name costs CPU: on macOS a dump of the whole file takes about 6% longer
(28 s) and 28% more CPU, with the names decoded ahead on a second thread. The
format version becomes 7 (#114). - Library:
QualityBlocks.hbecomesRecordBlocks.handQualityBlockWriter
becomesBlockWriter, which also writes the name blocks.NameBlockReader
gives the name of any row; a record read from a file has an empty
GetQNAME(). METADATA'squal_block_endsbecomesblock_ends(#114). - Lossless quality scores are stored in the blocks of 10,000 records with
rANS order 1, the codec CRAM 3.1 uses for them by default, instead of in the
qualcolumn. On chr20 of NA12878 at 30x the file goes from 478.2 to
435.7 MB (-8.9%), andramdump -hstill reproduces the input byte for byte.
Conversion takes the same CPU or up to 10% less. Reading every quality costs
CPU: on macOS a dump of the whole file takes about 20% more CPU and 8% more
time; small region queries,ramdump -c,flagstatandidxstatstake the
same time. The format version becomes 8 (#115). -fqzcompcompresses the same blocks with fqzcomp instead of rANS. On chr20
of NA12878 at 30x, whose qualities are binned, it saves about 1% of the
qualities and takes three times the CPU to read them (#115).- Library: lossless qualities go into blocks whatever the quality policy, so
kQualInBlockonly says that a record's quality is in its block, and
kFqzcompin the policy asks for fqzcomp. A record read from a file has an
emptyGetQUAL()when its quality is in a block;QualityBlockReadergives
the quality of any record (#115). ramdumpwrites numbers, CIGARs and qualities into its output without
temporary strings. On chr20 of NA12878 at 30x a dump of the whole file takes
27.9 s of CPU and 17.6 s instead of 29.8 s and 19.3 s (-6% and -9%, one
socket of a Xeon E5-2690 v4), and a 10 Mb region 6% less CPU. The output is
unchanged (#118).- Library:
QualityBlockReader::Getreturns astd::string_viewinto the
decoded block, valid until the next call, instead of a copy.
RAMNTupleRecord::AppendCIGARandRAMNTupleUtils::AppendIntappend to a
string without making a temporary one (#118).
Removed
- Reading files written by 0.1.0 and earlier, and with them SEQ in the 4-bit
packing:kSeqRaw,RAMNTupleUtils::EncodeSequenceand
RAMNTupleUtils::DecodeSequence(#101). RAMNTupleRecord::OpenRAMFile()andReadAllRefs(); open files with
RAMFile::Open()(#105).RAMNTupleRecord's static writer state and the calls on it:
InitializeRefs(),WriteAllRefs(),GetRnameRefs(),GetRnextRefs()and
the static getters and setters of METADATA values. A converter now fills a
RAMFileMetadataand callsWriteMetadata(). Also removed are the record's
SetRNAME(),SetRNEXT(),SetREFID(name),SetREFNEXT(name),
GetRNAME(),GetRNEXT()andPrint(). A record holds ids, and
RAMFile::GetRname()andGetRnext()give their names (#107).RAMNTupleRecord'sAddTag(),SetOPT(),ClearTags(),ResetNOPT(),
GetTags(),GetNOPT()andGetOPT().tagsnow holds only the tags
without a column of their own. Converters split the tags withRAMTagWriter,
andRAMFile::AppendTags()writes all of them back (#111).RAMNTupleRecord'sSetPNEXT()andSetTLEN(), andGetPNEXT()and
GetTLEN()without arguments. Converters set both withSetMate(), and
readers passRAMFile::MateOnSameRef()toGetPNEXT()andGetTLEN()
(#113).
Full list of changes: CHANGELOG.md.