Skip to content

Commit 6436eed

Browse files
committed
zstd support
Based on [1], but using compression level 3 for speed rather than 19 for competitiveness with xz. [1] rust-lang#109
1 parent 31b4e31 commit 6436eed

File tree

4 files changed

+45
-1
lines changed

4 files changed

+45
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ walkdir = "2"
1818
xz2 = "0.1.4"
1919
num_cpus = "1"
2020
remove_dir_all = "0.5"
21+
zstd = { version = "0.12", features = ["zstdmt"] }
2122

2223
[dependencies.clap]
2324
features = ["derive"]

src/compression.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@ use flate2::{read::GzDecoder, write::GzEncoder};
33
use rayon::prelude::*;
44
use std::{convert::TryFrom, fmt, io::Read, io::Write, path::Path, str::FromStr};
55
use xz2::{read::XzDecoder, write::XzEncoder};
6+
use zstd::stream::{read::Decoder as ZstdDecoder, write::Encoder as ZstdEncoder};
67

78
#[derive(Debug, Copy, Clone)]
89
pub enum CompressionFormat {
910
Gz,
1011
Xz,
12+
Zstd,
1113
}
1214

1315
impl CompressionFormat {
1416
pub(crate) fn detect_from_path(path: impl AsRef<Path>) -> Option<Self> {
1517
match path.as_ref().extension().and_then(|e| e.to_str()) {
1618
Some("gz") => Some(CompressionFormat::Gz),
1719
Some("xz") => Some(CompressionFormat::Xz),
20+
Some("zst") => Some(CompressionFormat::Zstd),
1821
_ => None,
1922
}
2023
}
@@ -23,6 +26,7 @@ impl CompressionFormat {
2326
match self {
2427
CompressionFormat::Gz => "gz",
2528
CompressionFormat::Xz => "xz",
29+
CompressionFormat::Zstd => "zst",
2630
}
2731
}
2832

@@ -71,6 +75,16 @@ impl CompressionFormat {
7175
);
7276
Box::new(compressor)
7377
}
78+
CompressionFormat::Zstd => {
79+
// zstd's default compression level is 3, which is on par with gzip but much faster
80+
let mut enc = ZstdEncoder::new(file, 3).context("failed to initialize zstd encoder")?;
81+
// Long-distance matching provides a substantial benefit for our tarballs
82+
enc.long_distance_matching(true).context("zst long_distance_matching")?;
83+
// Enable multithreaded mode. zstd seems to be faster when using the number of
84+
// physical CPU cores rather than logical/SMT threads.
85+
enc.multithread(num_cpus::get_physical() as u32).context("zst multithreaded")?;
86+
Box::new(enc)
87+
}
7488
})
7589
}
7690

@@ -79,6 +93,7 @@ impl CompressionFormat {
7993
Ok(match self {
8094
CompressionFormat::Gz => Box::new(GzDecoder::new(file)),
8195
CompressionFormat::Xz => Box::new(XzDecoder::new(file)),
96+
CompressionFormat::Zstd => Box::new(ZstdDecoder::new(file)?),
8297
})
8398
}
8499
}
@@ -96,6 +111,7 @@ impl TryFrom<&'_ str> for CompressionFormats {
96111
match format.trim() {
97112
"gz" => parsed.push(CompressionFormat::Gz),
98113
"xz" => parsed.push(CompressionFormat::Xz),
114+
"zst" => parsed.push(CompressionFormat::Zstd),
99115
other => anyhow::bail!("unknown compression format: {}", other),
100116
}
101117
}
@@ -121,6 +137,7 @@ impl fmt::Display for CompressionFormats {
121137
match format {
122138
CompressionFormat::Xz => "xz",
123139
CompressionFormat::Gz => "gz",
140+
CompressionFormat::Zstd => "zst",
124141
},
125142
f,
126143
)?;
@@ -139,6 +156,10 @@ impl CompressionFormats {
139156
pub(crate) fn iter(&self) -> impl Iterator<Item = CompressionFormat> + '_ {
140157
self.0.iter().map(|i| *i)
141158
}
159+
160+
pub(crate) fn len(&self) -> usize {
161+
self.0.len()
162+
}
142163
}
143164

144165
pub(crate) trait Encoder: Send + Write {
@@ -159,6 +180,13 @@ impl<W: Send + Write> Encoder for XzEncoder<W> {
159180
}
160181
}
161182

183+
impl<W: Send + Write> Encoder for ZstdEncoder<'_, W> {
184+
fn finish(mut self: Box<Self>) -> Result<(), Error> {
185+
ZstdEncoder::do_finish(self.as_mut()).context("failed to finish .zst file")?;
186+
Ok(())
187+
}
188+
}
189+
162190
pub(crate) struct CombinedEncoder {
163191
encoders: Vec<Box<dyn Encoder>>,
164192
}

src/tarballer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl Tarballer {
5555
let mut builder = Builder::new(buf);
5656

5757
let pool = rayon::ThreadPoolBuilder::new()
58-
.num_threads(2)
58+
.num_threads(self.compression_formats.len())
5959
.build()
6060
.unwrap();
6161
pool.install(move || {

test.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,21 @@ generate_compression_formats_multiple() {
12181218
}
12191219
runtest generate_compression_formats_multiple
12201220

1221+
generate_compression_formats_multiple_zst() {
1222+
try sh "$S/gen-installer.sh" \
1223+
--image-dir="$TEST_DIR/image1" \
1224+
--work-dir="$WORK_DIR" \
1225+
--output-dir="$OUT_DIR" \
1226+
--package-name="rustc" \
1227+
--component-name="rustc" \
1228+
--compression-formats="gz,zst"
1229+
1230+
try test -e "${OUT_DIR}/rustc.tar.gz"
1231+
try test ! -e "${OUT_DIR}/rustc.tar.xz"
1232+
try test -e "${OUT_DIR}/rustc.tar.zst"
1233+
}
1234+
runtest generate_compression_formats_multiple_zst
1235+
12211236
generate_compression_formats_error() {
12221237
expect_fail sh "$S/gen-installer.sh" \
12231238
--image-dir="$TEST_DIR/image1" \

0 commit comments

Comments
 (0)