Jaunty Optimization Roadmap
This document outlines the prioritized tasks for improving the performance, memory utilization, and code quality of the Jaunty micro-ORM.
Phase 1: Critical Hot-Path Optimizations (High Impact)
Task 1: Eliminate Reflection in Write Operations
- Target Files:
src/Jaunty/Internals/Write/InsertCore.cs,UpdateCore.cs - Context:
BindInsertParametersandBindUpdateParameterscurrently callPropertyInfo.GetValue(entity)in a loop for every row. - Implementation: Create an internal
WriteParameterCache<T>that compiles and caches a singleAction<IDbCommand, T>delegate for each entity type. This delegate should perform direct property access and add parameters to the command. - Goal: Zero reflection and zero boxing during INSERT/UPDATE parameter binding.
Task 2: Optimize Entity ID Population
- Target Files:
src/Jaunty/Internals/Write/InsertCore.cs(PopulateEntityIdmethod) - Context: Currently uses
GetProperty("Id")andSetValueon every successful insert. - Implementation: Move the ID-property resolution logic to
MetadataBuilder. Store a compiledAction<T, long>?setter inEntityMetadata. - Goal: Replace runtime reflection with a cached, compiled delegate call.
Task 3: Refactor Collection Parameter Expansion
- Target Files:
src/Jaunty/Internals/Parameters/ParameterBinder.cs(IsCollectionandExpandCollectionParameters) - Context:
IsCollectioncurrently allocates anew List<object?>and iterates the sourceIEnumerable, causing boxing for value types (e.g.,intIDs). - Implementation: Implement specialized checks for common types (e.g.,
int[],List<int>,string[]). Use a non-allocating iteration pattern orArrayPool<object>for heterogeneous collections. - Goal: Reduce heap allocations and boxing during
WHERE IN @Idsquery preparation.
Phase 2: Execution Engine Refinement (Medium-High Impact)
Task 4: High-Performance Expression Tree Setters
- Target Files:
src/Jaunty/Internals/Entity/MetadataCache.cs(CreateSettermethod) - Context: Currently uses
Convert.ChangeTypefor all assignments. - Implementation: Update the expression generator to check for primitive types. Emit direct casts or calls to
IDataRecord.GetInt32,GetString, etc., for common types. Fall back toConvert.ChangeTypeonly for complex conversions. - Goal: Reduce the CPU overhead of mapping database values to entity properties.
Task 5: Implement Multi-Entity Mapper Caching
- Target Files:
src/Jaunty/Internals/MultiEntityMapper.cs,src/Jaunty/Internals/QueryCore.cs - Context:
MultiEntityMapper<T1, T2>.Build(reader)compiles expression trees every time a multi-entity query (tuples) is executed. - Implementation: Introduce a
ConcurrentDictionaryinMultiEntityMapperto cache the mapping strategy. The key should be a combination of the column names and their ordinals from theIDataReader. - Goal: Avoid expensive expression compilation on every multi-entity query.
Phase 3: Memory Efficiency & Modernization (Medium Impact)
Task 6: Zero-Allocation SQL Parameter Parsing (.NET 8+)
- Target Files:
src/Jaunty/Internals/Parameters/SqlParameterParser.cs - Context: Uses
string.Substringwhen extracting parameter names. - Implementation: Use
#if NET8_0_OR_GREATERto utilizeReadOnlySpan<char>for parsing. Usestring.Internor a small internal pool for frequently seen parameter names if applicable. - Goal: Eliminate temporary string allocations during the SQL parsing phase.
Task 7: Cache Column-to-Property Mapping Signatures
- Target Files:
src/Jaunty/Internals/Entity/MetadataCache.cs(GetSettersmethod) - Context: Returns a
new PropertySetter<T>[]array on every execution. - Implementation: For common queries (like
SELECT *), cache thePropertySetter<T>[]array based on thereader.FieldCountand a hash of column names. - Goal: Reduce per-query array allocations.
Validation Strategy
- Benchmarks: Use
BenchmarkDotNetto verify performance gains. - Regression: Run existing integration tests (
dotnet test) to ensure mapping logic remains "Strict" and correct. - Memory: Use a profiler or
GC.GetTotalAllocatedByteschecks in tests to confirm reduced allocations.