231121.5 · 9c84575229131239f92149cd5790ddb553d7eea8
printer: drop dependency on serde_derive
As suggested by @epage[1].
Ad hoc timings on my i7-12900K:
before cargo build: 4.91s
before cargo build release: 8.05s
after cargo build: 4.69s
after cargo build release: 7.83s
... pretty underwhelming if you ask me. Ah well. And on my M2 mac mini:
before cargo build: 6.18s
before cargo build release: 14.50s
after cargo build: 5.52s
after cargo build release: 13.44s
Still kind of underwhelming, but definitely better. It shaves a full second off of compile times in release mode. I went back to my i7-12900K, but passed `-j1` to `cargo build` to force single threaded mode:
before cargo build: 19.44s
before cargo build release: 50.64s
after cargo build: 16.76s
after cargo build release: 48.00s
Which seems pretty consistent with the modest improvements above.
Looking at `cargo build --timings`, the beefiest chunk of time is spent in compiling `regex-automata`, by far. This is fine because it's core functionality. I wish a fast general purpose regex engine with its internals exposed as a separately versioned library didn't require so much code... Blech.
[1]: https://old.reddit.com/r/rust/comments/17rd8ww/faster_compilation_with_the_parallel_frontend_in/k8igjlg/
reading declarations
@@ -192,7 +192,6 @@ dependencies = [ "grep-searcher", "log", "serde",- "serde_derive", "serde_json", "termcolor", ]
@@ -16,7 +16,7 @@ edition = "2021" [features] default = ["serde"]-serde = ["dep:base64", "dep:serde", "dep:serde_derive", "dep:serde_json"]+serde = ["dep:base64", "dep:serde", "dep:serde_json"] [dependencies] base64 = { version = "0.21.4", optional = true }@@ -26,7 +26,6 @@ grep-searcher = { version = "0.1.11", path = "../searcher" } log = "0.4.5" termcolor = "1.3.0" serde = { version = "1.0.193", optional = true }-serde_derive = { version = "1.0.193", optional = true } serde_json = { version = "1.0.107", optional = true } [dev-dependencies]
@@ -8,13 +8,6 @@ use std::{borrow::Cow, path::Path}; -use {base64, serde::Serializer, serde_derive::Serialize};--use crate::stats::Stats;--#[derive(Serialize)]-#[serde(tag = "type", content = "data")]-#[serde(rename_all = "snake_case")] pub(crate) enum Message<'a> { Begin(Begin<'a>), End(End<'a>),@@ -22,51 +15,145 @@ pub(crate) enum Message<'a> { Context(Context<'a>), } -#[derive(Serialize)]+impl<'a> serde::Serialize for Message<'a> {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("Message", 2)?;+ match *self {+ Message::Begin(ref msg) => {+ state.serialize_field("type", &"begin")?;+ state.serialize_field("data", msg)?;+ }+ Message::End(ref msg) => {+ state.serialize_field("type", &"end")?;+ state.serialize_field("data", msg)?;+ }+ Message::Match(ref msg) => {+ state.serialize_field("type", &"match")?;+ state.serialize_field("data", msg)?;+ }+ Message::Context(ref msg) => {+ state.serialize_field("type", &"context")?;+ state.serialize_field("data", msg)?;+ }+ }+ state.end()+ }+}+ pub(crate) struct Begin<'a> {- #[serde(serialize_with = "ser_path")] pub(crate) path: Option<&'a Path>, } -#[derive(Serialize)]+impl<'a> serde::Serialize for Begin<'a> {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("Begin", 1)?;+ state.serialize_field("path", &self.path.map(Data::from_path))?;+ state.end()+ }+}+ pub(crate) struct End<'a> {- #[serde(serialize_with = "ser_path")] pub(crate) path: Option<&'a Path>, pub(crate) binary_offset: Option<u64>,- pub(crate) stats: Stats,+ pub(crate) stats: crate::stats::Stats,+}++impl<'a> serde::Serialize for End<'a> {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("End", 3)?;+ state.serialize_field("path", &self.path.map(Data::from_path))?;+ state.serialize_field("binary_offset", &self.binary_offset)?;+ state.serialize_field("stats", &self.stats)?;+ state.end()+ } } -#[derive(Serialize)] pub(crate) struct Match<'a> {- #[serde(serialize_with = "ser_path")] pub(crate) path: Option<&'a Path>,- #[serde(serialize_with = "ser_bytes")] pub(crate) lines: &'a [u8], pub(crate) line_number: Option<u64>, pub(crate) absolute_offset: u64, pub(crate) submatches: &'a [SubMatch<'a>], } -#[derive(Serialize)]+impl<'a> serde::Serialize for Match<'a> {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("Match", 5)?;+ state.serialize_field("path", &self.path.map(Data::from_path))?;+ state.serialize_field("lines", &Data::from_bytes(self.lines))?;+ state.serialize_field("line_number", &self.line_number)?;+ state.serialize_field("absolute_offset", &self.absolute_offset)?;+ state.serialize_field("submatches", &self.submatches)?;+ state.end()+ }+}+ pub(crate) struct Context<'a> {- #[serde(serialize_with = "ser_path")] pub(crate) path: Option<&'a Path>,- #[serde(serialize_with = "ser_bytes")] pub(crate) lines: &'a [u8], pub(crate) line_number: Option<u64>, pub(crate) absolute_offset: u64, pub(crate) submatches: &'a [SubMatch<'a>], } -#[derive(Serialize)]+impl<'a> serde::Serialize for Context<'a> {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("Context", 5)?;+ state.serialize_field("path", &self.path.map(Data::from_path))?;+ state.serialize_field("lines", &Data::from_bytes(self.lines))?;+ state.serialize_field("line_number", &self.line_number)?;+ state.serialize_field("absolute_offset", &self.absolute_offset)?;+ state.serialize_field("submatches", &self.submatches)?;+ state.end()+ }+}+ pub(crate) struct SubMatch<'a> {- #[serde(rename = "match")]- #[serde(serialize_with = "ser_bytes")] pub(crate) m: &'a [u8], pub(crate) start: usize, pub(crate) end: usize, } +impl<'a> serde::Serialize for SubMatch<'a> {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("SubMatch", 3)?;+ state.serialize_field("match", &Data::from_bytes(self.m))?;+ state.serialize_field("start", &self.start)?;+ state.serialize_field("end", &self.end)?;+ state.end()+ }+}+ /// Data represents things that look like strings, but may actually not be /// valid UTF-8. To handle this, `Data` is serialized as an object with one /// of two keys: `text` (for valid UTF-8) or `bytes` (for invalid UTF-8).@@ -74,16 +161,10 @@ pub(crate) struct SubMatch<'a> { /// The happy path is valid UTF-8, which streams right through as-is, since /// it is natively supported by JSON. When invalid UTF-8 is found, then it is /// represented as arbitrary bytes and base64 encoded.-#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]-#[serde(untagged)]+#[derive(Clone, Debug, Hash, PartialEq, Eq)] enum Data<'a> {- Text {- text: Cow<'a, str>,- },- Bytes {- #[serde(serialize_with = "to_base64")]- bytes: &'a [u8],- },+ Text { text: Cow<'a, str> },+ Bytes { bytes: &'a [u8] }, } impl<'a> Data<'a> {@@ -115,29 +196,22 @@ impl<'a> Data<'a> { } } -fn to_base64<T, S>(bytes: T, ser: S) -> Result<S::Ok, S::Error>-where- T: AsRef<[u8]>,- S: Serializer,-{- use base64::engine::{general_purpose::STANDARD, Engine};- ser.serialize_str(&STANDARD.encode(&bytes))-}--fn ser_bytes<T, S>(bytes: T, ser: S) -> Result<S::Ok, S::Error>-where- T: AsRef<[u8]>,- S: Serializer,-{- use serde::Serialize;- Data::from_bytes(bytes.as_ref()).serialize(ser)-}--fn ser_path<P, S>(path: &Option<P>, ser: S) -> Result<S::Ok, S::Error>-where- P: AsRef<Path>,- S: Serializer,-{- use serde::Serialize;- path.as_ref().map(|p| Data::from_path(p.as_ref())).serialize(ser)+impl<'a> serde::Serialize for Data<'a> {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("Data", 1)?;+ match *self {+ Data::Text { ref text } => state.serialize_field("text", text)?,+ Data::Bytes { bytes } => {+ use base64::engine::{general_purpose::STANDARD, Engine};+ let encoded = STANDARD.encode(bytes);+ state.serialize_field("bytes", &encoded)?;+ }+ }+ state.end()+ } }
@@ -10,7 +10,6 @@ use crate::util::NiceDuration; /// When statistics are reported by a printer, they correspond to all searches /// executed with that printer. #[derive(Clone, Debug, Default, PartialEq, Eq)]-#[cfg_attr(feature = "serde", derive(serde_derive::Serialize))] pub struct Stats { elapsed: NiceDuration, searches: u64,@@ -21,49 +20,6 @@ pub struct Stats { matches: u64, } -impl Add for Stats {- type Output = Stats;-- fn add(self, rhs: Stats) -> Stats {- self + &rhs- }-}--impl<'a> Add<&'a Stats> for Stats {- type Output = Stats;-- fn add(self, rhs: &'a Stats) -> Stats {- Stats {- elapsed: NiceDuration(self.elapsed.0 + rhs.elapsed.0),- searches: self.searches + rhs.searches,- searches_with_match: self.searches_with_match- + rhs.searches_with_match,- bytes_searched: self.bytes_searched + rhs.bytes_searched,- bytes_printed: self.bytes_printed + rhs.bytes_printed,- matched_lines: self.matched_lines + rhs.matched_lines,- matches: self.matches + rhs.matches,- }- }-}--impl AddAssign for Stats {- fn add_assign(&mut self, rhs: Stats) {- *self += &rhs;- }-}--impl<'a> AddAssign<&'a Stats> for Stats {- fn add_assign(&mut self, rhs: &'a Stats) {- self.elapsed.0 += rhs.elapsed.0;- self.searches += rhs.searches;- self.searches_with_match += rhs.searches_with_match;- self.bytes_searched += rhs.bytes_searched;- self.bytes_printed += rhs.bytes_printed;- self.matched_lines += rhs.matched_lines;- self.matches += rhs.matches;- }-}- impl Stats { /// Return a new value for tracking aggregate statistics across searches. ///@@ -147,3 +103,69 @@ impl Stats { self.matches += n; } }++impl Add for Stats {+ type Output = Stats;++ fn add(self, rhs: Stats) -> Stats {+ self + &rhs+ }+}++impl<'a> Add<&'a Stats> for Stats {+ type Output = Stats;++ fn add(self, rhs: &'a Stats) -> Stats {+ Stats {+ elapsed: NiceDuration(self.elapsed.0 + rhs.elapsed.0),+ searches: self.searches + rhs.searches,+ searches_with_match: self.searches_with_match+ + rhs.searches_with_match,+ bytes_searched: self.bytes_searched + rhs.bytes_searched,+ bytes_printed: self.bytes_printed + rhs.bytes_printed,+ matched_lines: self.matched_lines + rhs.matched_lines,+ matches: self.matches + rhs.matches,+ }+ }+}++impl AddAssign for Stats {+ fn add_assign(&mut self, rhs: Stats) {+ *self += &rhs;+ }+}++impl<'a> AddAssign<&'a Stats> for Stats {+ fn add_assign(&mut self, rhs: &'a Stats) {+ self.elapsed.0 += rhs.elapsed.0;+ self.searches += rhs.searches;+ self.searches_with_match += rhs.searches_with_match;+ self.bytes_searched += rhs.bytes_searched;+ self.bytes_printed += rhs.bytes_printed;+ self.matched_lines += rhs.matched_lines;+ self.matches += rhs.matches;+ }+}++#[cfg(feature = "serde")]+impl serde::Serialize for Stats {+ fn serialize<S: serde::Serializer>(+ &self,+ s: S,+ ) -> Result<S::Ok, S::Error> {+ use serde::ser::SerializeStruct;++ let mut state = s.serialize_struct("Stats", 7)?;+ state.serialize_field("elapsed", &self.elapsed)?;+ state.serialize_field("searches", &self.searches)?;+ state.serialize_field(+ "searches_with_match",+ &self.searches_with_match,+ )?;+ state.serialize_field("bytes_searched", &self.bytes_searched)?;+ state.serialize_field("bytes_printed", &self.bytes_printed)?;+ state.serialize_field("matched_lines", &self.matched_lines)?;+ state.serialize_field("matches", &self.matches)?;+ state.end()+ }+}
@@ -8,9 +8,6 @@ use { }, }; -#[cfg(feature = "serde")]-use serde::{Serialize, Serializer};- use crate::{hyperlink::HyperlinkPath, MAX_LOOK_AHEAD}; /// A type for handling replacements while amortizing allocation.@@ -385,11 +382,14 @@ impl NiceDuration { } #[cfg(feature = "serde")]-impl Serialize for NiceDuration {- fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {+impl serde::Serialize for NiceDuration {+ fn serialize<S: serde::Serializer>(+ &self,+ ser: S,+ ) -> Result<S::Ok, S::Error> { use serde::ser::SerializeStruct; - let mut state = ser.serialize_struct("Duration", 2)?;+ let mut state = ser.serialize_struct("Duration", 3)?; state.serialize_field("secs", &self.0.as_secs())?; state.serialize_field("nanos", &self.0.subsec_nanos())?; state.serialize_field("human", &format!("{}", self))?;