@ 261027.5

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

Cargo.lock

@@ -192,7 +192,6 @@ dependencies = [  "grep-searcher",  "log",  "serde",- "serde_derive",  "serde_json",  "termcolor", ]

crates/printer/Cargo.toml

@@ -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]

crates/printer/src/jsont.rs

@@ -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()+    } }

crates/printer/src/stats.rs

@@ -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()+    }+}

crates/printer/src/util.rs

@@ -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))?;