Skip to content

Commit ed957c0

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 5b2eee7 commit ed957c0

File tree

4 files changed

+45
-1
lines changed

4 files changed

+45
-1
lines changed

Cargo.toml

+1
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

+28
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

@@ -48,6 +52,16 @@ impl CompressionFormat {
4852
.encoder()?;
4953
Box::new(XzEncoder::new_stream(file, stream))
5054
}
55+
CompressionFormat::Zstd => {
56+
// zstd's default compression level is 3, which is on par with gzip but much faster
57+
let mut enc = ZstdEncoder::new(file, 3).context("failed to initialize zstd encoder")?;
58+
// Long-distance matching provides a substantial benefit for our tarballs
59+
enc.long_distance_matching(true).context("zst long_distance_matching")?;
60+
// Enable multithreaded mode. zstd seems to be faster when using the number of
61+
// physical CPU cores rather than logical/SMT threads.
62+
enc.multithread(num_cpus::get_physical() as u32).context("zst multithreaded")?;
63+
Box::new(enc)
64+
}
5165
})
5266
}
5367

@@ -56,6 +70,7 @@ impl CompressionFormat {
5670
Ok(match self {
5771
CompressionFormat::Gz => Box::new(GzDecoder::new(file)),
5872
CompressionFormat::Xz => Box::new(XzDecoder::new(file)),
73+
CompressionFormat::Zstd => Box::new(ZstdDecoder::new(file)?),
5974
})
6075
}
6176
}
@@ -73,6 +88,7 @@ impl TryFrom<&'_ str> for CompressionFormats {
7388
match format.trim() {
7489
"gz" => parsed.push(CompressionFormat::Gz),
7590
"xz" => parsed.push(CompressionFormat::Xz),
91+
"zst" => parsed.push(CompressionFormat::Zstd),
7692
other => anyhow::bail!("unknown compression format: {}", other),
7793
}
7894
}
@@ -97,6 +113,7 @@ impl fmt::Display for CompressionFormats {
97113
fmt::Display::fmt(match format {
98114
CompressionFormat::Xz => "xz",
99115
CompressionFormat::Gz => "gz",
116+
CompressionFormat::Zstd => "zst",
100117
}, f)?;
101118
}
102119
Ok(())
@@ -113,6 +130,10 @@ impl CompressionFormats {
113130
pub(crate) fn iter(&self) -> impl Iterator<Item = CompressionFormat> + '_ {
114131
self.0.iter().map(|i| *i)
115132
}
133+
134+
pub(crate) fn len(&self) -> usize {
135+
self.0.len()
136+
}
116137
}
117138

118139
pub(crate) trait Encoder: Send + Write {
@@ -133,6 +154,13 @@ impl<W: Send + Write> Encoder for XzEncoder<W> {
133154
}
134155
}
135156

157+
impl<W: Send + Write> Encoder for ZstdEncoder<'_, W> {
158+
fn finish(mut self: Box<Self>) -> Result<(), Error> {
159+
ZstdEncoder::do_finish(self.as_mut()).context("failed to finish .zst file")?;
160+
Ok(())
161+
}
162+
}
163+
136164
pub(crate) struct CombinedEncoder {
137165
encoders: Vec<Box<dyn Encoder>>,
138166
}

src/tarballer.rs

+1-1
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

+15
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)