Avoid LINQ and boxed-enumerator allocations in parameter binding#337
Merged
Giorgi merged 1 commit intoJul 11, 2026
Merged
Conversation
PreparedStatement.BindParameters ran a LINQ OfType<>().Any(...) and iterated the parameters with foreach over the non-generic collection (which boxed the List<T> struct enumerator) on every command execution. Replace both with index-based loops over the typed indexer. This removes ~128 bytes and 2-3 allocations per command execution, which matters in tight parameterized-query loops. Execution time is unchanged, as the native prepare/execute dominates.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #337 +/- ##
===========================================
- Coverage 87.58% 87.58% -0.01%
===========================================
Files 77 77
Lines 3134 3141 +7
Branches 463 465 +2
===========================================
+ Hits 2745 2751 +6
- Misses 275 276 +1
Partials 114 114 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
Author
|
Just flagging that the macOS red X isn't from this change — it's the Coveralls upload step, which dies trying to Happy to send a small CI fix for it separately if that's helpful. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's Changed
PreparedStatement.BindParametersruns on every command execution. It didtwo things that allocate each time:
parameterCollection.OfType<DuckDBParameter>().Any(p => …)— allocates aLINQ iterator.
foreach (DuckDBParameter param in parameterCollection)over the non-genericDbParameterCollection— boxes theList<T>struct enumerator.Both are replaced with index-based loops over the existing typed indexer, so the
detection of named parameters and the bind loop no longer allocate.
Benchmark
5,000 executions of a 3-parameter scalar query (
SELECT $a + $b + $c), reusingone command. BenchmarkDotNet, .NET 10, Apple M4 Pro:
That is ~128 bytes and 2–3 allocations removed per execution. It won't move
the wall clock — native prepare/execute dominates each call — but it cuts steady
GC pressure in tight parameterized-query loops (the rest of the 5.91 MB is
unavoidable per-query allocation from result handling).
Tests
Full existing suite passes locally (6,895 tests); the parameter and command
suites were run specifically against this change.
Companion to #336. The reproducible benchmark lives in the
DuckDB.NET.Benchmarksproject introduced there — I kept this PR to the single source change to avoid
overlapping that project. Happy to add a
ParameterBindingBenchmarkto it as afollow-up once #336 lands, if useful.