From 1597bc6728346ac050e81fe450d36ef5964169bf Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 20 Apr 2017 21:09:15 -0700 Subject: [PATCH] Switch from SHA-1 to SHA-512 Local benchmarking showed that the implementation of SHA-512 in the *ring* crate is 3x faster than the implementation of SHA-1 in the `sha1` crate. I've also noticed that 80%+ of sccache's runtime on a fully cached build is spent hashing. With this change I noticed a decrease from 108s to 92s when building a fully cached LLVM from the network. Not a huge win but if the network were faster could perhaps add up! Closes #108 --- Cargo.lock | 60 +++++++++++++++++++++++++++++++++--- Cargo.toml | 2 +- src/compiler/c.rs | 9 +++--- src/compiler/rust.rs | 24 +++++++-------- src/main.rs | 2 +- src/util.rs | 72 ++++++++++++++++++++++++++++++++------------ 6 files changed, 126 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3af10a0ca7..379e083fa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,12 +28,12 @@ dependencies = [ "redis 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)", "retry 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", - "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -198,6 +198,14 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "deque" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "dtoa" version = "0.4.1" @@ -370,7 +378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" -version = "0.2.2" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -642,7 +650,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -689,6 +697,26 @@ dependencies = [ "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rayon" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rayon-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redis" version = "0.8.0" @@ -723,6 +751,18 @@ dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ring" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rust-crypto" version = "0.2.36" @@ -756,7 +796,7 @@ dependencies = [ "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1111,6 +1151,11 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "untrusted" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.4.0" @@ -1218,6 +1263,7 @@ dependencies = [ "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" "checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" "checksum daemonize 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0239832c1b4ca406d5ec73728cf4c7336d25cf85dd32db9e047e9e706ee0e935" +"checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a" "checksum env_logger 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "82dcb9ceed3868a03b335657b85a159736c961900f7e7747d3b0b97b9ccb5ccb" @@ -1239,7 +1285,7 @@ dependencies = [ "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -"checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b" +"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135" "checksum linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bda158e0dabeb97ee8a401f4d17e479d6b891a14de0bba79d5cc2d4d325b5e48" @@ -1275,10 +1321,13 @@ dependencies = [ "checksum pulldown-cmark 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8361e81576d2e02643b04950e487ec172b687180da65c731c03cf336784e6c07" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" +"checksum rayon 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8c83adcb08e5b922e804fe1918142b422602ef11f2fd670b0b52218cb5984a20" +"checksum rayon-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "767d91bacddf07d442fe39257bf04fd95897d1c47c545d009f6beb03efd038f8" "checksum redis 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02a92e223490cc63d9230c4cdf132a48ce154ab1e063558e3841e219c2ea3f91" "checksum regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)" = "56b7ee9f764ecf412c6e2fff779bca4b22980517ae335a21aeaf4e32625a5df2" "checksum regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "31040aad7470ad9d8c46302dcffba337bb4289ca5da2e3cd6e37b64109a85199" "checksum retry 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29460f6011a25fc70b22010e796bd98330baccaa0005cba6f90b858a510dec0d" +"checksum ring 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6210568620e7b9d3f6e27f4bef63140cb88a15fbfb49b041bd3343b92c109166" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" "checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" @@ -1323,6 +1372,7 @@ dependencies = [ "checksum unicode-segmentation 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18127285758f0e2c6cf325bb3f3d138a12fee27de4f23e146cd6a179f26c2cf3" "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "193df64312e3515fd983ded55ad5bcaa7647a035804828ed757e832ce6029ef3" "checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" diff --git a/Cargo.toml b/Cargo.toml index 1152dbcc1e..0eb78e9239 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,12 +29,12 @@ number_prefix = "0.2.5" redis = { version = "0.8.0", optional = true } regex = "0.1.65" retry = "0.4.0" +ring = "0.7.0" rust-crypto = { version = "0.2.36", optional = true } rustc-serialize = "0.3" serde = "0.9" serde_derive = "0.9" serde_json = { version = "0.9.0", optional = true } -sha1 = "0.2.0" tempdir = "0.3.4" time = "0.1.35" tokio-core = "0.1.6" diff --git a/src/compiler/c.rs b/src/compiler/c.rs index b2556856d6..7499f53360 100644 --- a/src/compiler/c.rs +++ b/src/compiler/c.rs @@ -16,14 +16,13 @@ use compiler::{Cacheable, Compiler, CompilerArguments, CompilerHasher, CompilerK use futures::Future; use futures_cpupool::CpuPool; use mock_command::CommandCreatorSync; -use sha1; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::ffi::{OsStr, OsString}; use std::fmt; use std::path::Path; use std::process; -use util::{os_str_bytes, sha1_digest}; +use util::{os_str_bytes, Digest}; use errors::*; @@ -130,7 +129,7 @@ impl CCompiler { pub fn new(compiler: I, executable: String, pool: &CpuPool) -> SFuture> { - Box::new(sha1_digest(executable.clone(), &pool).map(move |digest| { + Box::new(Digest::file(executable.clone(), &pool).map(move |digest| { CCompiler { executable: executable, executable_digest: digest, @@ -271,7 +270,7 @@ pub fn hash_key(compiler_digest: &str, arguments: &str, env_vars: &[(OsString, O preprocessor_output: &[u8]) -> String { // If you change any of the inputs to the hash, you should change `CACHE_VERSION`. - let mut m = sha1::Sha1::new(); + let mut m = Digest::new(); m.update(compiler_digest.as_bytes()); m.update(CACHE_VERSION); m.update(arguments.as_bytes()); @@ -285,7 +284,7 @@ pub fn hash_key(compiler_digest: &str, arguments: &str, env_vars: &[(OsString, O } } m.update(preprocessor_output); - m.digest().to_string() + m.finish() } #[cfg(test)] diff --git a/src/compiler/rust.rs b/src/compiler/rust.rs index 6b892471ac..d6016efb18 100644 --- a/src/compiler/rust.rs +++ b/src/compiler/rust.rs @@ -18,7 +18,6 @@ use futures::{Future, future}; use futures_cpupool::CpuPool; use log::LogLevel::Trace; use mock_command::{CommandCreatorSync, RunCommand}; -use sha1; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::env::consts::DLL_EXTENSION; @@ -31,7 +30,7 @@ use std::process::{self, Stdio}; use std::slice; use std::time::Instant; use tempdir::TempDir; -use util::{fmt_duration_as_secs, os_str_bytes, run_input_output, sha1_digest}; +use util::{fmt_duration_as_secs, os_str_bytes, run_input_output, Digest}; use errors::*; @@ -137,7 +136,7 @@ fn hash_all(files: Vec, pool: &CpuPool) -> SFuture> let start = Instant::now(); let count = files.len(); let pool = pool.clone(); - Box::new(future::join_all(files.into_iter().map(move |f| sha1_digest(f, &pool))) + Box::new(future::join_all(files.into_iter().map(move |f| Digest::file(f, &pool))) .map(move |hashes| { trace!("Hashed {} files in {}", count, fmt_duration_as_secs(&start.elapsed())); hashes @@ -508,7 +507,7 @@ impl CompilerHasher for RustHasher let hashes = source_hashes.join(extern_hashes); Box::new(hashes.and_then(move |(source_hashes, extern_hashes)| -> SFuture<_> { // If you change any of the inputs to the hash, you should change `CACHE_VERSION`. - let mut m = sha1::Sha1::new(); + let mut m = Digest::new(); // Hash inputs: // 1. A version m.update(CACHE_VERSION); @@ -572,7 +571,7 @@ impl CompilerHasher for RustHasher outputs.insert(dep_info, p); } HashResult { - key: m.digest().to_string(), + key: m.finish(), compilation: Box::new(RustCompilation { executable: executable, arguments: arguments, @@ -629,7 +628,6 @@ mod test { use ::compiler::*; use itertools::Itertools; use mock_command::*; - use sha1; use std::fs::File; use std::io::Write; use std::sync::{Arc,Mutex}; @@ -842,7 +840,6 @@ bar.rs: drop(env_logger::init()); let f = TestFixture::new(); // SHA-1 digest of an empty file. - const EMPTY_DIGEST: &'static str = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; const FAKE_DIGEST: &'static str = "abcd1234"; // We'll just use empty files for each of these. for s in ["foo.rs", "bar.rs", "bar.rlib"].iter() { @@ -873,7 +870,10 @@ bar.rs: (OsString::from("FOO"), OsString::from("bar")), (OsString::from("CARGO_BLAH"), OsString::from("abc"))], &pool).wait().unwrap(); - let mut m = sha1::Sha1::new(); + let mut m = Digest::new(); + let empty_digest = m.finish(); + + let mut m = Digest::new(); // Version. m.update(CACHE_VERSION); // sysroot shlibs digests. @@ -881,15 +881,15 @@ bar.rs: // Arguments, with externs sorted at the end. m.update(b"ab--externabc--externxyz"); // bar.rs (source file, from dep-info) - m.update(EMPTY_DIGEST.as_bytes()); + m.update(empty_digest.as_bytes()); // foo.rs (source file, from dep-info) - m.update(EMPTY_DIGEST.as_bytes()); + m.update(empty_digest.as_bytes()); // bar.rlib (extern crate, from externs) - m.update(EMPTY_DIGEST.as_bytes()); + m.update(empty_digest.as_bytes()); // Env vars m.update(b"CARGO_BLAH=abc"); m.update(b"CARGO_PKG_NAME=foo"); - let digest = m.digest().to_string(); + let digest = m.finish(); assert_eq!(res.key, digest); let mut out = res.compilation.outputs().map(|(k, _)| k.to_owned()).collect::>(); out.sort(); diff --git a/src/main.rs b/src/main.rs index 533d0b3deb..890c63b97b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,7 @@ extern crate libc; #[cfg(windows)] extern crate mio_named_pipes; extern crate number_prefix; +extern crate ring; #[cfg(feature = "redis")] extern crate redis; extern crate regex; @@ -56,7 +57,6 @@ extern crate rustc_serialize; extern crate serde_json; #[macro_use] extern crate serde_derive; -extern crate sha1; extern crate tempdir; extern crate time; extern crate tokio_core; diff --git a/src/util.rs b/src/util.rs index 90a11e6608..2ee06a6e2e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -16,7 +16,7 @@ use futures::future; use futures::Future; use futures_cpupool::CpuPool; use mock_command::{CommandChild, RunCommand}; -use sha1; +use ring::digest::{SHA512, Context}; use std::ffi::OsStr; use std::fs::File; use std::io::prelude::*; @@ -27,26 +27,60 @@ use std::time::Duration; use errors::*; -/// Calculate the SHA-1 digest of the contents of `path`, running -/// the actual hash computation on a background thread in `pool`. -pub fn sha1_digest(path: T, pool: &CpuPool) -> SFuture - where T: Into -{ - let path = path.into(); - Box::new(pool.spawn_fn(move || -> Result<_> { - let f = File::open(&path).chain_err(|| format!("Failed to open file for hashing: {:?}", path))?; - let mut m = sha1::Sha1::new(); - let mut reader = BufReader::new(f); - loop { - let mut buffer = [0; 1024]; - let count = reader.read(&mut buffer[..])?; - if count == 0 { - break; +pub struct Digest { + inner: Context, +} + +impl Digest { + pub fn new() -> Digest { + Digest { inner: Context::new(&SHA512) } + } + + /// Calculate the SHA-1 digest of the contents of `path`, running + /// the actual hash computation on a background thread in `pool`. + pub fn file(path: T, pool: &CpuPool) -> SFuture + where T: Into + { + let path = path.into(); + Box::new(pool.spawn_fn(move || -> Result<_> { + let f = File::open(&path).chain_err(|| format!("Failed to open file for hashing: {:?}", path))?; + let mut m = Digest::new(); + let mut reader = BufReader::new(f); + loop { + let mut buffer = [0; 1024]; + let count = reader.read(&mut buffer[..])?; + if count == 0 { + break; + } + m.update(&buffer[..count]); } - m.update(&buffer[..count]); + Ok(m.finish()) + })) + } + + pub fn update(&mut self, bytes: &[u8]) { + self.inner.update(bytes); + } + + pub fn finish(self) -> String { + hex(self.inner.finish().as_ref()) + } +} + +fn hex(bytes: &[u8]) -> String { + let mut s = String::with_capacity(bytes.len() * 2); + for &byte in bytes { + s.push(hex(byte & 0xf)); + s.push(hex((byte >> 4)& 0xf)); + } + return s; + + fn hex(byte: u8) -> char { + match byte { + 0...9 => (b'0' + byte) as char, + _ => (b'a' + byte) as char, } - Ok(m.digest().to_string()) - })) + } } /// Format `duration` as seconds with a fractional component.