Skip to content

v0.7.0

Latest

Choose a tag to compare

@github-actions github-actions released this 03 Feb 06:49
· 1 commit to main since this release
v0.7.0
a6a166b

v0.7.0 - February 03, 2026

Contributors

Performance Improvements

  • 841e0d9 optimize Take using utf8.DecodeRuneInString avoiding allocations (#106) (@purpleclay)

    Use utf8.DecodeRuneInString to iterate through runes instead of converting the entire string to []rune, eliminating unnecessary allocations.

    benchstat results (n=10):

                    │   baseline   │             optimized              │
                    │    sec/op    │   sec/op     vs base               │
    Take/Ascii-12      75.35n ± 1%   15.65n ± 0%  -79.24% (p=0.000)
    Take/Unicode-12    95.28n ± 0%   10.17n ± 1%  -89.32% (p=0.000)
  • a54ca13 optimize AnyChar and Satisfy using utf8.DecodeRuneInString avoiding allocations (#104) (@purpleclay)

    Use utf8.DecodeRuneInString instead of []rune(s) conversion to extract the first character, eliminating unnecessary allocations.

    benchstat results (n=10):

                         │   baseline   │             optimized              │
                         │    sec/op    │   sec/op     vs base               │
    AnyChar/Ascii-10        56.51n ± 1%    1.80n ± 11%  -96.81% (p=0.000)
    AnyChar/Unicode-10      55.43n ± 0%    1.65n ± 10%  -97.03% (p=0.000)
    Satisfy/Ascii-10        56.94n ± 1%    2.06n ±  7%  -96.39% (p=0.000)
    Satisfy/Unicode-10      54.70n ± 2%    1.78n ±  5%  -96.74% (p=0.000)
  • 04bb974 pre-compute character sets for Any and Not combinators (#103) (@purpleclay)

    Build a map[rune]struct{} at combinator creation time to reduce character lookup complexity from O(m) to O(1) per input character, where m is the charset length.

    A threshold of 8 characters is used to determine when to use the map-based approach versus linear scanning. For small charsets (<8 chars), linear scanning is faster due to better cache locality and lower overhead. For larger charsets, the map lookup becomes more efficient.

    benchstat results (n=10):

                         │   baseline   │             optimized              │
                         │    sec/op    │   sec/op     vs base               │
    Any/Small/Ascii-12       11.45n ± 1%    9.53n ± 1%  -16.73% (p=0.000)
    Any/Large/Ascii-12       92.95n ± 0%   67.51n ± 2%  -27.38% (p=0.000)
    Any/Small/Unicode-12     29.05n ± 1%   27.92n ± 1%   -3.87% (p=0.000)
    Any/Large/Unicode-12    129.10n ± 1%   60.27n ± 2%  -53.32% (p=0.000)
    Not/Small/Ascii-12       62.27n ± 1%   52.23n ± 3%  -16.12% (p=0.000)
    Not/Large/Ascii-12       142.9n ± 1%   109.8n ± 0%  -23.20% (p=0.000)
    Not/Small/Unicode-12     98.23n ± 0%   96.01n ± 1%   -2.26% (p=0.000)
    Not/Large/Unicode-12     273.8n ± 0%   110.3n ± 1%  -59.70% (p=0.000)
  • b27af34 replace len(string(rune)) with utf8.RuneLen avoiding temporary string allocations (#100) (@purpleclay)

    Replace the len(string(c)) pattern with utf8.RuneLen(c) across predicate loops in basic.go and predicate.go. This avoids creating temporary string allocations when calculating rune byte lengths.

    Also optimized Escaped and EscapedTransform to use utf8.DecodeRuneInString instead of converting the entire remaining string to []rune just to check the first character.

    benchstat results (n=10):

                              │   baseline   │             optimized              │
                              │    sec/op    │   sec/op     vs base               │
    Any/Ascii-12                 40.65n ± 1%   38.29n ± 0%   -5.79% (p=0.000)
    Any/Unicode-12               62.34n ± 1%   54.76n ± 2%  -12.17% (p=0.000)
    Not/Ascii-12                 67.46n ± 1%   63.11n ± 1%   -6.44% (p=0.000)
    Not/Unicode-12              116.65n ± 1%   98.93n ± 0%  -15.19% (p=0.000)
    While/Digit-12               28.36n ± 0%   23.43n ± 0%  -17.39% (p=0.000)
    While/Letter/Ascii-12       10.480n ± 1%   8.715n ± 1%  -16.85% (p=0.000)
    While/Letter/Unicode-12      249.9n ± 0%   222.9n ± 1%  -10.80% (p=0.000)
    While/Alphanumeric-12        60.57n ± 0%   50.73n ± 0%  -16.25% (p=0.000)
    While/Space-12               21.95n ± 0%   17.52n ± 6%  -20.19% (p=0.000)
    WhileNot/Digit/Ascii-12      125.7n ± 1%   103.6n ± 0%  -17.55% (p=0.000)
    WhileNot/Digit/Unicode-12    205.6n ± 1%   178.8n ± 1%  -13.04% (p=0.000)
  • 742117b optimize Eol by inlining logic avoiding combinator composition overhead (#99) (@purpleclay)

    Replace the composed combinator chain Suffixed(WhileNotN(IsLineEnding, 0), Opt(Crlf())) with inlined logic using direct string iteration and byte comparisons to avoid heap allocations from combinator composition on each call.

    benchstat results (n=10):

                   │   baseline   │             optimized              │
                   │    sec/op    │   sec/op     vs base               │
    Eol/Ascii-12      159.95n ± 1%   54.90n ± 0%  -65.67% (p=0.000)
    Eol/Unicode-12    146.55n ± 0%   55.17n ± 1%  -62.35% (p=0.000)
  • ba03235 optimize Char, OneOf, and NoneOf using utf8.DecodeRuneInString avoiding allocations (#98) (@purpleclay)

    Replace []rune(s) conversions with utf8.DecodeRuneInString to avoid heap allocations when checking the first character of input strings.

    benchstat results (n=10):

                      │   baseline   │            optimized             │
                      │    sec/op    │   sec/op     vs base             │
    Char/Ascii-12        60.20n ± 3%   1.75n ± 6%   -97.09% (p=0.000)
    Char/Unicode-12      81.64n ± 1%   2.34n ± 1%   -97.13% (p=0.000)
    OneOf/Ascii-12       63.33n ± 1%  11.12n ± 2%   -82.44% (p=0.000)
    OneOf/Unicode-12     77.16n ± 1%   4.34n ± 1%   -94.37% (p=0.000)
    NoneOf/Ascii-12      55.41n ± 2%   3.35n ± 1%   -93.95% (p=0.000)
    NoneOf/Unicode-12    79.86n ± 1%   6.13n ± 1%   -92.33% (p=0.000)

Generated with release-note