SearchValues finally shows a measurable edge in .NET 10 when scanning large buffers for sparse delimiters, with SearchValues completing in 3.3 ms versus a 5.6 ms IndexOfAny run on a 32 MB payload.

Why SearchValues appeared

.NET 8 introduced SearchValues to pre-compute a set of characters (or bytes) and let the runtime pick the fastest scanning algorithm for that set. Build the object once, then reuse it whenever you need to locate any of the values in a span. The runtime may employ vectorized instructions, branch-free loops, or other low-level tricks that hand-written alternatives cannot match without a lot of effort.

Benchmarks that matter

The tests that sparked the discussion used a 32 MB buffer containing five delimiter characters. Three approaches were timed on .NET 10:

  • IndexOfAny(char[]) – 10.2 ms
  • Manual foreach loop over the span – 19.4 ms
  • SearchValues – 9.7 ms

SearchValues beat the built-in IndexOfAny by only half a millisecond. The result isn’t a dramatic “five-times faster” claim that circulates on forums; modern .NET already optimizes IndexOfAny for small sets, narrowing the gap.

When delimiters are rare—appearing once every 40 KB instead of every 60 bytes—the picture changes:

  • IndexOfAny(char[]) – 5.6 ms
  • SearchValues – 3.3 ms

Now SearchValues is 1.7 × faster because the engine sprints through long runs of non-matching data with vectorized steps, while IndexOfAny falls back to a less aggressive path.

The hidden cost

SearchValues isn’t free. Constructing the object allocates memory and builds internal lookup tables. If you instantiate it on every method call, the overhead dwarfs any runtime win. A benchmark that called the scanning routine one million times on short lines showed:

  • Static (reused) SearchValues – 25.1 ms total
  • Per-call SearchValues – 70.2 ms total

Creating the object per call makes the whole operation three times slower than not using SearchValues at all. Store the instance in a static readonly field or otherwise reuse it across calls.

When to reach for SearchValues

  • Large inputs, few matches – The vectorized path shines when the scanner can skip over long stretches without hitting a delimiter.
  • Repeated scans with the same set – If the same characters are searched for many times, the one-time setup pays off.

When to stay with IndexOfAny

  • Short strings or many matches – The extra setup cost outweighs the marginal speed gain.
  • Code that already uses IndexOfAny – Modern .NET’s implementation is already highly tuned for small character sets, so swapping in SearchValues may not move the needle.

Common pitfalls

  • Embedding the construction in hot loops – Leads to the three-fold slowdown shown above.
  • Trying to “out-smart” the runtime with manual loops – The manual foreach version was twice as slow as both built-in methods, confirming that the .NET libraries are hard to beat without deep knowledge of SIMD instructions.
  • Assuming universal speedups – The half-millisecond win on dense data proves that SearchValues is not a universal cheat code; its benefits are data-dependent.

Takeaway

SearchValues delivers a clear advantage only when you scan big buffers for infrequent characters and can afford the one-time construction cost. For the majority of everyday string-search scenarios—short inputs, frequent matches, or code that already relies on IndexOfAny—the built-in method remains the simpler, faster choice. Use SearchValues selectively, cache the instance, and you’ll avoid the hidden penalty while harvesting the real performance win.

Source code and full test suite: https://dev.to/ssukhpinder/when-searchvalues-actually-pays-off-310l