lo48576 / iri-string

String types for URIs/IRIs.
Apache License 2.0
17 stars 3 forks source link

Make the parsers faster #7

Closed lo48576 closed 2 years ago

lo48576 commented 2 years ago

As I noticed at https://github.com/lo48576/iri-string/issues/6#issuecomment-1007519376, the current parsing is slow, and I'd be able to make it faster relatively easily.

lo48576 commented 2 years ago

Baseline:

test parser::tests::bench_parse_host   ... bench:       2,367 ns/iter (+/- 46)
test parser::tests::bench_parse_normal ... bench:       1,919 ns/iter (+/- 37)
test resolve::tests::bench_resolve     ... bench:       3,521 ns/iter (+/- 52)
commit 4428ca066d59863a39dea676e65f8463414f81cb
Author: YOSHIOKA Takuma <lo48576@hard-wi.red>
Date:   2022-01-08 04:10:25 +0900

    Add bench tests

diff --git a/src/lib.rs b/src/lib.rs
index 593c7a3560fa..fd89efd6cace 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -115,6 +115,9 @@
 #![warn(missing_docs)]
 #![warn(clippy::missing_docs_in_private_items)]
 #![cfg_attr(not(feature = "std"), no_std)]
+#![feature(test)]
+
+extern crate test;

 // Inform users that `serde-alloc` is required when `serde` and `alloc` is enabled.
 #[cfg(all(
diff --git a/src/parser.rs b/src/parser.rs
index f4ff1fa9e115..200af2e3d6b4 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -40,3 +40,40 @@ impl<'a, S: Spec> From<&'a RiReferenceStr<S>> for RiReferenceComponents<'a, S> {
             .expect("Should never fail: `RiReferenceStr` should be already validated")
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::types::IriReferenceStr;
+
+    #[bench]
+    fn bench_parse_normal(b: &mut test::Bencher) {
+        b.iter(|| {
+            let s = concat!(
+                "scheme://user:pw@sub.example.com:8080/a/b/c/%30/%31/%32%33%34",
+                "/foo/foo/../../../foo.foo/foo/foo/././././//////foo",
+                "/\u{03B1}\u{03B2}\u{03B3}/\u{03B1}\u{03B2}\u{03B3}/\u{03B1}\u{03B2}\u{03B3}",
+                "?k1=v1&k2=v2&k3=v3#fragment"
+            );
+            let iri = IriReferenceStr::new(s);
+            iri
+        });
+    }
+
+    #[bench]
+    fn bench_parse_host(b: &mut test::Bencher) {
+        b.iter(|| {
+            let domain = "scheme://sub.sub.sub.example.com:8080/a/b/c";
+            let v4 = "scheme://198.51.100.23:8080/a/b/c";
+            let v6 = "scheme://[2001:db8:0123::cafe]:8080/a/b/c";
+            let v6v4 = "scheme://[2001:db8::198.51.100.23]:8080/a/b/c";
+            let vfuture = "scheme://[v2.ipv2-does-not-exist]:8080/a/b/c";
+            (
+                IriReferenceStr::new(domain),
+                IriReferenceStr::new(v4),
+                IriReferenceStr::new(v6),
+                IriReferenceStr::new(v6v4),
+                IriReferenceStr::new(vfuture),
+            )
+        });
+    }
+}
diff --git a/src/resolve.rs b/src/resolve.rs
index 10c075837a2b..8dbe9c61372e 100644
--- a/src/resolve.rs
+++ b/src/resolve.rs
@@ -1120,3 +1120,23 @@ mod tests {
         }
     }
 }
+
+#[cfg(feature = "alloc")]
+#[cfg(test)]
+mod tests_bench {
+    use super::*;
+
+    use crate::types::{IriReferenceStr, IriStr};
+
+    #[bench]
+    fn bench_resolve(b: &mut test::Bencher) {
+        let base = IriStr::new("https://sub.example.com/foo1/foo2/foo3/foo4/foo5")
+            .expect("should be valid IRI");
+        let rel = IriReferenceStr::new(concat!(
+            "bar1/bar2/bar3/../bar4/../../bar5/bar6/bar7/../../../../..",
+            "/bar8/../../../bar9/././././././bar10/bar11",
+        ))
+        .expect("should be valid IRI");
+        b.iter(|| resolve(rel, base));
+    }
+}
lo48576 commented 2 years ago

