diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs
index ddfa648b887a..150706aa4f2d 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs
@@ -13,8 +13,9 @@ namespace Microsoft.CodeAnalysis.CSharp
internal sealed partial class LocalRewriter
{
///
- /// Benchmark results (see https://github.com/dotnet/roslyn/pull/84961) show that short patterns can be more
- /// efficient when emitted as comparisons. Larger patterns can benefit from switch dispatch.
+ /// Benchmarks in src/Tools/Benchmarks/IsPatternBenchmarks.cs show that short sparse patterns can be more
+ /// efficient as comparisons, while the sparse and dense four-value cases show that larger patterns can benefit
+ /// from general decision-DAG dispatch.
///
private const int MaxTestsForInvertedLinearSequence = 3;
diff --git a/src/Tools/Benchmarks/IsPatternBenchmarks.cs b/src/Tools/Benchmarks/IsPatternBenchmarks.cs
new file mode 100644
index 000000000000..d416defbc88e
--- /dev/null
+++ b/src/Tools/Benchmarks/IsPatternBenchmarks.cs
@@ -0,0 +1,164 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+using BenchmarkDotNet.Attributes;
+
+namespace Benchmarks;
+
+public enum IsPatternInputPosition
+{
+ FirstHit,
+ LastHit,
+ Miss,
+}
+
+public enum IsPatternPathInput
+{
+ Empty,
+ Slash,
+ Backslash,
+ Other,
+}
+
+///
+/// The pattern and comparison forms are expected to have equivalent performance when short patterns use linear
+/// lowering. This benchmark detects regressions in that equivalence.
+///
+///
+[EvaluateOverhead(false)]
+public class IsPatternLeadingSlashBenchmarks
+{
+ private string _path = null!;
+
+ [Params(IsPatternPathInput.Empty, IsPatternPathInput.Slash, IsPatternPathInput.Backslash, IsPatternPathInput.Other)]
+ public IsPatternPathInput Input { get; set; }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _path = Input switch
+ {
+ IsPatternPathInput.Empty => "",
+ IsPatternPathInput.Slash => "/path",
+ IsPatternPathInput.Backslash => "\\path",
+ IsPatternPathInput.Other => "path",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+
+ [Benchmark(Baseline = true)]
+ public bool Comparisons() => HasLeadingSlashWithComparisons(_path);
+
+ [Benchmark]
+ public bool Pattern() => HasLeadingSlashWithPattern(_path);
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static bool HasLeadingSlashWithComparisons(ReadOnlySpan path)
+ {
+ if (path.Length > 0 && (path[0] == '/' || path[0] == '\\'))
+ return true;
+
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static bool HasLeadingSlashWithPattern(ReadOnlySpan path)
+ {
+ if (path.Length > 0 && path[0] is '/' or '\\')
+ return true;
+
+ return false;
+ }
+}
+
+///
+/// The pattern and comparison forms are expected to have equivalent performance when short patterns use linear
+/// lowering. This benchmark detects regressions in that equivalence.
+///
+///
+[EvaluateOverhead(false)]
+public class IsPatternJsonTerminatorBenchmarks : IsPatternByteInputBenchmarks
+{
+ protected override byte FirstCase => (byte)'.';
+ protected override byte LastCase => (byte)'e';
+
+ [Benchmark(Baseline = true)]
+ public int Comparisons()
+ {
+ var value = Value;
+ return value != (byte)'.' && value != (byte)'E' && value != (byte)'e'
+ ? value + 1
+ : value - 1;
+ }
+
+ [Benchmark]
+ public int Pattern()
+ {
+ var value = Value;
+ return value is not ((byte)'.' or (byte)'E' or (byte)'e')
+ ? value + 1
+ : value - 1;
+ }
+}
+
+///
+/// Compares explicit linear tests with general decision-DAG dispatch for four sparse values.
+///
+///
+[EvaluateOverhead(false)]
+public class IsPatternSparseFourValueBenchmarks : IsPatternByteInputBenchmarks
+{
+ protected override byte FirstCase => (byte)'+';
+ protected override byte LastCase => (byte)'e';
+
+ [Benchmark(Baseline = true)]
+ public bool Comparisons() => Guard && (Value == (byte)'+' || Value == (byte)'.' || Value == (byte)'E' || Value == (byte)'e');
+
+ [Benchmark]
+ public bool Pattern() => Guard && Value is (byte)'+' or (byte)'.' or (byte)'E' or (byte)'e';
+}
+
+///
+/// Compares explicit linear tests with general decision-DAG dispatch for four dense values.
+///
+///
+[EvaluateOverhead(false)]
+public class IsPatternDenseFourValueBenchmarks : IsPatternByteInputBenchmarks
+{
+ protected override byte FirstCase => 1;
+ protected override byte LastCase => 4;
+
+ [Benchmark(Baseline = true)]
+ public bool Comparisons() => Guard && (Value == 1 || Value == 2 || Value == 3 || Value == 4);
+
+ [Benchmark]
+ public bool Pattern() => Guard && Value is 1 or 2 or 3 or 4;
+}
+
+public abstract class IsPatternByteInputBenchmarks
+{
+ [Params(IsPatternInputPosition.FirstHit, IsPatternInputPosition.LastHit, IsPatternInputPosition.Miss)]
+ public IsPatternInputPosition Input { get; set; }
+
+ protected bool Guard;
+ protected byte Value;
+
+ protected abstract byte FirstCase { get; }
+ protected abstract byte LastCase { get; }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ Guard = true;
+ Value = Input switch
+ {
+ IsPatternInputPosition.FirstHit => FirstCase,
+ IsPatternInputPosition.LastHit => LastCase,
+ IsPatternInputPosition.Miss => byte.MaxValue,
+ _ => throw new InvalidOperationException(),
+ };
+ }
+}