From 9c84575229131239f92149cd5790ddb553d7eea8 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 21 Nov 2023 12:40:45 -0500 Subject: 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/ --- crates/printer/Cargo.toml | 3 +- crates/printer/src/jsont.rs | 184 +++++++++++++++++++++++++++++++------------- crates/printer/src/stats.rs | 110 +++++++++++++++----------- crates/printer/src/util.rs | 12 +-- 4 files changed, 202 insertions(+), 107 deletions(-) (limited to 'crates') diff --git a/crates/printer/Cargo.toml b/crates/printer/Cargo.toml index 4193d3d1..10537c53 100644 --- a/crates/printer/Cargo.toml +++ b/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] diff --git a/crates/printer/src/jsont.rs b/crates/printer/src/jsont.rs index 5d901041..6e5e85df 100644 --- a/crates/printer/src/jsont.rs +++ b/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( + &self, + s: S, + ) -> Result { + 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( + &self, + s: S, + ) -> Result { + 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, - pub(crate) stats: Stats, + pub(crate) stats: crate::stats::Stats, +} + +impl<'a> serde::Serialize for End<'a> { + fn serialize( + &self, + s: S, + ) -> Result { + 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, pub(crate) absolute_offset: u64, pub(crate) submatches: &'a [SubMatch<'a>], } -#[derive(Serialize)] +impl<'a> serde::Serialize for Match<'a> { + fn serialize( + &self, + s: S, + ) -> Result { + 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, pub(crate) absolute_offset: u64, pub(crate) submatches: &'a [SubMatch<'a>], } -#[derive(Serialize)] +impl<'a> serde::Serialize for Context<'a> { + fn serialize( + &self, + s: S, + ) -> Result { + 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( + &self, + s: S, + ) -> Result { + 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(bytes: T, ser: S) -> Result -where - T: AsRef<[u8]>, - S: Serializer, -{ - use base64::engine::{general_purpose::STANDARD, Engine}; - ser.serialize_str(&STANDARD.encode(&bytes)) -} - -fn ser_bytes(bytes: T, ser: S) -> Result -where - T: AsRef<[u8]>, - S: Serializer, -{ - use serde::Serialize; - Data::from_bytes(bytes.as_ref()).serialize(ser) -} - -fn ser_path(path: &Option

, ser: S) -> Result -where - P: AsRef, - 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( + &self, + s: S, + ) -> Result { + 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() + } } diff --git a/crates/printer/src/stats.rs b/crates/printer/src/stats.rs index f1898c0b..555401b3 100644 --- a/crates/printer/src/stats.rs +++ b/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( + &self, + s: S, + ) -> Result { + 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() + } +} diff --git a/crates/printer/src/util.rs b/crates/printer/src/util.rs index db19504c..04f9e129 100644 --- a/crates/printer/src/util.rs +++ b/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(&self, ser: S) -> Result { +impl serde::Serialize for NiceDuration { + fn serialize( + &self, + ser: S, + ) -> Result { 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))?; -- cgit v1.2.3