v0.7.0 - February 03, 2026
Contributors
@purpleclay (
7commits)
Performance Improvements
-
841e0d9optimize Take using utf8.DecodeRuneInString avoiding allocations (#106) (@purpleclay)Use
utf8.DecodeRuneInStringto 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) -
a54ca13optimize AnyChar and Satisfy using utf8.DecodeRuneInString avoiding allocations (#104) (@purpleclay)Use
utf8.DecodeRuneInStringinstead 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) -
04bb974pre-compute character sets for Any and Not combinators (#103) (@purpleclay)Build a
map[rune]struct{}at combinator creation time to reduce character lookup complexity fromO(m)toO(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) -
b27af34replace len(string(rune)) with utf8.RuneLen avoiding temporary string allocations (#100) (@purpleclay)Replace the
len(string(c))pattern withutf8.RuneLen(c)across predicate loops in basic.go and predicate.go. This avoids creating temporary string allocations when calculating rune byte lengths.Also optimized
EscapedandEscapedTransformto useutf8.DecodeRuneInStringinstead of converting the entire remaining string to[]runejust 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) -
742117boptimize 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) -
ba03235optimize Char, OneOf, and NoneOf using utf8.DecodeRuneInString avoiding allocations (#98) (@purpleclay)Replace
[]rune(s)conversions withutf8.DecodeRuneInStringto 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