Baseline:

commit f3451954d84b8765c5747be63b35e1c6022aca27
Author: YOSHIOKA Takuma <lo48576@hard-wi.red>
Date:   2022-01-08 00:51:44 +0900

    Add examples and config to use `cargo flamegraph`

    Run `cargo +nightly flamegraph --example flame` or `... resolve`.

diff --git a/Cargo.toml b/Cargo.toml
index 8d573b68c78e..cfaf41638a15 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,6 +51,9 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"],
 [dev-dependencies]
 serde_test = "1.0.104"

+[profile.release]
+debug = true
+
 [badges]
 maintenance = { status = "actively-developed" }
 travis-ci = { repository = "lo48576/iri-string" }
diff --git a/examples/flame.rs b/examples/flame.rs
new file mode 100644
index 000000000000..657459104a5f
--- /dev/null
+++ b/examples/flame.rs
@@ -0,0 +1,28 @@
+#![feature(bench_black_box)]
+
+use iri_string::types::IriReferenceStr;
+
+fn main() {
+    for _ in 0..1000000 {
+        let s = concat!(
+            "scheme://user:pw@sub.example.com:8080/a/b/c/%30/%31/%32%33%34",
+            "/foo/foo/../../../foo.foo/foo/foo/././././//////foo",
+            "/\u{03B1}\u{03B2}\u{03B3}/\u{03B1}\u{03B2}\u{03B3}/\u{03B1}\u{03B2}\u{03B3}",
+            "?k1=v1&k2=v2&k3=v3#fragment"
+        );
+
+        let domain = "scheme://sub.sub.sub.example.com:8080/a/b/c";
+        let v4 = "scheme://198.51.100.23:8080/a/b/c";
+        let v6 = "scheme://[2001:db8:0123::cafe]:8080/a/b/c";
+        let v6v4 = "scheme://[2001:db8::198.51.100.23]:8080/a/b/c";
+        let vfuture = "scheme://[v2.ipv2-does-not-exist]:8080/a/b/c";
+        let _ = std::hint::black_box((
+            IriReferenceStr::new(s),
+            IriReferenceStr::new(domain),
+            IriReferenceStr::new(v4),
+            IriReferenceStr::new(v6),
+            IriReferenceStr::new(v6v4),
+            IriReferenceStr::new(vfuture),
+        ));
+    }
+}
diff --git a/examples/resolve.rs b/examples/resolve.rs
new file mode 100644
index 000000000000..45aeb22faef4
--- /dev/null
+++ b/examples/resolve.rs
@@ -0,0 +1,18 @@
+#![feature(bench_black_box)]
+
+use iri_string::resolve::resolve;
+use iri_string::types::{IriAbsoluteStr, IriReferenceStr};
+
+fn main() {
+    let base = IriAbsoluteStr::new("https://sub.example.com/foo1/foo2/foo3/foo4/foo5")
+        .expect("should be valid IRI");
+    let rel = IriReferenceStr::new(concat!(
+        "bar1/bar2/bar3/../bar4/../../bar5/bar6/bar7/../../../../..",
+        "/bar8/../../../bar9/././././././bar10/bar11",
+    ))
+    .expect("should be valid IRI");
+    for _ in 0..1000000 {
+        let resolved = resolve(rel, base);
+        drop(std::hint::black_box(resolved));
+    }
+}

Run cargo flamegraph.

flamegraph

lo48576 commented 2 years ago
commit 9f5d1c99b0454b677af377f19a7b0e40b2272431
Author: YOSHIOKA Takuma <lo48576@hard-wi.red>
Date:   2022-01-08 06:50:43 +0900

    Utilize knowledge about valid IRIs in `RiReferenceComponent::from`

    This makes `resolve()` quite fast (3,521 ns/iter to 1,362 ns/iter
    for some bench test).

diff --git a/src/parser.rs b/src/parser.rs
index 200af2e3d6b4..9b01029ce57c 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -2,15 +2,14 @@

 use core::marker::PhantomData;

-use nom::combinator::all_consuming;
+use crate::spec::Spec;
+use crate::types::RiReferenceStr;

-use crate::{spec::Spec, types::RiReferenceStr};
-
-use self::details::decompose_uri_reference;
 pub(crate) use self::details::{absolute_uri, fragment, path, relative_ref, uri, uri_reference};

 pub(crate) mod char;
 mod details;
