From 4f42c60821a95ebbea5cb180c2f8247c8d7e699d Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 11 Mar 2026 10:32:42 -0700 Subject: [PATCH 01/12] incorporated feedback from previous pr, iterated with 100+ scalar loop tests to come to this skill, moved from skill to reference in the performance skill --- .../analyzing-dotnet-performance/SKILL.md | 10 +- .../references/simd-vectorization.md | 191 ++++++++++++++++++ .../analyzing-dotnet-performance/eval.yaml | 72 +++++++ .../fixtures/simd-bit-reverser.cs | 16 ++ .../fixtures/simd-conditional-increment.cs | 15 ++ .../fixtures/simd-no-opportunity-catalog.cs | 38 ++++ .../fixtures/simd-tensor-primitives-minmax.cs | 22 ++ .../simd-tensor-primitives-product.cs | 14 ++ 8 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md create mode 100644 tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs create mode 100644 tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs create mode 100644 tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs create mode 100644 tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs create mode 100644 tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md index 7bf993fce6..ac17cda957 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md @@ -2,9 +2,10 @@ name: analyzing-dotnet-performance description: >- Scans .NET code for ~50 performance anti-patterns across async, memory, - strings, collections, LINQ, regex, serialization, and I/O with tiered - severity classification. Use when analyzing .NET code for optimization - opportunities, reviewing hot paths, or auditing allocation-heavy patterns. + strings, collections, LINQ, regex, serialization, I/O with tiered + severity classification, and scalar loops amenable to SIMD vectorization. + Use when analyzing .NET code for optimization opportunities, reviewing + hot paths, or auditing allocation-heavy patterns. --- # .NET Performance Patterns @@ -38,6 +39,8 @@ Scan C#/.NET code for performance anti-patterns and produce prioritized findings Try to load `references/critical-patterns.md` and the topic-specific reference files listed below. These contain detailed detection recipes and grep commands. +When scalar loops over contiguous numeric arrays or spans are detected, also load `references/simd-vectorization.md`. This reference provides TensorPrimitives API coverage and cross-platform Vector128/Vector256/Vector512 intrinsic patterns for optimizing hot-path scalar loops. + **If reference files are not found** (e.g., in a sandboxed environment or when the skill is embedded as instructions only), **skip file loading and proceed directly to Step 3** using the scan recipes listed inline below. Do not spend time searching the filesystem for reference files — if they aren't at the expected relative path, they aren't available. ### Step 2: Detect Code Signals and Select Topic Recipes @@ -51,6 +54,7 @@ Scan the code for signals that indicate which pattern categories to check. If re | `Regex`, `[GeneratedRegex]`, `Regex.Match`, `RegexOptions.Compiled` | Regex patterns | | `Dictionary<`, `List<`, `.ToList()`, `.Where(`, `.Select(`, LINQ methods, `static readonly Dictionary<` | Collections & LINQ | | `JsonSerializer`, `HttpClient`, `Stream`, `FileStream` | I/O & serialization | +| `for` / `foreach` loops over `byte[]`, `int[]`, `float[]`, `double[]`, `Span<`, `ReadOnlySpan<` with scalar arithmetic, comparison, or bitwise ops | SIMD vectorization (see `references/simd-vectorization.md`) | Always check structural patterns (unsealed classes) regardless of signals. diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md new file mode 100644 index 0000000000..809810c731 --- /dev/null +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md @@ -0,0 +1,191 @@ +# SIMD Vectorization + +## Decision Gate + +1. **Check for TensorPrimitives first.** If one or more TensorPrimitives methods cover the operation → use them. If the `.csproj` does NOT already reference `System.Numerics.Tensors`, **add the package**: ``. Then replace the scalar loop with TP calls and stop. See the full API table below. Compose multiple TP calls when needed (e.g., finding both min and max → `TensorPrimitives.Min(span)` + `TensorPrimitives.Max(span)` as two calls). Do NOT write manual Vector128 code for operations TP already handles. + +2. **Scalar loop over contiguous array/span** of `byte`, `short`, `int`, `long`, `float`, `double`? → Implement with explicit `Vector128` / `Vector256` / `Vector512` intrinsics using the patterns below. + +3. **No contiguous numeric array processing** (dictionary lookups, tree traversals, linked lists, state machines, string formatting, small collections, enum comparisons, recursive algorithms, decimal arithmetic)? → Report `[NO SIMD OPPORTUNITY]` and write a **full paragraph** explaining WHY, referencing the specific code characteristics that prevent vectorization (e.g., "State machines require sequential branching on enum values — there are no contiguous numeric arrays to process in parallel, and each transition depends on the previous state"). This explanation is graded. + +## TensorPrimitives API Reference + +Use for any float/double array operation that has a matching API below. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one** call: + +### Reductions (span → scalar) +| Operation | API | +|-----------|-----| +| Sum | `TensorPrimitives.Sum(span)` | +| Sum of squares | `TensorPrimitives.SumOfSquares(span)` | +| Sum of magnitudes (L1 norm) | `TensorPrimitives.SumOfMagnitudes(span)` | +| L2 norm | `TensorPrimitives.Norm(span)` | +| Product of all elements | `TensorPrimitives.Product(span)` | +| Min value | `TensorPrimitives.Min(span)` | +| Max value | `TensorPrimitives.Max(span)` | +| Index of max | `TensorPrimitives.IndexOfMax(span)` | +| Index of min | `TensorPrimitives.IndexOfMin(span)` | +| Dot product | `TensorPrimitives.Dot(a, b)` | +| Cosine similarity | `TensorPrimitives.CosineSimilarity(a, b)` | +| Euclidean distance | `TensorPrimitives.Distance(a, b)` | + +### Element-wise transforms (span → span) +| Operation | API | +|-----------|-----| +| Negate | `TensorPrimitives.Negate(src, dst)` | +| Abs | `TensorPrimitives.Abs(src, dst)` | +| Sqrt | `TensorPrimitives.Sqrt(src, dst)` | +| Exp | `TensorPrimitives.Exp(src, dst)` | +| Log | `TensorPrimitives.Log(src, dst)` | +| Log2 | `TensorPrimitives.Log2(src, dst)` | +| Tanh | `TensorPrimitives.Tanh(src, dst)` | +| Sigmoid | `TensorPrimitives.Sigmoid(src, dst)` | +| SoftMax | `TensorPrimitives.SoftMax(src, dst)` | +| Sinh | `TensorPrimitives.Sinh(src, dst)` | +| Cosh | `TensorPrimitives.Cosh(src, dst)` | +| Round | `TensorPrimitives.Round(src, dst)` | +| Floor | `TensorPrimitives.Floor(src, dst)` | +| Ceiling | `TensorPrimitives.Ceiling(src, dst)` | +| CopySign | `TensorPrimitives.CopySign(src, sign, dst)` | +| Pow | `TensorPrimitives.Pow(bases, exponents, dst)` | + +### Two-span operations (a, b → dst) +| Operation | API | +|-----------|-----| +| Add | `TensorPrimitives.Add(a, b, dst)` | +| Subtract | `TensorPrimitives.Subtract(a, b, dst)` | +| Multiply | `TensorPrimitives.Multiply(a, b, dst)` | +| Divide | `TensorPrimitives.Divide(a, b, dst)` | +| Element-wise Min | `TensorPrimitives.Min(a, b, dst)` | +| Element-wise Max | `TensorPrimitives.Max(a, b, dst)` | + +### Three-span fused operations +| Operation | API | +|-----------|-----| +| (a+b)*c | `TensorPrimitives.AddMultiply(a, b, c, dst)` | +| a*b+c (FMA) | `TensorPrimitives.MultiplyAdd(a, b, c, dst)` | + +## Manual SIMD with Vector128/Vector256/Vector512 + +Use this when TensorPrimitives doesn't have a single API for the operation. This is required for byte-level operations, character class counting, range validation, bitwise bulk ops, cross-type conversions, and custom patterns. + +### Required imports +```csharp +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +``` +Never use `System.Runtime.Intrinsics.X86` or `.Arm` — cross-platform APIs only. + +### Three-tier dispatch pattern +Always include all three tiers. The `IsHardwareAccelerated` check goes in the outer `if`; the length loop is a `while` inside. JIT eliminates dead paths at compile time. **Do NOT put length checks in the `if` condition** — separate the capability check from the loop: +```csharp +ref var src = ref MemoryMarshal.GetReference(span); +uint i = 0; +uint length = (uint)span.Length; + +if (Vector512.IsHardwareAccelerated && Vector512.IsSupported) +{ + uint vec512Count = (uint)Vector512.Count; + while (i + vec512Count <= length) + { + var vec = Vector512.LoadUnsafe(ref src, i); + // ... process vec ... + i += vec512Count; + } +} +if (Vector256.IsHardwareAccelerated && Vector256.IsSupported) +{ + uint vec256Count = (uint)Vector256.Count; + while (i + vec256Count <= length) + { + var vec = Vector256.LoadUnsafe(ref src, i); + // ... process vec ... + i += vec256Count; + } +} +if (Vector128.IsHardwareAccelerated && Vector128.IsSupported) +{ + uint vec128Count = (uint)Vector128.Count; + while (i + vec128Count <= length) + { + var vec = Vector128.LoadUnsafe(ref src, i); + // ... process vec ... + i += vec128Count; + } +} +// Scalar fallback for remaining elements +for (; i < length; i++) +{ + // ... scalar processing ... +} +``` + +### Core SIMD operations +- **Load/Store:** `Vector128.LoadUnsafe(ref src, offset)` / `.StoreUnsafe(ref dst, offset)` +- **Arithmetic:** `+`, `-`, `*`, `/` operators on vector types +- **FMA:** `Vector128.MultiplyAddEstimate(a, b, c)` — fused multiply-add +- **Comparison:** `Vector128.Equals`, `.LessThan`, `.GreaterThan` — returns mask vector +- **Mask ops:** `Vector128.All(mask)`, `.Any(mask)`, `.None(mask)`, `.Count(mask)`, `.CountWhereAllBitsSet(mask)` +- **Horizontal:** `Vector128.Sum(vec)` for reduction; `.Min(a,b)`, `.Max(a,b)` element-wise +- **Broadcast:** `Vector128.Create(scalarValue)` — fill all lanes with one value +- **Bitwise:** `&`, `|`, `^`, `~` operators; `Vector128.ShiftLeft`, `.ShiftRightLogical` +- **Widening:** `Vector128.WidenLower(v)` / `.WidenUpper(v)` for byte→short, short→int +- **Narrowing:** `Vector128.Narrow(lower, upper)` for int→short, short→byte +- **Type convert:** `Vector128.ConvertToSingle(intVec)`, `.ConvertToInt32(floatVec)` +- **Shuffle:** `Vector128.Shuffle(vec, indices)` — lookup table / permutation +- **Conditional:** `Vector128.ConditionalSelect(mask, trueVec, falseVec)` + +### Pattern: Unsigned range check (byte-range validation) +For checking if all bytes are in range [lo, hi]: +```csharp +var vLo = Vector128.Create((byte)lo); +var vRange = Vector128.Create((byte)(hi - lo)); +// (b - lo) > range means out-of-range (unsigned wraparound catches b < lo) +var shifted = Vector128.Subtract(vec, vLo); +var inRange = Vector128.LessThanOrEqual(shifted, vRange); +if (!Vector128.All(inRange.AsByte())) return false; // for validation +// or: count += Vector128.CountWhereAllBitsSet(inRange); // for counting +``` + +### Pattern: Nibble-lookup counting (character classes, popcount, etc.) +For counting bytes matching a sparse set of values (vowels, digits, punctuation, bit counts) — build two 16-byte lookup tables indexed by low/high nibble: +```csharp +var lo_lut = Vector128.Create(/* 16 bytes: bit pattern for low nibble match */); +var hi_lut = Vector128.Create(/* 16 bytes: bit pattern for high nibble match */); +var nibbleMask = Vector128.Create((byte)0x0F); + +var lo_nibble = vec & nibbleMask; +var hi_nibble = Vector128.ShiftRightLogical(vec.AsUInt16(), 4).AsByte() & nibbleMask; +var lo_match = Vector128.Shuffle(lo_lut, lo_nibble); +var hi_match = Vector128.Shuffle(hi_lut, hi_nibble); +var match = lo_match & hi_match; +count += Vector128.CountWhereAllBitsSet(Vector128.Equals(match, Vector128.Zero).IsNot()); +``` +This same technique works for popcount (LUT = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}). +For simpler cases (single byte value, adjacent range), use `Equals` + `Count` or range check instead. + +### Pattern: Cross-type conversion (widening chains) +When the source and destination types differ (e.g., byte→float for dequantization, short→byte for narrowing): +```csharp +// Widen: byte → short → int → float +var bytes = Vector128.LoadUnsafe(ref src, offset); +var (lo16, hi16) = Vector128.Widen(bytes); +var (lo32a, lo32b) = Vector128.Widen(lo16); +var f0 = Vector128.ConvertToSingle(lo32a.AsInt32()); + +// Narrow: int → short → byte (with saturation via Min/Max clamping) +var clamped = Vector128.Min(Vector128.Max(vec, Vector128.Zero), Vector128.Create((short)255)); +var narrowed = Vector128.Narrow(clamped.AsUInt16(), nextVec.AsUInt16()); +``` + +### Trailing elements +- **Idempotent ops** (validation, search): overlap last vector — re-processing is safe +- **Aggregations** (sum, count, min/max): scalar loop for remainder to avoid double-counting +- **Store ops** (transform in-place): use `ConditionalSelect` to merge with last stored vector + +## Key Rules +- Preserve original method signature — drop-in replacement +- Keep scalar code as fallback — never delete it +- Use `Vector128` / `Vector256` / `Vector512` explicitly — never `Vector` +- Never use platform-specific intrinsics (`Avx2`, `Sse42`, `AdvSimd`, `Fma`) +- Testing: use `dotnet run` (NOT `dotnet test`) — xunit.v3 is an in-process runner diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml index 6af7dd20ca..6e200229e0 100644 --- a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml +++ b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml @@ -205,3 +205,75 @@ scenarios: - "Identifies multiple unsealed leaf classes that should be sealed for JIT devirtualization" - "Notes that DefaultOrdinalizer is a base class and should remain unsealed, but its derived classes should be sealed" - "Notes the LocaleRegistry uses correct StringComparer.OrdinalIgnoreCase as a positive finding" + + - name: "Optimize manual min/max with TensorPrimitives" + prompt: "This .NET 10 project has a method that finds the minimum and maximum values in a float array. It's used in a real-time sensor monitoring system to compute value ranges over sliding windows of 10K–100K readings. Please optimize FindMinMax for maximum throughput." + setup: + files: + - path: "RangeCalculator.cs" + source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs" + assertions: + - type: "exit_success" + rubric: + - "Identifies that finding min and max of a float span is already covered by TensorPrimitives.Min and TensorPrimitives.Max" + - "Replaces the scalar loop with TensorPrimitives.Min/Max or equivalent framework API — not manual SIMD" + - "Does not introduce Vector128/Vector256/Vector512 intrinsics for operations already optimized by TensorPrimitives" + timeout: 180 + + - name: "Optimize manual product with TensorPrimitives" + prompt: "This .NET 10 project computes the product of all elements in a float array for probability calculations. Arrays are 100-1000 elements. Please optimize Product for maximum throughput." + setup: + files: + - path: "MathOps.cs" + source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs" + assertions: + - type: "exit_success" + rubric: + - "Identifies that aggregate product is already covered by TensorPrimitives.Product" + - "Replaces the scalar loop with TensorPrimitives.Product — not manual SIMD" + - "Does not introduce Vector128/Vector256/Vector512 intrinsics for an operation already optimized by TensorPrimitives" + timeout: 180 + + - name: "No optimization opportunity — dictionary-based lookup service" + prompt: "This .NET 10 project is a product catalog lookup service that maps product codes to categories using dictionaries. The development team suspects it could benefit from SIMD optimization. Please analyze and optimize for maximum throughput." + setup: + files: + - path: "ProductCatalog.cs" + source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs" + assertions: + - type: "exit_success" + rubric: + - "Correctly identifies that this code has no meaningful SIMD optimization opportunity" + - "Explains why SIMD is not applicable (dictionary lookups, hash-based operations, small string keys, branching logic)" + - "Does not introduce unnecessary SIMD code that adds complexity without benefit" + timeout: 180 + + - name: "Optimize int array conditional increment with SIMD" + prompt: "This .NET 10 project conditionally increments each element of an int array based on a threshold for counter updates. Arrays are 100K+ elements. Please optimize ConditionalIncrement for maximum throughput." + setup: + files: + - path: "ConditionalIncrement.cs" + source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs" + assertions: + - type: "output_contains" + value: "Vector" + - type: "exit_success" + rubric: + - "Introduces Vector128/Vector256/Vector512 with GreaterThan comparison mask and ConditionalSelect or masked Add for conditional increment" + - "Preserves scalar fallback for trailing elements or non-accelerated hardware" + timeout: 180 + + - name: "Optimize byte buffer bit reversal with SIMD" + prompt: "This .NET 10 project reverses the bit order of each byte in a buffer for serial protocol decoding. Buffers are 64KB+. Please optimize ReverseBits for maximum throughput." + setup: + files: + - path: "BitReverser.cs" + source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs" + assertions: + - type: "output_contains" + value: "Vector" + - type: "exit_success" + rubric: + - "Introduces Vector128/Vector256/Vector512 with nibble-based Shuffle lookup tables for parallel bit reversal" + - "Preserves scalar fallback for trailing elements or non-accelerated hardware" + timeout: 180 diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs new file mode 100644 index 0000000000..dffa23804b --- /dev/null +++ b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs @@ -0,0 +1,16 @@ +using System; + +namespace ByteBitReverse; + +public class BitReverser +{ + public static void ReverseInPlace(Span data) + { + for (int i = 0; i < data.Length; i++) + { + byte b = data[i]; + b = (byte)((b * 0x0202020202UL & 0x010884422010UL) % 1023); + data[i] = b; + } + } +} diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs new file mode 100644 index 0000000000..e5717ad83f --- /dev/null +++ b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs @@ -0,0 +1,15 @@ +using System; + +namespace IntConditionalInc; + +public class ConditionalIncrement +{ + public static void IncrementAbove(Span data, int threshold) + { + for (int i = 0; i < data.Length; i++) + { + if (data[i] > threshold) + data[i]++; + } + } +} diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs new file mode 100644 index 0000000000..bd4eb0d26d --- /dev/null +++ b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs @@ -0,0 +1,38 @@ +namespace CatalogSvc; + +public class ProductCatalog +{ + private readonly Dictionary _categories = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _prices = new(StringComparer.OrdinalIgnoreCase); + + public void AddProduct(string code, string category, decimal price) + { + _categories[code] = category; + _prices[code] = price; + } + + public string? GetCategory(string code) + => _categories.TryGetValue(code, out var cat) ? cat : null; + + public decimal GetTotalPrice(IEnumerable codes) + { + decimal total = 0m; + foreach (var code in codes) + { + if (_prices.TryGetValue(code, out var price)) + total += price; + } + return total; + } + + public List GetProductsByCategory(string category) + { + var result = new List(); + foreach (var kvp in _categories) + { + if (kvp.Value.Equals(category, StringComparison.OrdinalIgnoreCase)) + result.Add(kvp.Key); + } + return result; + } +} diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs new file mode 100644 index 0000000000..af4b631146 --- /dev/null +++ b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs @@ -0,0 +1,22 @@ +namespace MinMax; + +public static class RangeCalculator +{ + /// + /// Returns the minimum and maximum values in . + /// + public static (float Min, float Max) FindMinMax(ReadOnlySpan values) + { + if (values.Length == 0) + throw new ArgumentException("Span must not be empty."); + + float min = values[0]; + float max = values[0]; + for (int i = 1; i < values.Length; i++) + { + if (values[i] < min) min = values[i]; + if (values[i] > max) max = values[i]; + } + return (min, max); + } +} diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs new file mode 100644 index 0000000000..7614faefe9 --- /dev/null +++ b/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs @@ -0,0 +1,14 @@ +namespace ProdOp; + +public static class MathOps +{ + /// Computes the product of all elements. + public static float Product(ReadOnlySpan values) + { + if (values.IsEmpty) return 0f; + float product = 1f; + for (int i = 0; i < values.Length; i++) + product *= values[i]; + return product; + } +} From d21dc2932fa7fd436f25f575bbb07fac525e1379 Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 11 Mar 2026 10:41:13 -0700 Subject: [PATCH 02/12] Update tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml index 6e200229e0..162f8c7c32 100644 --- a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml +++ b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml @@ -249,7 +249,7 @@ scenarios: timeout: 180 - name: "Optimize int array conditional increment with SIMD" - prompt: "This .NET 10 project conditionally increments each element of an int array based on a threshold for counter updates. Arrays are 100K+ elements. Please optimize ConditionalIncrement for maximum throughput." + prompt: "This .NET 10 project conditionally increments each element of an int array based on a threshold for counter updates. Arrays are 100K+ elements. Please optimize ConditionalIncrement.IncrementAbove for maximum throughput." setup: files: - path: "ConditionalIncrement.cs" From b9cb1f82756a34b8d9acb4ffb39ec2c29e08752d Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 11 Mar 2026 10:41:33 -0700 Subject: [PATCH 03/12] Update tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml index 162f8c7c32..2ef3cd4ff5 100644 --- a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml +++ b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml @@ -264,7 +264,7 @@ scenarios: timeout: 180 - name: "Optimize byte buffer bit reversal with SIMD" - prompt: "This .NET 10 project reverses the bit order of each byte in a buffer for serial protocol decoding. Buffers are 64KB+. Please optimize ReverseBits for maximum throughput." + prompt: "This .NET 10 project reverses the bit order of each byte in a buffer for serial protocol decoding. Buffers are 64KB+. Please optimize BitReverser.ReverseInPlace for maximum throughput." setup: files: - path: "BitReverser.cs" From 8f50a920ec4ac8cad81faa17ea4cbbf74e02ec8f Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 11 Mar 2026 10:43:47 -0700 Subject: [PATCH 04/12] Update plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../references/simd-vectorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md index 809810c731..b967957fc5 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md @@ -10,7 +10,7 @@ ## TensorPrimitives API Reference -Use for any float/double array operation that has a matching API below. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one** call: +Use for any float/double array operation that has a matching API below. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one or more** `TensorPrimitives` calls as needed (prefer a single call when possible): ### Reductions (span → scalar) | Operation | API | From f436a6857bb87928627a1763139509575b2eb230 Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 11 Mar 2026 10:44:09 -0700 Subject: [PATCH 05/12] Update plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../references/simd-vectorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md index b967957fc5..90a9798667 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md @@ -2,7 +2,7 @@ ## Decision Gate -1. **Check for TensorPrimitives first.** If one or more TensorPrimitives methods cover the operation → use them. If the `.csproj` does NOT already reference `System.Numerics.Tensors`, **add the package**: ``. Then replace the scalar loop with TP calls and stop. See the full API table below. Compose multiple TP calls when needed (e.g., finding both min and max → `TensorPrimitives.Min(span)` + `TensorPrimitives.Max(span)` as two calls). Do NOT write manual Vector128 code for operations TP already handles. +1. **Check for TensorPrimitives first.** If one or more TensorPrimitives methods cover the operation → use them. If the `.csproj` does NOT already reference `System.Numerics.Tensors`, **add the package**, for example: `` (or use the versioning approach already used by your solution). Then replace the scalar loop with TP calls and stop. See the full API table below. Compose multiple TP calls when needed (e.g., finding both min and max → `TensorPrimitives.Min(span)` + `TensorPrimitives.Max(span)` as two calls). Do NOT write manual Vector128 code for operations TP already handles. 2. **Scalar loop over contiguous array/span** of `byte`, `short`, `int`, `long`, `float`, `double`? → Implement with explicit `Vector128` / `Vector256` / `Vector512` intrinsics using the patterns below. From 127f5203258b85bae53d900b27176ebd94352a1b Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 11 Mar 2026 16:52:20 -0700 Subject: [PATCH 06/12] Update plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../references/simd-vectorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md index 90a9798667..ea9ceed9c6 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md @@ -159,7 +159,7 @@ var hi_nibble = Vector128.ShiftRightLogical(vec.AsUInt16(), 4).AsByte() & nibble var lo_match = Vector128.Shuffle(lo_lut, lo_nibble); var hi_match = Vector128.Shuffle(hi_lut, hi_nibble); var match = lo_match & hi_match; -count += Vector128.CountWhereAllBitsSet(Vector128.Equals(match, Vector128.Zero).IsNot()); +count += Vector128.CountWhereAllBitsSet(~Vector128.Equals(match, Vector128.Zero)); ``` This same technique works for popcount (LUT = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}). For simpler cases (single byte value, adjacent range), use `Equals` + `Count` or range check instead. From f7b044e03cc8ea9632b730d963dbaf121a34160b Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Mon, 16 Mar 2026 16:03:55 -0700 Subject: [PATCH 07/12] Update plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../references/simd-vectorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md index ea9ceed9c6..15851500fb 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md @@ -123,7 +123,7 @@ for (; i < length; i++) ### Core SIMD operations - **Load/Store:** `Vector128.LoadUnsafe(ref src, offset)` / `.StoreUnsafe(ref dst, offset)` - **Arithmetic:** `+`, `-`, `*`, `/` operators on vector types -- **FMA:** `Vector128.MultiplyAddEstimate(a, b, c)` — fused multiply-add +- **Multiply-add (approximate):** `Vector128.MultiplyAddEstimate(a, b, c)` — performs a multiply-add with implementation-defined approximation; not guaranteed to be a strict IEEE fused multiply-add. For precise fused semantics, use hardware-specific intrinsics such as `System.Runtime.Intrinsics.X86.Fma.MultiplyAdd` when available. - **Comparison:** `Vector128.Equals`, `.LessThan`, `.GreaterThan` — returns mask vector - **Mask ops:** `Vector128.All(mask)`, `.Any(mask)`, `.None(mask)`, `.Count(mask)`, `.CountWhereAllBitsSet(mask)` - **Horizontal:** `Vector128.Sum(vec)` for reduction; `.Min(a,b)`, `.Max(a,b)` element-wise From d2e4f2ab62377dd3e54fdda705211e32b88b6d14 Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Mon, 16 Mar 2026 16:35:01 -0700 Subject: [PATCH 08/12] Address review feedback on SIMD vectorization reference - Check Span/MemoryExtensions before TensorPrimitives (no extra dependency) - Add all supported types (sbyte, ushort, uint, ulong, nint, nuint, char via ushort) - TensorPrimitives: add constraint and applicable types columns (not just float/double) - Add FusedMultiplyAdd, clarify AddMultiply vs MultiplyAdd distinction - Prefer portable APIs over platform-specific intrinsics (allow when perf justifies) - Fix dispatch pattern to use if/else if to avoid pessimizing small inputs - Remove LINQ references Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/simd-vectorization.md | 113 +++++++++--------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md index 15851500fb..36890a2a6b 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md @@ -2,67 +2,72 @@ ## Decision Gate -1. **Check for TensorPrimitives first.** If one or more TensorPrimitives methods cover the operation → use them. If the `.csproj` does NOT already reference `System.Numerics.Tensors`, **add the package**, for example: `` (or use the versioning approach already used by your solution). Then replace the scalar loop with TP calls and stop. See the full API table below. Compose multiple TP calls when needed (e.g., finding both min and max → `TensorPrimitives.Min(span)` + `TensorPrimitives.Max(span)` as two calls). Do NOT write manual Vector128 code for operations TP already handles. +1. **Check `Span` and `MemoryExtensions` first.** If the operation can be expressed using built-in `Span` methods (e.g., `Contains`, `IndexOf`, `CopyTo`, `SequenceEqual`) or `MemoryExtensions`, use them — no additional dependency is needed and the runtime already vectorizes many of these internally. -2. **Scalar loop over contiguous array/span** of `byte`, `short`, `int`, `long`, `float`, `double`? → Implement with explicit `Vector128` / `Vector256` / `Vector512` intrinsics using the patterns below. +2. **Check for TensorPrimitives next.** If one or more TensorPrimitives methods cover the operation → use them. If the `.csproj` does NOT already reference `System.Numerics.Tensors`, **add the package**, for example: `` (or use the versioning approach already used by your solution). Then replace the scalar loop with TP calls and stop. See the full API table below. Compose multiple TP calls when needed (e.g., finding both min and max → `TensorPrimitives.Min(span)` + `TensorPrimitives.Max(span)` as two calls). Do NOT write manual Vector128 code for operations TP already handles. -3. **No contiguous numeric array processing** (dictionary lookups, tree traversals, linked lists, state machines, string formatting, small collections, enum comparisons, recursive algorithms, decimal arithmetic)? → Report `[NO SIMD OPPORTUNITY]` and write a **full paragraph** explaining WHY, referencing the specific code characteristics that prevent vectorization (e.g., "State machines require sequential branching on enum values — there are no contiguous numeric arrays to process in parallel, and each transition depends on the previous state"). This explanation is graded. +3. **Scalar loop over contiguous array/span** of `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `nint`, `nuint`, `float`, `double` (and `char` via reinterpretation as `ushort`)? → Implement with explicit `Vector128` / `Vector256` / `Vector512` intrinsics using the patterns below. + +4. **No contiguous numeric array processing** (dictionary lookups, tree traversals, linked lists, state machines, string formatting, small collections, enum comparisons, recursive algorithms, decimal arithmetic)? → Report `[NO SIMD OPPORTUNITY]` and write a **full paragraph** explaining WHY, referencing the specific code characteristics that prevent vectorization (e.g., "State machines require sequential branching on enum values — there are no contiguous numeric arrays to process in parallel, and each transition depends on the previous state"). This explanation is graded. ## TensorPrimitives API Reference -Use for any float/double array operation that has a matching API below. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one or more** `TensorPrimitives` calls as needed (prefer a single call when possible): +TensorPrimitives APIs are generic and work for any primitive type that satisfies the method's generic constraints — not just `float`/`double`. For example, `Sum` requires `IAdditionOperators` + `IAdditiveIdentity` and works for all primitive numeric types, while `CosineSimilarity` requires `IRootFunctions` and only works for `float`/`double`. Check the constraint column below to determine which types apply. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one or more** `TensorPrimitives` calls as needed (prefer a single call when possible): ### Reductions (span → scalar) -| Operation | API | -|-----------|-----| -| Sum | `TensorPrimitives.Sum(span)` | -| Sum of squares | `TensorPrimitives.SumOfSquares(span)` | -| Sum of magnitudes (L1 norm) | `TensorPrimitives.SumOfMagnitudes(span)` | -| L2 norm | `TensorPrimitives.Norm(span)` | -| Product of all elements | `TensorPrimitives.Product(span)` | -| Min value | `TensorPrimitives.Min(span)` | -| Max value | `TensorPrimitives.Max(span)` | -| Index of max | `TensorPrimitives.IndexOfMax(span)` | -| Index of min | `TensorPrimitives.IndexOfMin(span)` | -| Dot product | `TensorPrimitives.Dot(a, b)` | -| Cosine similarity | `TensorPrimitives.CosineSimilarity(a, b)` | -| Euclidean distance | `TensorPrimitives.Distance(a, b)` | +| Operation | API | Constraint | Applicable types | +|-----------|-----|------------|-----------------| +| Sum | `TensorPrimitives.Sum(span)` | `IAdditionOperators`, `IAdditiveIdentity` | All primitive numerics | +| Sum of squares | `TensorPrimitives.SumOfSquares(span)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | +| Sum of magnitudes (L1 norm) | `TensorPrimitives.SumOfMagnitudes(span)` | `INumberBase` | All primitive numerics | +| L2 norm | `TensorPrimitives.Norm(span)` | `IRootFunctions` | `float`, `double` | +| Product of all elements | `TensorPrimitives.Product(span)` | `IMultiplyOperators`, `IMultiplicativeIdentity` | All primitive numerics | +| Min value | `TensorPrimitives.Min(span)` | `INumber` | All primitive numerics | +| Max value | `TensorPrimitives.Max(span)` | `INumber` | All primitive numerics | +| Index of max | `TensorPrimitives.IndexOfMax(span)` | `INumber` | All primitive numerics | +| Index of min | `TensorPrimitives.IndexOfMin(span)` | `INumber` | All primitive numerics | +| Dot product | `TensorPrimitives.Dot(a, b)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | +| Cosine similarity | `TensorPrimitives.CosineSimilarity(a, b)` | `IRootFunctions` | `float`, `double` | +| Euclidean distance | `TensorPrimitives.Distance(a, b)` | `IRootFunctions` | `float`, `double` | ### Element-wise transforms (span → span) -| Operation | API | -|-----------|-----| -| Negate | `TensorPrimitives.Negate(src, dst)` | -| Abs | `TensorPrimitives.Abs(src, dst)` | -| Sqrt | `TensorPrimitives.Sqrt(src, dst)` | -| Exp | `TensorPrimitives.Exp(src, dst)` | -| Log | `TensorPrimitives.Log(src, dst)` | -| Log2 | `TensorPrimitives.Log2(src, dst)` | -| Tanh | `TensorPrimitives.Tanh(src, dst)` | -| Sigmoid | `TensorPrimitives.Sigmoid(src, dst)` | -| SoftMax | `TensorPrimitives.SoftMax(src, dst)` | -| Sinh | `TensorPrimitives.Sinh(src, dst)` | -| Cosh | `TensorPrimitives.Cosh(src, dst)` | -| Round | `TensorPrimitives.Round(src, dst)` | -| Floor | `TensorPrimitives.Floor(src, dst)` | -| Ceiling | `TensorPrimitives.Ceiling(src, dst)` | -| CopySign | `TensorPrimitives.CopySign(src, sign, dst)` | -| Pow | `TensorPrimitives.Pow(bases, exponents, dst)` | +| Operation | API | Constraint | Applicable types | +|-----------|-----|------------|-----------------| +| Negate | `TensorPrimitives.Negate(src, dst)` | `IUnaryNegationOperators` | All signed numerics, `float`, `double` | +| Abs | `TensorPrimitives.Abs(src, dst)` | `INumberBase` | All primitive numerics | +| Sqrt | `TensorPrimitives.Sqrt(src, dst)` | `IRootFunctions` | `float`, `double` | +| Exp | `TensorPrimitives.Exp(src, dst)` | `IExponentialFunctions` | `float`, `double` | +| Log | `TensorPrimitives.Log(src, dst)` | `ILogarithmicFunctions` | `float`, `double` | +| Log2 | `TensorPrimitives.Log2(src, dst)` | `ILogarithmicFunctions` | `float`, `double` | +| Tanh | `TensorPrimitives.Tanh(src, dst)` | `IHyperbolicFunctions` | `float`, `double` | +| Sigmoid | `TensorPrimitives.Sigmoid(src, dst)` | `IExponentialFunctions` | `float`, `double` | +| SoftMax | `TensorPrimitives.SoftMax(src, dst)` | `IExponentialFunctions` | `float`, `double` | +| Sinh | `TensorPrimitives.Sinh(src, dst)` | `IHyperbolicFunctions` | `float`, `double` | +| Cosh | `TensorPrimitives.Cosh(src, dst)` | `IHyperbolicFunctions` | `float`, `double` | +| Round | `TensorPrimitives.Round(src, dst)` | `IFloatingPoint` | `float`, `double` | +| Floor | `TensorPrimitives.Floor(src, dst)` | `IFloatingPoint` | `float`, `double` | +| Ceiling | `TensorPrimitives.Ceiling(src, dst)` | `IFloatingPoint` | `float`, `double` | +| CopySign | `TensorPrimitives.CopySign(src, sign, dst)` | `INumber` | All primitive numerics | +| Pow | `TensorPrimitives.Pow(bases, exponents, dst)` | `IPowerFunctions` | `float`, `double` | ### Two-span operations (a, b → dst) -| Operation | API | -|-----------|-----| -| Add | `TensorPrimitives.Add(a, b, dst)` | -| Subtract | `TensorPrimitives.Subtract(a, b, dst)` | -| Multiply | `TensorPrimitives.Multiply(a, b, dst)` | -| Divide | `TensorPrimitives.Divide(a, b, dst)` | -| Element-wise Min | `TensorPrimitives.Min(a, b, dst)` | -| Element-wise Max | `TensorPrimitives.Max(a, b, dst)` | +| Operation | API | Constraint | Applicable types | +|-----------|-----|------------|-----------------| +| Add | `TensorPrimitives.Add(a, b, dst)` | `IAdditionOperators` | All primitive numerics | +| Subtract | `TensorPrimitives.Subtract(a, b, dst)` | `ISubtractionOperators` | All primitive numerics | +| Multiply | `TensorPrimitives.Multiply(a, b, dst)` | `IMultiplyOperators` | All primitive numerics | +| Divide | `TensorPrimitives.Divide(a, b, dst)` | `IDivisionOperators` | All primitive numerics | +| Element-wise Min | `TensorPrimitives.Min(a, b, dst)` | `INumber` | All primitive numerics | +| Element-wise Max | `TensorPrimitives.Max(a, b, dst)` | `INumber` | All primitive numerics | ### Three-span fused operations -| Operation | API | -|-----------|-----| -| (a+b)*c | `TensorPrimitives.AddMultiply(a, b, c, dst)` | -| a*b+c (FMA) | `TensorPrimitives.MultiplyAdd(a, b, c, dst)` | +| Operation | API | Constraint | Applicable types | +|-----------|-----|------------|-----------------| +| (x+y)*z | `TensorPrimitives.AddMultiply(x, y, z, dst)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | +| x*y+z | `TensorPrimitives.MultiplyAdd(x, y, z, dst)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | +| fma(x,y,z) | `TensorPrimitives.FusedMultiplyAdd(x, y, z, dst)` | `IFloatingPointIeee754` | `float`, `double` | + +> `AddMultiply` and `MultiplyAdd` are distinct — they optimize differently depending on whether the dependency chain flows from the addend or the multiplier. `FusedMultiplyAdd` is the IEEE 754 fused form of (x*y)+z with a single rounding step. ## Manual SIMD with Vector128/Vector256/Vector512 @@ -74,10 +79,10 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; ``` -Never use `System.Runtime.Intrinsics.X86` or `.Arm` — cross-platform APIs only. +Prefer cross-platform APIs (`System.Runtime.Intrinsics`). Only use platform-specific intrinsics (`System.Runtime.Intrinsics.X86`, `.Arm`) when there is a significant performance advantage that justifies the increased code complexity of maintaining separate code paths. ### Three-tier dispatch pattern -Always include all three tiers. The `IsHardwareAccelerated` check goes in the outer `if`; the length loop is a `while` inside. JIT eliminates dead paths at compile time. **Do NOT put length checks in the `if` condition** — separate the capability check from the loop: +Always include all three tiers. Use `if`/`else if` so that small inputs hit only one branch before reaching the scalar fallback — a fallthrough pattern (sequential `if`s) pessimizes the scalar case by requiring up to three not-taken branches that may mispredict. The `IsHardwareAccelerated` checks are JIT-time constants, so dead paths are eliminated at compile time: ```csharp ref var src = ref MemoryMarshal.GetReference(span); uint i = 0; @@ -93,7 +98,7 @@ if (Vector512.IsHardwareAccelerated && Vector512.IsSupported) i += vec512Count; } } -if (Vector256.IsHardwareAccelerated && Vector256.IsSupported) +else if (Vector256.IsHardwareAccelerated && Vector256.IsSupported) { uint vec256Count = (uint)Vector256.Count; while (i + vec256Count <= length) @@ -103,7 +108,7 @@ if (Vector256.IsHardwareAccelerated && Vector256.IsSupported) i += vec256Count; } } -if (Vector128.IsHardwareAccelerated && Vector128.IsSupported) +else if (Vector128.IsHardwareAccelerated && Vector128.IsSupported) { uint vec128Count = (uint)Vector128.Count; while (i + vec128Count <= length) @@ -113,7 +118,7 @@ if (Vector128.IsHardwareAccelerated && Vector128.IsSupported) i += vec128Count; } } -// Scalar fallback for remaining elements +// Scalar fallback for remaining elements (and the only loop hit for small inputs) for (; i < length; i++) { // ... scalar processing ... @@ -187,5 +192,5 @@ var narrowed = Vector128.Narrow(clamped.AsUInt16(), nextVec.AsUInt16()); - Preserve original method signature — drop-in replacement - Keep scalar code as fallback — never delete it - Use `Vector128` / `Vector256` / `Vector512` explicitly — never `Vector` -- Never use platform-specific intrinsics (`Avx2`, `Sse42`, `AdvSimd`, `Fma`) +- Prefer portable `Vector128`/`Vector256`/`Vector512` APIs over platform-specific intrinsics (`Avx2`, `Sse42`, `AdvSimd`, `Fma`) unless there is a significant performance advantage - Testing: use `dotnet run` (NOT `dotnet test`) — xunit.v3 is an in-process runner From 093d78f4d5d43c6b6f0092d569424a62cee0d7c4 Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Tue, 17 Mar 2026 09:14:44 -0700 Subject: [PATCH 09/12] addressing more feedback --- .../references/simd-vectorization.md | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md index 36890a2a6b..a8b9fe9566 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md @@ -8,64 +8,64 @@ 3. **Scalar loop over contiguous array/span** of `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `nint`, `nuint`, `float`, `double` (and `char` via reinterpretation as `ushort`)? → Implement with explicit `Vector128` / `Vector256` / `Vector512` intrinsics using the patterns below. -4. **No contiguous numeric array processing** (dictionary lookups, tree traversals, linked lists, state machines, string formatting, small collections, enum comparisons, recursive algorithms, decimal arithmetic)? → Report `[NO SIMD OPPORTUNITY]` and write a **full paragraph** explaining WHY, referencing the specific code characteristics that prevent vectorization (e.g., "State machines require sequential branching on enum values — there are no contiguous numeric arrays to process in parallel, and each transition depends on the previous state"). This explanation is graded. +4. **No contiguous numeric arrays to process** (dictionary lookups, tree traversals, linked lists, state machines, string formatting, small collections, enum comparisons, recursive algorithms, decimal arithmetic)? → Report `[NO SIMD OPPORTUNITY]` and write a **full paragraph** explaining WHY, referencing the specific code characteristics that prevent vectorization (e.g., "State machines require sequential branching on enum values — there are no contiguous numeric arrays to process in parallel, and each transition depends on the previous state"). This explanation is graded. ## TensorPrimitives API Reference -TensorPrimitives APIs are generic and work for any primitive type that satisfies the method's generic constraints — not just `float`/`double`. For example, `Sum` requires `IAdditionOperators` + `IAdditiveIdentity` and works for all primitive numeric types, while `CosineSimilarity` requires `IRootFunctions` and only works for `float`/`double`. Check the constraint column below to determine which types apply. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one or more** `TensorPrimitives` calls as needed (prefer a single call when possible): +TensorPrimitives APIs are generic and work for any primitive type that satisfies the method's generic constraints — not just `float`/`double`. For example, `Sum` requires `IAdditionOperators` + `IAdditiveIdentity` and works for all primitive numeric types, while `CosineSimilarity` requires `IRootFunctions` and only works for `float`/`double`. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one or more** `TensorPrimitives` calls as needed (prefer a single call when possible): ### Reductions (span → scalar) -| Operation | API | Constraint | Applicable types | -|-----------|-----|------------|-----------------| -| Sum | `TensorPrimitives.Sum(span)` | `IAdditionOperators`, `IAdditiveIdentity` | All primitive numerics | -| Sum of squares | `TensorPrimitives.SumOfSquares(span)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | -| Sum of magnitudes (L1 norm) | `TensorPrimitives.SumOfMagnitudes(span)` | `INumberBase` | All primitive numerics | -| L2 norm | `TensorPrimitives.Norm(span)` | `IRootFunctions` | `float`, `double` | -| Product of all elements | `TensorPrimitives.Product(span)` | `IMultiplyOperators`, `IMultiplicativeIdentity` | All primitive numerics | -| Min value | `TensorPrimitives.Min(span)` | `INumber` | All primitive numerics | -| Max value | `TensorPrimitives.Max(span)` | `INumber` | All primitive numerics | -| Index of max | `TensorPrimitives.IndexOfMax(span)` | `INumber` | All primitive numerics | -| Index of min | `TensorPrimitives.IndexOfMin(span)` | `INumber` | All primitive numerics | -| Dot product | `TensorPrimitives.Dot(a, b)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | -| Cosine similarity | `TensorPrimitives.CosineSimilarity(a, b)` | `IRootFunctions` | `float`, `double` | -| Euclidean distance | `TensorPrimitives.Distance(a, b)` | `IRootFunctions` | `float`, `double` | +| Operation | API | +|-----------|-----| +| Sum | `TensorPrimitives.Sum(span)` | +| Sum of squares | `TensorPrimitives.SumOfSquares(span)` | +| Sum of magnitudes (L1 norm) | `TensorPrimitives.SumOfMagnitudes(span)` | +| L2 norm | `TensorPrimitives.Norm(span)` | +| Product of all elements | `TensorPrimitives.Product(span)` | +| Min value | `TensorPrimitives.Min(span)` | +| Max value | `TensorPrimitives.Max(span)` | +| Index of max | `TensorPrimitives.IndexOfMax(span)` | +| Index of min | `TensorPrimitives.IndexOfMin(span)` | +| Dot product | `TensorPrimitives.Dot(a, b)` | +| Cosine similarity | `TensorPrimitives.CosineSimilarity(a, b)` | +| Euclidean distance | `TensorPrimitives.Distance(a, b)` | ### Element-wise transforms (span → span) -| Operation | API | Constraint | Applicable types | -|-----------|-----|------------|-----------------| -| Negate | `TensorPrimitives.Negate(src, dst)` | `IUnaryNegationOperators` | All signed numerics, `float`, `double` | -| Abs | `TensorPrimitives.Abs(src, dst)` | `INumberBase` | All primitive numerics | -| Sqrt | `TensorPrimitives.Sqrt(src, dst)` | `IRootFunctions` | `float`, `double` | -| Exp | `TensorPrimitives.Exp(src, dst)` | `IExponentialFunctions` | `float`, `double` | -| Log | `TensorPrimitives.Log(src, dst)` | `ILogarithmicFunctions` | `float`, `double` | -| Log2 | `TensorPrimitives.Log2(src, dst)` | `ILogarithmicFunctions` | `float`, `double` | -| Tanh | `TensorPrimitives.Tanh(src, dst)` | `IHyperbolicFunctions` | `float`, `double` | -| Sigmoid | `TensorPrimitives.Sigmoid(src, dst)` | `IExponentialFunctions` | `float`, `double` | -| SoftMax | `TensorPrimitives.SoftMax(src, dst)` | `IExponentialFunctions` | `float`, `double` | -| Sinh | `TensorPrimitives.Sinh(src, dst)` | `IHyperbolicFunctions` | `float`, `double` | -| Cosh | `TensorPrimitives.Cosh(src, dst)` | `IHyperbolicFunctions` | `float`, `double` | -| Round | `TensorPrimitives.Round(src, dst)` | `IFloatingPoint` | `float`, `double` | -| Floor | `TensorPrimitives.Floor(src, dst)` | `IFloatingPoint` | `float`, `double` | -| Ceiling | `TensorPrimitives.Ceiling(src, dst)` | `IFloatingPoint` | `float`, `double` | -| CopySign | `TensorPrimitives.CopySign(src, sign, dst)` | `INumber` | All primitive numerics | -| Pow | `TensorPrimitives.Pow(bases, exponents, dst)` | `IPowerFunctions` | `float`, `double` | +| Operation | API | +|-----------|-----| +| Negate | `TensorPrimitives.Negate(src, dst)` | +| Abs | `TensorPrimitives.Abs(src, dst)` | +| Sqrt | `TensorPrimitives.Sqrt(src, dst)` | +| Exp | `TensorPrimitives.Exp(src, dst)` | +| Log | `TensorPrimitives.Log(src, dst)` | +| Log2 | `TensorPrimitives.Log2(src, dst)` | +| Tanh | `TensorPrimitives.Tanh(src, dst)` | +| Sigmoid | `TensorPrimitives.Sigmoid(src, dst)` | +| SoftMax | `TensorPrimitives.SoftMax(src, dst)` | +| Sinh | `TensorPrimitives.Sinh(src, dst)` | +| Cosh | `TensorPrimitives.Cosh(src, dst)` | +| Round | `TensorPrimitives.Round(src, dst)` | +| Floor | `TensorPrimitives.Floor(src, dst)` | +| Ceiling | `TensorPrimitives.Ceiling(src, dst)` | +| CopySign | `TensorPrimitives.CopySign(src, sign, dst)` | +| Pow | `TensorPrimitives.Pow(bases, exponents, dst)` | ### Two-span operations (a, b → dst) -| Operation | API | Constraint | Applicable types | -|-----------|-----|------------|-----------------| -| Add | `TensorPrimitives.Add(a, b, dst)` | `IAdditionOperators` | All primitive numerics | -| Subtract | `TensorPrimitives.Subtract(a, b, dst)` | `ISubtractionOperators` | All primitive numerics | -| Multiply | `TensorPrimitives.Multiply(a, b, dst)` | `IMultiplyOperators` | All primitive numerics | -| Divide | `TensorPrimitives.Divide(a, b, dst)` | `IDivisionOperators` | All primitive numerics | -| Element-wise Min | `TensorPrimitives.Min(a, b, dst)` | `INumber` | All primitive numerics | -| Element-wise Max | `TensorPrimitives.Max(a, b, dst)` | `INumber` | All primitive numerics | +| Operation | API | +|-----------|-----| +| Add | `TensorPrimitives.Add(a, b, dst)` | +| Subtract | `TensorPrimitives.Subtract(a, b, dst)` | +| Multiply | `TensorPrimitives.Multiply(a, b, dst)` | +| Divide | `TensorPrimitives.Divide(a, b, dst)` | +| Element-wise Min | `TensorPrimitives.Min(a, b, dst)` | +| Element-wise Max | `TensorPrimitives.Max(a, b, dst)` | ### Three-span fused operations -| Operation | API | Constraint | Applicable types | -|-----------|-----|------------|-----------------| -| (x+y)*z | `TensorPrimitives.AddMultiply(x, y, z, dst)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | -| x*y+z | `TensorPrimitives.MultiplyAdd(x, y, z, dst)` | `IAdditionOperators`, `IMultiplyOperators` | All primitive numerics | -| fma(x,y,z) | `TensorPrimitives.FusedMultiplyAdd(x, y, z, dst)` | `IFloatingPointIeee754` | `float`, `double` | +| Operation | API | +|-----------|-----| +| (x+y)*z | `TensorPrimitives.AddMultiply(x, y, z, dst)` | +| x*y+z | `TensorPrimitives.MultiplyAdd(x, y, z, dst)` | +| fma(x,y,z) | `TensorPrimitives.FusedMultiplyAdd(x, y, z, dst)` | > `AddMultiply` and `MultiplyAdd` are distinct — they optimize differently depending on whether the dependency chain flows from the addend or the multiplier. `FusedMultiplyAdd` is the IEEE 754 fused form of (x*y)+z with a single rounding step. @@ -128,7 +128,7 @@ for (; i < length; i++) ### Core SIMD operations - **Load/Store:** `Vector128.LoadUnsafe(ref src, offset)` / `.StoreUnsafe(ref dst, offset)` - **Arithmetic:** `+`, `-`, `*`, `/` operators on vector types -- **Multiply-add (approximate):** `Vector128.MultiplyAddEstimate(a, b, c)` — performs a multiply-add with implementation-defined approximation; not guaranteed to be a strict IEEE fused multiply-add. For precise fused semantics, use hardware-specific intrinsics such as `System.Runtime.Intrinsics.X86.Fma.MultiplyAdd` when available. +- **Multiply-add (approximate):** `Vector128.MultiplyAddEstimate(a, b, c)` — performs a multiply-add with implementation-defined approximation; not guaranteed to be a strict IEEE fused multiply-add. For precise fused semantics, use `Vector128.FusedMultiplyAdd(a, b, c)`. - **Comparison:** `Vector128.Equals`, `.LessThan`, `.GreaterThan` — returns mask vector - **Mask ops:** `Vector128.All(mask)`, `.Any(mask)`, `.None(mask)`, `.Count(mask)`, `.CountWhereAllBitsSet(mask)` - **Horizontal:** `Vector128.Sum(vec)` for reduction; `.Min(a,b)`, `.Max(a,b)` element-wise From e520816c3500c98be7d120543c21bffadda5d509 Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 25 Mar 2026 09:25:27 -0700 Subject: [PATCH 10/12] move to the experimental plugin --- .../skills/analyzing-dotnet-performance/SKILL.md | 10 +++------- .../skills/exp-simd-vectorization/SKILL.md} | 10 +++++----- 2 files changed, 8 insertions(+), 12 deletions(-) rename plugins/{dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md => dotnet-experimental/skills/exp-simd-vectorization/SKILL.md} (96%) diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md index ac17cda957..7bf993fce6 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md +++ b/plugins/dotnet-diag/skills/analyzing-dotnet-performance/SKILL.md @@ -2,10 +2,9 @@ name: analyzing-dotnet-performance description: >- Scans .NET code for ~50 performance anti-patterns across async, memory, - strings, collections, LINQ, regex, serialization, I/O with tiered - severity classification, and scalar loops amenable to SIMD vectorization. - Use when analyzing .NET code for optimization opportunities, reviewing - hot paths, or auditing allocation-heavy patterns. + strings, collections, LINQ, regex, serialization, and I/O with tiered + severity classification. Use when analyzing .NET code for optimization + opportunities, reviewing hot paths, or auditing allocation-heavy patterns. --- # .NET Performance Patterns @@ -39,8 +38,6 @@ Scan C#/.NET code for performance anti-patterns and produce prioritized findings Try to load `references/critical-patterns.md` and the topic-specific reference files listed below. These contain detailed detection recipes and grep commands. -When scalar loops over contiguous numeric arrays or spans are detected, also load `references/simd-vectorization.md`. This reference provides TensorPrimitives API coverage and cross-platform Vector128/Vector256/Vector512 intrinsic patterns for optimizing hot-path scalar loops. - **If reference files are not found** (e.g., in a sandboxed environment or when the skill is embedded as instructions only), **skip file loading and proceed directly to Step 3** using the scan recipes listed inline below. Do not spend time searching the filesystem for reference files — if they aren't at the expected relative path, they aren't available. ### Step 2: Detect Code Signals and Select Topic Recipes @@ -54,7 +51,6 @@ Scan the code for signals that indicate which pattern categories to check. If re | `Regex`, `[GeneratedRegex]`, `Regex.Match`, `RegexOptions.Compiled` | Regex patterns | | `Dictionary<`, `List<`, `.ToList()`, `.Where(`, `.Select(`, LINQ methods, `static readonly Dictionary<` | Collections & LINQ | | `JsonSerializer`, `HttpClient`, `Stream`, `FileStream` | I/O & serialization | -| `for` / `foreach` loops over `byte[]`, `int[]`, `float[]`, `double[]`, `Span<`, `ReadOnlySpan<` with scalar arithmetic, comparison, or bitwise ops | SIMD vectorization (see `references/simd-vectorization.md`) | Always check structural patterns (unsealed classes) regardless of signals. diff --git a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md b/plugins/dotnet-experimental/skills/exp-simd-vectorization/SKILL.md similarity index 96% rename from plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md rename to plugins/dotnet-experimental/skills/exp-simd-vectorization/SKILL.md index a8b9fe9566..dbc48b5d1b 100644 --- a/plugins/dotnet-diag/skills/analyzing-dotnet-performance/references/simd-vectorization.md +++ b/plugins/dotnet-experimental/skills/exp-simd-vectorization/SKILL.md @@ -1,17 +1,17 @@ +--- +name: exp-simd-vectorization +description: "Optimizes hot-path scalar loops in .NET 8+ with cross-platform Vector128/Vector256/Vector512 SIMD intrinsics, or replaces manual math loops with single TensorPrimitives API calls. Covers byte-range validation, character counting, bulk bitwise ops, cross-type conversion, fused multi-array computations, and float/double math operations." +--- + # SIMD Vectorization ## Decision Gate - 1. **Check `Span` and `MemoryExtensions` first.** If the operation can be expressed using built-in `Span` methods (e.g., `Contains`, `IndexOf`, `CopyTo`, `SequenceEqual`) or `MemoryExtensions`, use them — no additional dependency is needed and the runtime already vectorizes many of these internally. - 2. **Check for TensorPrimitives next.** If one or more TensorPrimitives methods cover the operation → use them. If the `.csproj` does NOT already reference `System.Numerics.Tensors`, **add the package**, for example: `` (or use the versioning approach already used by your solution). Then replace the scalar loop with TP calls and stop. See the full API table below. Compose multiple TP calls when needed (e.g., finding both min and max → `TensorPrimitives.Min(span)` + `TensorPrimitives.Max(span)` as two calls). Do NOT write manual Vector128 code for operations TP already handles. - 3. **Scalar loop over contiguous array/span** of `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `nint`, `nuint`, `float`, `double` (and `char` via reinterpretation as `ushort`)? → Implement with explicit `Vector128` / `Vector256` / `Vector512` intrinsics using the patterns below. - 4. **No contiguous numeric arrays to process** (dictionary lookups, tree traversals, linked lists, state machines, string formatting, small collections, enum comparisons, recursive algorithms, decimal arithmetic)? → Report `[NO SIMD OPPORTUNITY]` and write a **full paragraph** explaining WHY, referencing the specific code characteristics that prevent vectorization (e.g., "State machines require sequential branching on enum values — there are no contiguous numeric arrays to process in parallel, and each transition depends on the previous state"). This explanation is graded. ## TensorPrimitives API Reference - TensorPrimitives APIs are generic and work for any primitive type that satisfies the method's generic constraints — not just `float`/`double`. For example, `Sum` requires `IAdditionOperators` + `IAdditiveIdentity` and works for all primitive numeric types, while `CosineSimilarity` requires `IRootFunctions` and only works for `float`/`double`. If the project doesn't already reference `System.Numerics.Tensors`, add it to the `.csproj`. Replace the entire manual loop with **one or more** `TensorPrimitives` calls as needed (prefer a single call when possible): ### Reductions (span → scalar) From a13506a2684734c4429fcb48ec346f3a82e7262e Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 25 Mar 2026 09:30:52 -0700 Subject: [PATCH 11/12] move the simd tests to exprimental --- .../analyzing-dotnet-performance/eval.yaml | 71 ------------------ .../exp-simd-vectorization/eval.yaml | 72 +++++++++++++++++++ .../fixtures/simd-bit-reverser.cs | 0 .../fixtures/simd-conditional-increment.cs | 0 .../fixtures/simd-no-opportunity-catalog.cs | 0 .../fixtures/simd-tensor-primitives-minmax.cs | 0 .../simd-tensor-primitives-product.cs | 0 7 files changed, 72 insertions(+), 71 deletions(-) create mode 100644 tests/dotnet-experimental/exp-simd-vectorization/eval.yaml rename tests/{dotnet-diag/analyzing-dotnet-performance => dotnet-experimental/exp-simd-vectorization}/fixtures/simd-bit-reverser.cs (100%) rename tests/{dotnet-diag/analyzing-dotnet-performance => dotnet-experimental/exp-simd-vectorization}/fixtures/simd-conditional-increment.cs (100%) rename tests/{dotnet-diag/analyzing-dotnet-performance => dotnet-experimental/exp-simd-vectorization}/fixtures/simd-no-opportunity-catalog.cs (100%) rename tests/{dotnet-diag/analyzing-dotnet-performance => dotnet-experimental/exp-simd-vectorization}/fixtures/simd-tensor-primitives-minmax.cs (100%) rename tests/{dotnet-diag/analyzing-dotnet-performance => dotnet-experimental/exp-simd-vectorization}/fixtures/simd-tensor-primitives-product.cs (100%) diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml index 2ef3cd4ff5..e949b7d816 100644 --- a/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml +++ b/tests/dotnet-diag/analyzing-dotnet-performance/eval.yaml @@ -206,74 +206,3 @@ scenarios: - "Notes that DefaultOrdinalizer is a base class and should remain unsealed, but its derived classes should be sealed" - "Notes the LocaleRegistry uses correct StringComparer.OrdinalIgnoreCase as a positive finding" - - name: "Optimize manual min/max with TensorPrimitives" - prompt: "This .NET 10 project has a method that finds the minimum and maximum values in a float array. It's used in a real-time sensor monitoring system to compute value ranges over sliding windows of 10K–100K readings. Please optimize FindMinMax for maximum throughput." - setup: - files: - - path: "RangeCalculator.cs" - source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs" - assertions: - - type: "exit_success" - rubric: - - "Identifies that finding min and max of a float span is already covered by TensorPrimitives.Min and TensorPrimitives.Max" - - "Replaces the scalar loop with TensorPrimitives.Min/Max or equivalent framework API — not manual SIMD" - - "Does not introduce Vector128/Vector256/Vector512 intrinsics for operations already optimized by TensorPrimitives" - timeout: 180 - - - name: "Optimize manual product with TensorPrimitives" - prompt: "This .NET 10 project computes the product of all elements in a float array for probability calculations. Arrays are 100-1000 elements. Please optimize Product for maximum throughput." - setup: - files: - - path: "MathOps.cs" - source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs" - assertions: - - type: "exit_success" - rubric: - - "Identifies that aggregate product is already covered by TensorPrimitives.Product" - - "Replaces the scalar loop with TensorPrimitives.Product — not manual SIMD" - - "Does not introduce Vector128/Vector256/Vector512 intrinsics for an operation already optimized by TensorPrimitives" - timeout: 180 - - - name: "No optimization opportunity — dictionary-based lookup service" - prompt: "This .NET 10 project is a product catalog lookup service that maps product codes to categories using dictionaries. The development team suspects it could benefit from SIMD optimization. Please analyze and optimize for maximum throughput." - setup: - files: - - path: "ProductCatalog.cs" - source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs" - assertions: - - type: "exit_success" - rubric: - - "Correctly identifies that this code has no meaningful SIMD optimization opportunity" - - "Explains why SIMD is not applicable (dictionary lookups, hash-based operations, small string keys, branching logic)" - - "Does not introduce unnecessary SIMD code that adds complexity without benefit" - timeout: 180 - - - name: "Optimize int array conditional increment with SIMD" - prompt: "This .NET 10 project conditionally increments each element of an int array based on a threshold for counter updates. Arrays are 100K+ elements. Please optimize ConditionalIncrement.IncrementAbove for maximum throughput." - setup: - files: - - path: "ConditionalIncrement.cs" - source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs" - assertions: - - type: "output_contains" - value: "Vector" - - type: "exit_success" - rubric: - - "Introduces Vector128/Vector256/Vector512 with GreaterThan comparison mask and ConditionalSelect or masked Add for conditional increment" - - "Preserves scalar fallback for trailing elements or non-accelerated hardware" - timeout: 180 - - - name: "Optimize byte buffer bit reversal with SIMD" - prompt: "This .NET 10 project reverses the bit order of each byte in a buffer for serial protocol decoding. Buffers are 64KB+. Please optimize BitReverser.ReverseInPlace for maximum throughput." - setup: - files: - - path: "BitReverser.cs" - source: "../../../../tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs" - assertions: - - type: "output_contains" - value: "Vector" - - type: "exit_success" - rubric: - - "Introduces Vector128/Vector256/Vector512 with nibble-based Shuffle lookup tables for parallel bit reversal" - - "Preserves scalar fallback for trailing elements or non-accelerated hardware" - timeout: 180 diff --git a/tests/dotnet-experimental/exp-simd-vectorization/eval.yaml b/tests/dotnet-experimental/exp-simd-vectorization/eval.yaml new file mode 100644 index 0000000000..c1483765a9 --- /dev/null +++ b/tests/dotnet-experimental/exp-simd-vectorization/eval.yaml @@ -0,0 +1,72 @@ +scenarios: + - name: "Optimize manual min/max with TensorPrimitives" + prompt: "This .NET 10 project has a method that finds the minimum and maximum values in a float array. It's used in a real-time sensor monitoring system to compute value ranges over sliding windows of 10K–100K readings. Please optimize FindMinMax for maximum throughput." + setup: + files: + - path: "RangeCalculator.cs" + source: "fixtures/simd-tensor-primitives-minmax.cs" + assertions: + - type: "exit_success" + rubric: + - "Identifies that finding min and max of a float span is already covered by TensorPrimitives.Min and TensorPrimitives.Max" + - "Replaces the scalar loop with TensorPrimitives.Min/Max or equivalent framework API — not manual SIMD" + - "Does not introduce Vector128/Vector256/Vector512 intrinsics for operations already optimized by TensorPrimitives" + timeout: 180 + + - name: "Optimize manual product with TensorPrimitives" + prompt: "This .NET 10 project computes the product of all elements in a float array for probability calculations. Arrays are 100-1000 elements. Please optimize Product for maximum throughput." + setup: + files: + - path: "MathOps.cs" + source: "fixtures/simd-tensor-primitives-product.cs" + assertions: + - type: "exit_success" + rubric: + - "Identifies that aggregate product is already covered by TensorPrimitives.Product" + - "Replaces the scalar loop with TensorPrimitives.Product — not manual SIMD" + - "Does not introduce Vector128/Vector256/Vector512 intrinsics for an operation already optimized by TensorPrimitives" + timeout: 180 + + - name: "No optimization opportunity — dictionary-based lookup service" + prompt: "This .NET 10 project is a product catalog lookup service that maps product codes to categories using dictionaries. The development team suspects it could benefit from SIMD optimization. Please analyze and optimize for maximum throughput." + setup: + files: + - path: "ProductCatalog.cs" + source: "fixtures/simd-no-opportunity-catalog.cs" + assertions: + - type: "exit_success" + rubric: + - "Correctly identifies that this code has no meaningful SIMD optimization opportunity" + - "Explains why SIMD is not applicable (dictionary lookups, hash-based operations, small string keys, branching logic)" + - "Does not introduce unnecessary SIMD code that adds complexity without benefit" + timeout: 180 + + - name: "Optimize int array conditional increment with SIMD" + prompt: "This .NET 10 project conditionally increments each element of an int array based on a threshold for counter updates. Arrays are 100K+ elements. Please optimize ConditionalIncrement.IncrementAbove for maximum throughput." + setup: + files: + - path: "ConditionalIncrement.cs" + source: "fixtures/simd-conditional-increment.cs" + assertions: + - type: "output_contains" + value: "Vector" + - type: "exit_success" + rubric: + - "Introduces Vector128/Vector256/Vector512 with GreaterThan comparison mask and ConditionalSelect or masked Add for conditional increment" + - "Preserves scalar fallback for trailing elements or non-accelerated hardware" + timeout: 180 + + - name: "Optimize byte buffer bit reversal with SIMD" + prompt: "This .NET 10 project reverses the bit order of each byte in a buffer for serial protocol decoding. Buffers are 64KB+. Please optimize BitReverser.ReverseInPlace for maximum throughput." + setup: + files: + - path: "BitReverser.cs" + source: "fixtures/simd-bit-reverser.cs" + assertions: + - type: "output_contains" + value: "Vector" + - type: "exit_success" + rubric: + - "Introduces Vector128/Vector256/Vector512 with nibble-based Shuffle lookup tables for parallel bit reversal" + - "Preserves scalar fallback for trailing elements or non-accelerated hardware" + timeout: 180 diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs b/tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-bit-reverser.cs similarity index 100% rename from tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-bit-reverser.cs rename to tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-bit-reverser.cs diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs b/tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-conditional-increment.cs similarity index 100% rename from tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-conditional-increment.cs rename to tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-conditional-increment.cs diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs b/tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-no-opportunity-catalog.cs similarity index 100% rename from tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-no-opportunity-catalog.cs rename to tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-no-opportunity-catalog.cs diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs b/tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-tensor-primitives-minmax.cs similarity index 100% rename from tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-minmax.cs rename to tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-tensor-primitives-minmax.cs diff --git a/tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs b/tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-tensor-primitives-product.cs similarity index 100% rename from tests/dotnet-diag/analyzing-dotnet-performance/fixtures/simd-tensor-primitives-product.cs rename to tests/dotnet-experimental/exp-simd-vectorization/fixtures/simd-tensor-primitives-product.cs From 4ec893eae1c36a42f48b2aa8f61138a4e91041c7 Mon Sep 17 00:00:00 2001 From: Jeff Schwartz Date: Wed, 25 Mar 2026 09:32:20 -0700 Subject: [PATCH 12/12] add codeowners --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6f3cded2c0..27bb036953 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -80,6 +80,9 @@ /plugins/dotnet-experimental/skills/exp-test-tagging/ @dotnet/dotnet-testing /tests/dotnet-experimental/exp-test-tagging/ @dotnet/dotnet-testing +/plugins/dotnet-experimental/skills/exp-simd-vectorization/ @jeffschw @artl93 +/tests/dotnet-experimental/exp-simd-vectorization/ @jeffschw @artl93 + # dotnet-maui /plugins/dotnet-maui/ @Redth @jfversluis /tests/dotnet-maui/ @Redth @jfversluis