Fix IAST strings deferred enumerable#8556
Conversation
BenchmarksBenchmark execution time: 2026-05-05 14:43:00 Comparing candidate commit 12d75eb in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 27 metrics, 0 unstable metrics, 59 known flaky benchmarks, 28 flaky benchmarks without significant changes.
|
andrewlock
left a comment
There was a problem hiding this comment.
Thanks for looking into this! I have a few suggestions I think we should consider:
- Is
ICollection<T>orIReadOnlyCollection<T>good enough? If so, that could avoid allocating for many more scenarios (would only allocate for "true"IEnumerable, notList<T>,T[],Stack<T>(read only colleciton) etc - Using
ToList()is probably preferable if it doesn't have to be an array - I think we should avoid the explicit
throwcalls
| throw new ArgumentNullException(nameof(values)); | ||
| } | ||
|
|
||
| return values as T[] ?? values.ToArray(); |
There was a problem hiding this comment.
I wonder... is T[] the correct choice here? Would ICollection<T> be sufficient? Also, should we call ToList() here - I assume it doesn't have to be an array? My reasons are:
- If the customer passes in a
List<T>(for example) this will unecessarily allocate currently. I think anICollectionimplementation would be "good enough" as it doesn't have the "delayed execution" semantics? OrIList<T>if we really need it? ToArray()generally allocates more thanToList()(because under the hood it uses a List, and then has to allocate a "final" array.
So maybe this is preferable (we'd need to adjust the method signatures obviously)
| return values as T[] ?? values.ToArray(); | |
| return values as ICollection<T> ?? values.ToList(); |
There was a problem hiding this comment.
Looking at the ICollection<T> or IReadOnlyCollection<T>, I guess IReadOnlyCollection<T> would be the preferred choice (Stack and Queue would be missed if only using the ICollection<T>, no?)
About the .ToArray() vs .ToList(), I'm good changing that to .ToList().
However, .ToArray() seems to be widely used in the IAST source (might be worth to change them in the future?)
…rAspects.cs Co-authored-by: Andrew Lock <[email protected]>
Co-authored-by: Andrew Lock <[email protected]>
| return string.Join(separator, materializedValues); | ||
| } | ||
|
|
||
| private static IReadOnlyCollection<T> Materialize<T>(IEnumerable<T> values) |
There was a problem hiding this comment.
NIT: Since this is the same Materialize code, it might be worth to extract to a shared class.
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8556) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (75ms) : 71, 78
master - mean (74ms) : 70, 79
section Bailout
This PR (8556) - mean (77ms) : 75, 79
master - mean (77ms) : 75, 79
section CallTarget+Inlining+NGEN
This PR (8556) - mean (1,081ms) : 1033, 1129
master - mean (1,085ms) : 1041, 1129
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (116ms) : 110, 123
master - mean (115ms) : 110, 120
section Bailout
This PR (8556) - mean (114ms) : 111, 117
master - mean (115ms) : 112, 118
section CallTarget+Inlining+NGEN
This PR (8556) - mean (783ms) : 756, 809
master - mean (787ms) : 754, 820
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (101ms) : 97, 106
master - mean (101ms) : 97, 105
section Bailout
This PR (8556) - mean (107ms) : 102, 113
master - mean (103ms) : 98, 108
section CallTarget+Inlining+NGEN
This PR (8556) - mean (944ms) : 903, 985
master - mean (941ms) : 903, 980
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (101ms) : 98, 105
master - mean (103ms) : 97, 109
section Bailout
This PR (8556) - mean (101ms) : 99, 104
master - mean (102ms) : 101, 104
section CallTarget+Inlining+NGEN
This PR (8556) - mean (828ms) : 787, 869
master - mean (824ms) : 781, 868
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (203ms) : 197, 209
master - mean (201ms) : 196, 207
section Bailout
This PR (8556) - mean (206ms) : 201, 210
master - mean (204ms) : 198, 211
section CallTarget+Inlining+NGEN
This PR (8556) - mean (1,193ms) : 1147, 1240
master - mean (1,184ms) : 1139, 1229
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (290ms) : 281, 299
master - mean (284ms) : 272, 295
section Bailout
This PR (8556) - mean (289ms) : 281, 297
master - mean (285ms) : 274, 297
section CallTarget+Inlining+NGEN
This PR (8556) - mean (955ms) : 928, 982
master - mean (938ms) : 910, 966
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (283ms) : 273, 293
master - mean (277ms) : 265, 289
section Bailout
This PR (8556) - mean (283ms) : 273, 293
master - mean (279ms) : 266, 292
section CallTarget+Inlining+NGEN
This PR (8556) - mean (1,155ms) : 1106, 1204
master - mean (1,151ms) : 1108, 1193
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8556) - mean (277ms) : 265, 289
master - mean (281ms) : 270, 293
section Bailout
This PR (8556) - mean (276ms) : 267, 285
master - mean (283ms) : 273, 292
section CallTarget+Inlining+NGEN
This PR (8556) - mean (1,043ms) : 992, 1094
master - mean (1,038ms) : 986, 1091
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Summary of changes
string.Join,string.Concat, andStringBuilder.AppendJoin.Reason for change
IAST propagation was re-enumerating deferred
IEnumerable<T>inputs after the original .NET API had already consumed them.For deferred sequences with side effects, this changed application behavior.
We got case where when using SqlKata/Npgsql, the second enumeration duplicated query compiler side effects and misaligned SQL bindings.
Implementation details
Test coverage
string.Join,string.Concat, andStringBuilder.AppendJoin.https://datadoghq.atlassian.net/browse/APPSEC-63121