+mod validated;

 /// Components of an IRI reference.
 ///
@@ -34,15 +33,80 @@ pub(crate) struct RiReferenceComponents<'a, S> {
 }

 impl<'a, S: Spec> From<&'a RiReferenceStr<S>> for RiReferenceComponents<'a, S> {
+    #[inline]
     fn from(s: &'a RiReferenceStr<S>) -> Self {
-        all_consuming(decompose_uri_reference::<(), S>)(s.as_str())
-            .map(|(_rest, components)| components)
-            .expect("Should never fail: `RiReferenceStr` should be already validated")
+        validated::decompose_iri_reference(s)
     }
 }

 #[cfg(test)]
 mod tests {
+    use super::*;
+
+    use crate::types::IriReferenceStr;
+
+    /// Creates an `IriReferenceStr`.
+    fn iri_ref(s: &str) -> &IriReferenceStr {
+        IriReferenceStr::new(s).expect("should be valid")
+    }
+
+    #[test]
+    fn absolute_slashes() {
+        let c0 = RiReferenceComponents::from(iri_ref("scheme:"));
+        assert_eq!(c0.authority, None);
+        assert_eq!(c0.path, "");
+
+        let c1 = RiReferenceComponents::from(iri_ref("scheme:/"));
+        assert_eq!(c1.authority, None);
+        assert_eq!(c1.path, "/");
+
+        let c2 = RiReferenceComponents::from(iri_ref("scheme://"));
+        assert_eq!(c2.authority, Some(""));
+        assert_eq!(c2.path, "");
+
+        let c3 = RiReferenceComponents::from(iri_ref("scheme:///"));
+        assert_eq!(c3.authority, Some(""));
+        assert_eq!(c3.path, "/");
+
+        let c4 = RiReferenceComponents::from(iri_ref("scheme:////"));
+        assert_eq!(c4.authority, Some(""));
+        assert_eq!(c4.path, "//");
+
+        let c5 = RiReferenceComponents::from(iri_ref("scheme://///"));
+        assert_eq!(c5.authority, Some(""));
+        assert_eq!(c5.path, "///");
+    }
+
+    #[test]
+    fn relative_slashes() {
+        let c0 = RiReferenceComponents::from(iri_ref(""));
+        assert_eq!(c0.authority, None);
+        assert_eq!(c0.path, "");
+
+        let c1 = RiReferenceComponents::from(iri_ref("/"));
+        assert_eq!(c1.authority, None);
+        assert_eq!(c1.path, "/");
+
+        let c2 = RiReferenceComponents::from(iri_ref("//"));
+        assert_eq!(c2.authority, Some(""));
+        assert_eq!(c2.path, "");
+
+        let c3 = RiReferenceComponents::from(iri_ref("///"));
+        assert_eq!(c3.authority, Some(""));
+        assert_eq!(c3.path, "/");
+
+        let c4 = RiReferenceComponents::from(iri_ref("////"));
+        assert_eq!(c4.authority, Some(""));
+        assert_eq!(c4.path, "//");
+
+        let c5 = RiReferenceComponents::from(iri_ref("/////"));
+        assert_eq!(c5.authority, Some(""));
+        assert_eq!(c5.path, "///");
+    }
+}
+
+#[cfg(test)]
+mod tests_bench {
     use crate::types::IriReferenceStr;

     #[bench]
@@ -54,8 +118,7 @@ mod tests {
                 "/\u{03B1}\u{03B2}\u{03B3}/\u{03B1}\u{03B2}\u{03B3}/\u{03B1}\u{03B2}\u{03B3}",
                 "?k1=v1&k2=v2&k3=v3#fragment"
             );
-            let iri = IriReferenceStr::new(s);
-            iri
+            IriReferenceStr::new(s)
         });
     }

diff --git a/src/parser/details.rs b/src/parser/details.rs
index 5e63eb8e65b4..a993dc2cba78 100644
--- a/src/parser/details.rs
+++ b/src/parser/details.rs
@@ -1,7 +1,5 @@
 //! Parser implementatitons.

-use core::marker::PhantomData;
-
 use nom::{
     branch::alt,
     bytes::complete::{tag, take_while, take_while1, take_while_m_n},
@@ -14,7 +12,7 @@ use nom::{
 };

 use crate::{
-    parser::{char::is_sub_delim, RiReferenceComponents},
+    parser::char::is_sub_delim,
     spec::{Spec, SpecInternal, UriSpec},
 };

@@ -47,32 +45,6 @@ pub(crate) fn uri<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
     )(i)
 }

