One pass instead of forty is not automatically cheaper.

0.20× the time at eight keywords, 1.23× at forty.

8 keywords · 20 000 characters · no match · StringComparison.Ordinal
loop of text.Contains 4.19 µs reads 160 000 characters
ContainsAny(searchValues) 0.85 µs 4.9 times faster
40 keywords · 20 000 characters · no match · StringComparison.Ordinal
loop of text.Contains 21.79 µs reads 800 000 characters
ContainsAny(searchValues) 26.79 µs 23% slower
what actually changed not the pass count — how often the SIMD prefilter stops to verify a candidate. Those forty keywords start with characters that are everywhere in the text. Give the same forty a three-character prefix that never occurs in it and the single pass drops to 843 ns
generated Regex on the same inputs: 38.58 µs at eight keywords, 90.61 µs at forty · bars share one scale · .NET 10.0.5, x64 AVX2 · BenchmarkDotNet 0.15.8, 10 iterations · Intel Core Ultra 9 285HX
gdhami.net