You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ace87e0
Browse filesBrowse the repository at this point in the historyBrowse files
Benchmarks: two ODX Map(Func) paths in the IN6 probe, with and without the async-read opt-in (#143)
The IN6 probe measured the ODX Map<T>() paths and the hand-written provider paths, but not the Map(Func<IDataRecord,T>) selector path that Default-mode callers use. Two paths are added, selector+Read (Map(selector).ToListAsync(), rows read with Read) and selector+EnableAsyncRead (the same after EnableAsyncRead(), rows read with ReadAsync), so the selector path has an ODX-on-both-sides allocation and timing pair. They run in S2 only, with the existing hand-written 20-column selector carried on the AsyncPlan, and are checked field by field against Map<T>() on the first 5 rows before measuring; --selftest-in6 covers them on the synthetic provider. The default --paths set is unchanged.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
/// <summary>The hand-written Map(Func<IDataRecord, TRow>) selector for the selector+Read and selector+EnableAsyncRead paths (S2 only), or null.</summary>
59
+
publicFunc<IDataRecord,TRow>?Selector{get;init;}
60
+
58
61
/// <summary>Reads the rows (at most maxRows) with ExecuteReaderAsync, ReadAsync and the async getters; drains the other result sets when it read them all.</summary>
Copy file name to clipboardExpand all lines: Benchmarks/Open.Database.Extensions.Benchmarks/In6Probe.cs
+36-3Lines changed: 36 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -96,7 +96,7 @@ public static class In6Probe
96
96
--pressure auto|N|off|both auto or N: K busy thread-pool items for the "pressure on" cells (default auto = processor count x 2); off: no pressure cells for any scenario; both (default): pressure off and on
97
97
--rows 10000 --warmup 5 --ops 20 rows per scenario, warm-up and measured operations per cell (per path)
98
98
--scenarios S1,S2,S3 which SQL Server scenarios run (default all three; S1 width 5, S2 width 20, S3 about 8 KB per row)
99
-
--paths <list> which read paths run (default sync,async+Read,EnableAsyncRead; also P4 = hand-written async getters, P5 = the same with SequentialAccess, P6 = raw provider: ReadAsync + sync getters, no ODX, no wrapper, timed inline), e.g. --paths sync,P4,P5
99
+
--paths <list> which read paths run (default sync,async+Read,EnableAsyncRead; also P4 = hand-written async getters, P5 = the same with SequentialAccess, P6 = raw provider: ReadAsync + sync getters, no ODX, no wrapper, timed inline; selector+Read and selector+EnableAsyncRead = ODX Map(Func<IDataRecord,T>).ToListAsync() with the hand-written selector, rows read with Read or, with the opt-in, with ReadAsync; S2 only), e.g. --paths sync,P4,P5
100
100
--ops-s3 N measured operations for S3 only (default: the value of --ops)
101
101
--pressure-s3 both|off with off, S3 runs only its pressure-off cells (S1 and S2 still run both); default both
102
102
--sql-tcp <host,port> run the SQL Server part against a server over TCP (for example 127.0.0.1,14333) as user sa, TrustServerCertificate, default packet size, instead of LocalDB;
if(s.Paths.Contains("P4"))Console.WriteLine("P4 (hand-written, not ODX): ExecuteReaderAsync with the default behavior, ReadAsync, and EVERY cell read with await GetFieldValueAsync<T>(i); IsDBNullAsync only for the columns that can be NULL; builds the same row objects (checked on the first 5 rows before measuring)");
308
308
if(s.Paths.Contains("P6"))Console.WriteLine("P6 (raw, not ODX, NO timing wrapper): the provider's own connection and command, ExecuteReaderAsync (default behavior), await ReadAsync per row, then SYNC typed getters (the same cells and row objects); timed INLINE in the probe loop: 'inline await ReadAsync ms sum/max' = the time from before each ReadAsync call to after its await (a truly async completion and the continuation's wait for a thread are inside it), 'inline getters ms sum' = the time in the typed getters, the ReadAsync incomplete count = calls whose task was not complete when checked, 'wait ms' = the await time of those calls (not the wait until completion), 'wait rows' = the rows read so far at those calls; reader-open is timed inline too");
309
309
if(s.Paths.Contains("P5"))Console.WriteLine("P5 (hand-written, not ODX): the same as P4 with CommandBehavior.SequentialAccess, cells read strictly in ordinal order");
310
+
if(s.Paths.Contains("selector+Read"))Console.WriteLine("selector+Read (ODX): Map(hand-written Func<IDataRecord,T> over all 20 columns, typed getters).ToListAsync(), async execute + Read; S2 only (checked against Map<T>() on the first 5 rows before measuring)");
311
+
if(s.Paths.Contains("selector+EnableAsyncRead"))Console.WriteLine("selector+EnableAsyncRead (ODX): the same with EnableAsyncRead() first, async execute + ReadAsync; S2 only (checked the same way)");
// The paths of one scenario over one (wrapped) connection: the three ODX paths, and P4 and P5 (hand-written, async getters) when a plan is given. T is the row type.
staticAsyncPlan<T>RequirePlan<T>(AsyncPlan<T>?plan)=>plan??thrownewInvalidOperationException("this scenario has no hand-written plan for the async getter paths");
494
498
499
+
staticFunc<IDataRecord,T>RequireSelector<T>(AsyncPlan<T>?plan)=>plan?.Selector??thrownewInvalidOperationException("this scenario has no hand-written selector (S2 only)");
if(planis not null&&(s.Paths.Contains("P4")||s.Paths.Contains("P5")||s.Paths.Contains("P6")))VerifyAsyncPaths(timed,sql,plan,s.Paths);
545
+
if(planis not null&&(s.Paths.Contains("P4")||s.Paths.Contains("P5")||s.Paths.Contains("P6")||s.Paths.Contains("selector+Read")||s.Paths.Contains("selector+EnableAsyncRead")))VerifyAsyncPaths(timed,sql,plan,s.Paths);
Check(shapeOk,$"{r.Path}: getter wait rows are not 100, 200, ..., 900 each seen {GetterOps} times: {spots?.Format()}");
1082
1093
}
1083
1094
1095
+
// The two ODX selector paths (width 20, the hand-written selector): checked on the first rows against Map<T>(), then run; Read for the first, ReadAsync for the second.
if(r.Path=="selector+EnableAsyncRead")Check(r.ReadAsyncCalls==Rows+1&&r.ReadCalls==0,$"{r.Path}: {r.ReadAsyncCalls} ReadAsync and {r.ReadCalls} Read calls per operation, expected {Rows+1} and 0");
1114
+
elseCheck(r.ReadAsyncCalls==0&&r.ReadCalls==Rows+1,$"{r.Path}: {r.ReadAsyncCalls} ReadAsync and {r.ReadCalls} Read calls per operation, expected 0 and {Rows+1}");
1115
+
}
1116
+
1084
1117
// The recorder inside the synthetic reader and the wrappers around it time the same readers: the wrapper's window contains the recorder's.
0 commit comments