-/// Parses RFC 3986 / 3987 IRI and returns components.
-fn decompose_uri<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
-    i: &'a str,
-) -> IResult<&'a str, RiReferenceComponents<'a, S>, E> {
-    context(
-        "uri",
-        tuple((
-            scheme,
-            char_(':'),
-            decompose_hier_part::<E, S>,
-            opt(preceded(char_('?'), query::<E, S>)),
-            opt(preceded(char_('#'), fragment::<E, S>)),
-        ))
-        .map(|(scheme, _colon, (authority, path), query, fragment)| {
-            RiReferenceComponents {
-                scheme: Some(scheme),
-                authority,
-                path,
-                query,
-                fragment,
-                _spec: PhantomData,
-            }
-        }),
-    )(i)
-}
-
 /// Parses `hier-part` and `ihier-part` rules.
 fn hier_part<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
     i: &'a str,
@@ -91,24 +63,6 @@ fn hier_part<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
     )(i)
 }

-/// Parses `hier-part` and `ihier-part` rules and returns authority and path.
-fn decompose_hier_part<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
-    i: &'a str,
-) -> IResult<&'a str, (Option<&'a str>, &'a str), E> {
-    context(
-        "hier-part",
-        alt((
-            preceded(
-                tag("//"),
-                pair(authority::<E, S>.map(Some), path_abempty::<E, S>),
-            ),
-            path_absolute::<E, S>.map(|path| (None, path)),
-            path_rootless::<E, S>.map(|path| (None, path)),
-            path_empty::<E>.map(|path| (None, path)),
-        )),
-    )(i)
-}
-
 /// Parses RFC 3986 / 3987 IRI reference.
 pub(crate) fn uri_reference<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
     i: &'a str,
@@ -116,20 +70,6 @@ pub(crate) fn uri_reference<'a, E: ParseError<&'a str> + ContextError<&'a str>,
     context("uri_reference", alt((uri::<E, S>, relative_ref::<E, S>)))(i)
 }

-/// Parses RFC 3986 / 3987 IRI reference and returns components.
-pub(crate) fn decompose_uri_reference<
-    'a,
-    E: ParseError<&'a str> + ContextError<&'a str>,
-    S: Spec,
->(
-    i: &'a str,
-) -> IResult<&'a str, RiReferenceComponents<'a, S>, E> {
-    context(
-        "uri_reference",
-        alt((decompose_uri::<E, S>, decompose_relative_ref::<E, S>)),
-    )(i)
-}
-
 /// Parses RFC 3986 / 3987 absolute IRI.
 pub(crate) fn absolute_uri<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
     i: &'a str,
@@ -159,30 +99,6 @@ pub(crate) fn relative_ref<'a, E: ParseError<&'a str> + ContextError<&'a str>, S
     )(i)
 }

-/// Parses RFC 3986 / 3987 relative reference and returns components.
-fn decompose_relative_ref<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
-    i: &'a str,
-) -> IResult<&'a str, RiReferenceComponents<'a, S>, E> {
-    context(
-        "relative_ref",
-        tuple((
-            decompose_relative_part::<E, S>,
-            opt(preceded(char_('?'), query::<E, S>)),
-            opt(preceded(char_('#'), fragment::<E, S>)),
-        ))
-        .map(
-            |((authority, path), query, fragment)| RiReferenceComponents {
-                scheme: None,
-                authority,
-                path,
-                query,
-                fragment,
-                _spec: PhantomData,
-            },
-        ),
-    )(i)
-}
-
 /// Parses `relative_part` rule.
 fn relative_part<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
     i: &'a str,
@@ -198,24 +114,6 @@ fn relative_part<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
     )(i)
 }

-/// Parses `relative_part` rule and returns authority and path.
-fn decompose_relative_part<'a, E: ParseError<&'a str> + ContextError<&'a str>, S: Spec>(
-    i: &'a str,
-) -> IResult<&'a str, (Option<&'a str>, &'a str), E> {
-    context(
-        "relative-part",
-        alt((
-            preceded(
-                tag("//"),
-                pair(authority::<E, S>.map(Some), path_abempty::<E, S>),
-            ),
-            path_absolute::<E, S>.map(|path| (None, path)),
-            path_noscheme::<E, S>.map(|path| (None, path)),
-            path_empty.map(|path| (None, path)),
-        )),
-    )(i)
-}
-
 /// Parses `scheme` rule.
 fn scheme<'a, E: ParseError<&'a str> + ContextError<&'a str>>(
     i: &'a str,
