Releases: Prawirdani/qparser
v1.3.5
This release introduces performance enhancements, focusing on eliminating bottlenecks and improving concurrency.
Changes:
- Eliminated splitAndTrim bottleneck: Replaced the split and trim mechanism with direct slice parsing using a new
parseSliceFromStringsfunction, avoiding intermediate string allocations. - Concurrent access optimization: Replaced map + RWMutex with
sync.Mapfor better concurrent performance. - Enhanced time.Time parsing: Improved parsing efficiency for date and time fields.
- New benchmark methods: Added parallel benchmarks to better measure performance under various conditions.
Benchmark Results Comparison:
Previous Benchmarks:
Benchmark/Small-8 224376 4602 ns/op 2680 B/op 13 allocs/op
Benchmark/Medium-8 246818 4851 ns/op 2720 B/op 13 allocs/op
Benchmark/Large-8 173422 5867 ns/op 3056 B/op 21 allocs/op
Benchmark/LargeWithDate-8 160401 6543 ns/op 3104 B/op 23 allocs/op
New Benchmarks:
goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
=== Sequential ===========================================================================================
Benchmark/Seq/Minimal-8 1204855 983.8 ns/op 224 B/op 1 allocs/op
Benchmark/Seq/1-date-8 930016 1233 ns/op 248 B/op 2 allocs/op
Benchmark/Seq/2-dates-8 626414 1599 ns/op 272 B/op 3 allocs/op
Benchmark/Seq/slices-string-1*50-8 348960 2924 ns/op 1144 B/op 3 allocs/op
Benchmark/Seq/slices-int-1*50-8 292065 3613 ns/op 664 B/op 3 allocs/op
Benchmark/Seq/slices-2*50-8 202550 5514 ns/op 1584 B/op 5 allocs/op
Benchmark/Seq/slices-2*100-8 123055 9351 ns/op 2960 B/op 5 allocs/op
Benchmark/Seq/2*25-slices-and-2-dates-8 257817 4158 ns/op 944 B/op 7 allocs/op
=== Parallel =============================================================================================
Benchmark/Par/Minimal-8 4529499 261.3 ns/op 224 B/op 1 allocs/op
Benchmark/Par/1-date-8 3590239 356.3 ns/op 248 B/op 2 allocs/op
Benchmark/Par/2-dates-8 2865769 420.1 ns/op 272 B/op 3 allocs/op
Benchmark/Par/slices-string-1*50-8 1261774 963.2 ns/op 1144 B/op 3 allocs/op
Benchmark/Par/slices-int-1*50-8 1136395 1025 ns/op 664 B/op 3 allocs/op
Benchmark/Par/slices-2*50-8 684651 1758 ns/op 1584 B/op 5 allocs/op
Benchmark/Par/slices-2*100-8 410012 2982 ns/op 2960 B/op 5 allocs/op
Benchmark/Par/2*25-slices-and-2-dates-8 990036 1244 ns/op 944 B/op 7 allocs/op
Performance Gains:
- Sequential operations: Up to 4x faster execution times with significantly reduced memory allocations (from 13-23 allocs/op to 1-7 allocs/op).
- Memory efficiency: Dramatic reduction in bytes per operation, from thousands to hundreds.
- Overall, the changes provide substantial performance improvements while maintaining functionality.
v1.3.4
Changes
- API: Internal
parsefunction is now public asParse. - Errors: Enhanced messages with specific types (e.g., ErrInvalidValue) and field-specific reporting.
- Pointers: Always initialized; no longer reset to nil if unset, reducing overhead.
- Caching: Introduced reflection caching to avoid repeated struct field operations.
- Parsing: Optimized with direct switch on reflect.Kind for better performance.
Performance
Benchmark Results
Before:
goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
Benchmark/Small-8 91339 13457 ns/op 4720 B/op 111 allocs/op
Benchmark/Medium-8 69544 17252 ns/op 4784 B/op 113 allocs/op
Benchmark/Large-8 63728 18126 ns/op 5240 B/op 128 allocs/op
Benchmark/LargeWithDate-8 18294 67437 ns/op 36575 B/op 402 allocs/op
After:
goos: linux
goarch: amd64
cpu: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
Benchmark/Small-8 224376 4602 ns/op 2680 B/op 13 allocs/op
Benchmark/Medium-8 246818 4851 ns/op 2720 B/op 13 allocs/op
Benchmark/Large-8 173422 5867 ns/op 3056 B/op 21 allocs/op
Benchmark/LargeWithDate-8 160401 6543 ns/op 3104 B/op 23 allocs/op
Performance Improvements
The refactored version delivers massive performance improvements across all benchmark scenarios:
- Small: 2.5x faster (13,457ns → 4,602ns) with 94% fewer allocations (111 → 13)
- Medium: 3.6x faster (17,252ns → 4,851ns) with 89% fewer allocations (113 → 13)
- Large: 3.1x faster (18,126ns → 5,867ns) with 84% fewer allocations (128 → 21)
- LargeWithDate: 10.3x faster (67,437ns → 6,543ns) with 94% fewer allocations (402 → 23)
Key optimizations driving these gains:
-
Reflection Caching: Eliminated repeated struct field introspection by caching field metadata, reducing reflection overhead from O(n) to O(1) per struct type.
-
Direct Type Switching: Replaced function lookups with a raw switch statement on
reflect.KindinsetSingleValue, eliminating function call overhead and enabling compiler optimizations. -
Global Regex Optimization: Moved
timezoneFixRegexfrom function-level to global scope, preventing regex recompilation on every date parse call - the primary driver of the 10x improvement in LargeWithDate benchmarks. -
Memory Efficiency: Reduced allocations by pre-allocating slices and reusing cached field information, cutting memory pressure by up to 94% in date-heavy scenarios.
-
Pointer Optimization: Simplified pointer handling logic and eliminated unnecessary nil checks, streamlining the parsing path for pointer fields.
The most dramatic improvement comes in date parsing scenarios (LargeWithDate), where eliminating regex recompilation combined with caching and optimized type switching reduces parsing time from 67μs to just 6.5μs - a 10x speedup.
Full Changelog: v1.2.4...v1.3.4
v1.2.4
Changes
- Updated Go version to 1.24.5, used for development and benchmarks. No impact on consumers using older Go versions.
- Added initial benchmark suite to evaluate performance of the library.
Notes
- This release does not include breaking changes.
v1.2.3
🚀 New: time.Time Support
Added support for parsing time.Time, *time.Time, and their aliases.
Handles various standard formats including:
- RFC3339, RFC3339Nano
- Date-only (YYYY-MM-DD)
- Time-only (HH:MM:SS)
- With or without timezone offsets
- Supports up to nanosecond precision
v1.2.0
What's Changed
Repeated Keys Multiple Values Query
- The parser now supports repeated keys for multiple values query parameters not only by comma-separated values but also by repeated keys. This allows for more flexibility in handling query parameters. It also supports a combination of both methods.
Full Changelog: v1.1.0...v1.2.0
v1.1.0
What's Changed
Pointer Fields Handling
- Regular pointer fields and nested struct pointer fields now remain nil under the following conditions:
- The field is not tagged.
- The value for the related tag is not provided in the query parameters.
- For nested struct pointers: All fields within the nested struct are zero/empty.
Slice behavior
- Slices now remain nil if no corresponding values are provided in the query parameters.
Full Changelog: v1.0.0...v1.1.0
1.0.0
Initial Release 🎉