@@ -853,64 +751,4 @@ mod tests {
             &["", "/", "//", "///", "////", "/////"]
         );
     }
-
-    #[test]
-    fn test_decompose_hier_part_only_slashes() {
-        assert_complete!(decompose_hier_part::<Error<'_>, IriSpec>, "", (None, ""));
-        assert_complete!(decompose_hier_part::<Error<'_>, IriSpec>, "/", (None, "/"));
-        assert_complete!(
-            decompose_hier_part::<Error<'_>, IriSpec>,
-            "//",
-            (Some(""), "")
-        );
-        assert_complete!(
-            decompose_hier_part::<Error<'_>, IriSpec>,
-            "///",
-            (Some(""), "/")
-        );
-        assert_complete!(
-            decompose_hier_part::<Error<'_>, IriSpec>,
-            "////",
-            (Some(""), "//")
-        );
-        assert_complete!(
-            decompose_hier_part::<Error<'_>, IriSpec>,
-            "/////",
-            (Some(""), "///")
-        );
-    }
-
-    #[test]
-    fn test_decompose_relative_part_only_slashes() {
-        assert_complete!(
-            decompose_relative_part::<Error<'_>, IriSpec>,
-            "",
-            (None, "")
-        );
-        assert_complete!(
-            decompose_relative_part::<Error<'_>, IriSpec>,
-            "/",
-            (None, "/")
-        );
-        assert_complete!(
-            decompose_relative_part::<Error<'_>, IriSpec>,
-            "//",
-            (Some(""), "")
-        );
-        assert_complete!(
-            decompose_relative_part::<Error<'_>, IriSpec>,
-            "///",
-            (Some(""), "/")
-        );
-        assert_complete!(
-            decompose_relative_part::<Error<'_>, IriSpec>,
-            "////",
-            (Some(""), "//")
-        );
-        assert_complete!(
-            decompose_relative_part::<Error<'_>, IriSpec>,
-            "/////",
-            (Some(""), "///")
-        );
-    }
 }
diff --git a/src/parser/validated.rs b/src/parser/validated.rs
new file mode 100644
index 000000000000..cdbbc0fcf738
--- /dev/null
+++ b/src/parser/validated.rs
@@ -0,0 +1,112 @@
+//! Parsers for already validated string.
+//!
+//! Parsers in this module make assumptions below:
+//!
+//! * The given IRI strings are valid.
+//! * The parser is parsing the "correct position" in the IRI.
+
+use core::marker::PhantomData;
+
+use crate::parser::RiReferenceComponents;
+use crate::spec::Spec;
+use crate::types::RiReferenceStr;
+
+/// Eats a scheme and a following colon if available, and returns the scheme.
+///
+/// This should be called at the head of an `IRI-reference`.
+fn scheme_colon(i: &str) -> (&str, Option<&str>) {
+    // Get a string before the first colon.
+    let scheme = match i.find(&[':', '/']).map(|pos| (pos, i.as_bytes()[pos])) {
+        Some((_, b'/')) => {
+            // A slash appears before the first colon. This is a relative IRI reference.
+            // Example: `/foo/bar`, `//[2001:db8::1]/`, `//example.com:80/`.
+            return (i, None);
+        }
+        Some((colon_pos, c)) => {
+            debug_assert_eq!(c, b':');
+            // Scheme.
+            &i[..colon_pos]
+        }
+        None => {
+            // No colon or no slash appears. This is a relative IRI reference.
+            return (i, None);
+        }
+    };
+    let rest = &i[(scheme.len() + 1)..];
+    debug_assert!(!scheme.is_empty());
+    debug_assert!(scheme.as_bytes()[0].is_ascii_alphabetic());
+    debug_assert!(scheme
+        .bytes()
+        .skip(1)
+        .all(|b| b.is_ascii_alphanumeric() || [b'+', b'-', b'.'].contains(&b)));
+    (rest, Some(scheme))
+}
+
+/// Eats double slash and the following authority if available, and returns the authority.
+///
+/// This should be called at the head of an `IRI-reference`, or at the result of `scheme_colon`.
+fn slash_slash_authority(i: &str) -> (&str, Option<&str>) {
+    let s = match i.strip_prefix("//") {
+        Some(rest) => rest,
+        None => return (i, None),
+    };
+    // `i` might match `path-abempty` (which can start with `//`), but it is not
+    // allowed as `relative-part`, so no need to care `path-abempty` rule here.
+    // A slash won't appear in `authority`.
+    let authority_end = s.find('/').unwrap_or(s.len());
+    let (authority, rest) = s.split_at(authority_end);
+
+    (rest, Some(authority))
+}
+
+/// Eats a string until the query, and returns that part (excluding `?` for the query).
+fn until_query(i: &str) -> (&str, &str) {
+    // `?` won't appear before the query part.
+    let before_query_pos = i.find(&['?', '#']).unwrap_or(i.len());
+    let (before_query, rest) = i.split_at(before_query_pos);
+    (rest, before_query)
+}
+
+/// Decomposes query and fragment, if available.
+///
+/// The string must starts with `?`, or `#`, or be empty.
+fn decompose_query_and_fragment(i: &str) -> (Option<&str>, Option<&str>) {
+    match i.as_bytes().get(0).copied() {
+        None => (None, None),
+        Some(b'?') => {
+            let rest = &i[1..];
+            match rest.find('#') {
+                Some(hash_pos) => (Some(&rest[..hash_pos]), Some(&rest[(hash_pos + 1)..])),
+                None => (Some(rest), None),
+            }
+        }
+        Some(c) => {
+            debug_assert_eq!(c, b'#');
+            (None, Some(&i[1..]))
+        }
+    }
+}
+
+/// Decomposes the given valid `IRI-reference`.
+pub(super) fn decompose_iri_reference<S: Spec>(
+    i: &RiReferenceStr<S>,
+) -> RiReferenceComponents<'_, S> {
+    /// Inner function to avoid unnecessary monomorphizations on `S`.
+    fn decompose(i: &str) -> (Option<&str>, Option<&str>, &str, Option<&str>, Option<&str>) {
+        let (i, scheme) = scheme_colon(i);
+        let (i, authority) = slash_slash_authority(i);
+        let (i, path) = until_query(i);
+        let (query, fragment) = decompose_query_and_fragment(i);
+        (scheme, authority, path, query, fragment)
+    }
+
+    let (scheme, authority, path, query, fragment) = decompose(i.as_str());
+    RiReferenceComponents {
+        scheme,
+        authority,
+        path,
+        query,
+        fragment,
+        _spec: PhantomData,
+    }
+}

Before:

test parser::tests::bench_parse_host   ... bench:       2,367 ns/iter (+/- 46)
test parser::tests::bench_parse_normal ... bench:       1,919 ns/iter (+/- 37)
test resolve::tests::bench_resolve     ... bench:       3,521 ns/iter (+/- 52)

After:

test parser::tests_bench::bench_parse_host   ... bench:       2,344 ns/iter (+/- 48)
test parser::tests_bench::bench_parse_normal ... bench:       2,117 ns/iter (+/- 23)
test resolve::tests::bench_resolve           ... bench:       1,362 ns/iter (+/- 22)

WOW. resolve() is now 2.5x faster for loooong IRIs.

Now I'm quite confident that I should rewrite parsers.

lo48576 commented 2 years ago

Oops, impl<'a, 'b, const N: usize> Pattern<'a> for &'b [char; N] is introduced since Rust 1.58.0, which will be released at 2022-01-13. I'll bump MSRV to 1.58, and delay the v0.5.0 release even if this refactoring is done earlier than Rust 1.58 release.

EDIT: I avoided to use string.find(&[a, b, c]) so this is not a problem now.

lo48576 commented 2 years ago

Current progress:

(Note that ordering of benches is changed)

test resolve::tests_bench::bench_resolve ... bench:         969 ns/iter (+/- 15)
test tests_bench::bench_parse_host       ... bench:         548 ns/iter (+/- 30)
test tests_bench::bench_parse_normal     ... bench:         454 ns/iter (+/- 6)
lo48576 commented 2 years ago

Merged to develop branch, but I'm still enhancing tests and benchmarks.

lo48576 commented 2 years ago

Done. Will be included in the next release.

lo48576 commented 2 years ago

Bonus: dependencies for nom is dropped.