# Why is Regex slower than using a bunch of String.contains?

**URL:** <https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247>\
**Category:** Using Swift\
**Tags:** performance, regex\
**Created:** [July 19, 2023, 5:53am UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247 "2023-07-19T05:53:27Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [July 19, 2023, 5:53am UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/1 "2023-07-19T05:53:27Z")

</div>

As part of [Use RegexBuilder als grep replacement?](https://forums.swift.org/t/use-regexbuilder-als-grep-replacement/65782) some folks before me figured out that `Regex` seems a little slow. I tested this a bit myself and confirmed that not only is `Regex` alone slower than "pre-screening" candidate lines with `String.contains()`, but it's _massively_ slower. As in, an order of magnitude slower.

The [Async iteration over lines of a file is surprising slow](https://forums.swift.org/t/async-iteration-over-lines-of-a-file-is-surprising-slow/66244) topic has the base example code for context, but the essential parts are:

```swift
let botListRegEx = Regex {
    ChoiceOf {
        "www.apple.com/go/applebot"
        "www.bing.com/bingbot.htm"
        "www.googlebot.com/bot.html"
        "Xing Bot"
    }
}

let ipAtStartRegEx = Regex {
    Anchor.startOfLine

    Capture {
        Repeat(1...3) { .digit }
        Repeat(3...3) {
            "."
            Repeat(1...3) { .digit }
        }
    }
}.asciiOnlyDigits()

```

…and their application:

```swift
let matchedLines = sequence(state: 0) { _ in readLine() }
    .filter { $0.contains("26/Apr") }
    .filter { $0.contains("\"GET ") }
    .filter { !$0.contains(botListRegEx) }
    .compactMap { $0.firstMatch(of: ipAtStartRegEx)?.output.0 }

```

In principle this can be done using a single regex, and one might think that ideally it _should_ be for simplicity. Yet if you do that, like this:

```swift
let combinedRegex = Regex {
    ipAtStartRegEx

    ZeroOrMore(.whitespace)

    "26/Apr"

    ZeroOrMore(.anyNonNewline)

    "\"GET "

    NegativeLookahead {
        botListRegEx
    }
}

let matchedLines = sequence(state: 0) { _ in readLine() }
    .compactMap { $0.firstMatch(of: combinedRegex)?.output.0 }

```

…then the performance sucks. In a ~420 MB test case (test case synthesier included in [the base code](https://forums.swift.org/t/async-iteration-over-lines-of-a-file-is-surprising-slow/66244)) the `contains`-using version takes about seven seconds on my M2 MacBook Air but the unified regex approach takes 81 seconds!

Plus, the `contains` version can be further optimised to reduce Objective-C bridging overheads, like so:

```swift
let targetDate = "26/Apr" as CFString
let GET = "\"GET " as CFString

let matchedLines = sequence(state: 0) { _ in readLine() }
    .filter {
        let str = $0 as CFString
        return (kCFNotFound != CFStringFind(str, targetDate, []).location
                && kCFNotFound != CFStringFind(str, GET, []).location)
    }
    .filter { !$0.contains(botListRegEx) }
    .compactMap { $0.firstMatch(of: ipAtStartRegEx)?.output.0 }

```

…and that shaves it down further to five seconds. No such optimisation is possible when using `Regex`, as far as I can tell (`Regex` under the covers uses the same CoreFoundation functions as `contains`, among others, and seems to suffer a lot from Objective-C bridging overheads).

So _sixteen_ times faster than `Regex` alone. Is that… expected? Am I misusing `Regex`? Are there optimisation that can be made to improve its performance?

It's particularly surprising since in the 'decomposed' case each of those `contains` / `CFStringFind` / `firstMatch` calls is ignorant of the other, re-scanning prefixes of the line that cannot possibly match. The combined `Regex` has the advantage of only having to search _suffixes_ of the line for a possible match for each component (after the first).

---

<div class="post-metadata">

**Author:** ![hassila](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hassila/32/22647_2.png) [@hassila](https://forums.swift.org/u/hassila)\
**Post date:** [July 19, 2023, 8:10am UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/2 "2023-07-19T08:10:55Z")

</div>

I think this ties into my previous comment on performance of these new super nice abstractions:

> [@SE-0395: Observability](https://forums.swift.org/t/se-0395-observability/64342/113):
>
> I understand that the review period is up (and overall thumbs up to the proposal and it really make sense as a first class thing to support in the standard library rather than a platform-constrained one), but I have a general question applicable to both this proposal but also to the pitch/proposal templates in general; There is little discussion on performance related concerns (same questions came up with Regex was in progress, Predicates and surely many more) - it's always hard to judge what s…

The swift-collections package and standard library are careful to define time complexity and also often provide benchmarks - as suggested in the above comment, maybe it'd be beneficial to surface some of the performance expectations and design envelope as part of the evolution process.

Maybe should be broken out to a separate discussion though.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [July 19, 2023, 3:59pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/3 "2023-07-19T15:59:56Z")

</div>

Every time I've asked about such performance goals has been met negatively. In general, we've been told implementation concerns beyond initial viability aren't relevant to the evolution process. So I don't anticipate much progress here.

---

<div class="post-metadata">

**Author:** ![Karl](https://avatars.discourse-cdn.com/v4/letter/k/34f0e0/32.png) [@Karl](https://forums.swift.org/u/Karl)\
**Post date:** [July 19, 2023, 4:40pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/4 "2023-07-19T16:40:38Z")

</div>

A lot of this stuff just hasn't been optimised yet.

For example, [`String.contains(String)`](https://github.com/apple/swift-experimental-string-processing/blob/d61ba4cd77fd97954ac05d3619e222eca429181c/Sources/_StringProcessing/Algorithms/Algorithms/Contains.swift#L55) is implemented using [`String.firstMatch(of: some RegexComponent)`](https://github.com/apple/swift-experimental-string-processing/blob/d61ba4cd77fd97954ac05d3619e222eca429181c/Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift#L68). This turns the searched-for String in to a `Regex`, containing a `Program`, which contains a `DSLTree`, etc. The engine which executes this regex also has a bunch of heap allocations to represent its matching state, etc -- it doesn't know the regex you gave it is a simple string; it could be any regex.

For a general Unicode-Unicode match, that kind of complexity might be necessary.

But when both Strings are ASCII (as in your example), there is an [obvious fast path](https://opensource.apple.com/source/Libc/Libc-825.25/string/FreeBSD/memmem.c.auto.html) we could use. We could do it; it just hasn't been done yet.

I'm sure PRs are welcome.

---

<div class="post-metadata">

**Author:** ![scanon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scanon/32/2445_2.png) [@scanon](https://forums.swift.org/u/scanon)\
**Post date:** [July 19, 2023, 4:45pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/5 "2023-07-19T16:45:25Z")

</div>

> [@Jon\_Shier](#):
>
> Every time I've asked about such performance goals has been met negatively. In general, we've been told implementation concerns beyond initial viability aren't relevant to the evolution process. So I don't anticipate much progress here.

For the purposes of Evolution, we care that an API _can be optimized_. We don't want to introduce API that cannot ever be made fast. However, it's perfectly OK to introduce new API and then chip away at optimizing the implementation over following language releases.

---

<div class="post-metadata">

**Author:** ![Jumhyn](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jumhyn/32/3891_2.png) [@Jumhyn](https://forums.swift.org/u/Jumhyn)\
**Post date:** [July 19, 2023, 4:47pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/6 "2023-07-19T16:47:12Z")

</div>

Moreover, this means that specific implementation questions are broadly off-topic for the #Evolution category but are perfectly germane here in #Using Swift or #Compiler.

---

<div class="post-metadata">

**Author:** ![scanon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scanon/32/2445_2.png) [@scanon](https://forums.swift.org/u/scanon)\
**Post date:** [July 19, 2023, 4:52pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/7 "2023-07-19T16:52:46Z")

</div>

> [@Karl](#):
>
> But when both Strings are ASCII (as in your example), there is an [obvious fast path](https://opensource.apple.com/source/Libc/Libc-825.25/string/FreeBSD/memmem.c.auto.html) we could use. We could do it; it just hasn't been done yet.

As a side note: `memmem`, in general, is not actually fast. On most platforms, it binds a naive O(n²) worst-case search (suitable for small searches). With a needle that is known-ASCII (and with more work, even when it isn't), there are a variety of faster search options available that I would hope we would use eventually, at least when the haystack is large enough.

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [July 19, 2023, 9:05pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/8 "2023-07-19T21:05:20Z")

</div>

> [@Karl](#):
>
> I'm sure PRs are welcome.

And I'm considering diving into that. I appreciate the "put your time where your complaints are" contract that comes with open-source and community-driven software, and I _love_ that Swift is largely open, from language to libraries. I just don't know if or when I'll actually have the time (or how well I'll do learning all the relevant libraries' code).

That said, `String.contains(String)` using `Regex` under the hood seems… 😳

I don't know if that means it should be super easy to optimise `String` or if it's an omen that the `String` implementation is terrifying.

---

<div class="post-metadata">

**Author:** ![hassila](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hassila/32/22647_2.png) [@hassila](https://forums.swift.org/u/hassila)\
**Post date:** [July 19, 2023, 9:06pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/9 "2023-07-19T21:06:27Z")

</div>

I think that is fine, but what I’m missing is the discussion on expected “final” performance characteristics after such work has been done - I’d definitely find that useful. In my experience such discourse often can expose thoughts on the expected implementation and expected usage - which may help provide better feedback.

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [July 19, 2023, 9:08pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/10 "2023-07-19T21:08:05Z")

</div>

> [@Karl](#):
>
> For example, [`String.contains(String)`](https://github.com/apple/swift-experimental-string-processing/blob/d61ba4cd77fd97954ac05d3619e222eca429181c/Sources/_StringProcessing/Algorithms/Algorithms/Contains.swift#L55) is implemented using [`String.firstMatch(of: some RegexComponent)`](https://github.com/apple/swift-experimental-string-processing/blob/d61ba4cd77fd97954ac05d3619e222eca429181c/Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift#L68).

Is this true? That seems counter to the results I'm seeing here, where `String.contains(String)` is much faster than explicitly using `Regex`. In the time profiles I didn't notice any regex stuff under `String.contains(String)`, only Objective-C bridging gunk and CoreFoundation string functions.

Forgive the noob question, but can you point me at the `String.contains(String)` implementation (in GitHub or wherever)? Oddly I can't seem to find it (I'm totally unfamiliar with the layout of Swift's open source projects).

---

<div class="post-metadata">

**Author:** ![hassila](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hassila/32/22647_2.png) [@hassila](https://forums.swift.org/u/hassila)\
**Post date:** [July 19, 2023, 9:10pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/11 "2023-07-19T21:10:00Z")

</div>

Ok, but doesn’t that conflict a bit with (the reasonable stance):

> [@scanon](#):
>
> For the purposes of Evolution, we care that an API _can be optimized_. We don't want to introduce API that cannot ever be made fast.

Doesn’t it make it much harder to judge the merits of an api in terms of being able to be optimized if we basically avoid discussing the “how”?

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [July 19, 2023, 9:12pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/12 "2023-07-19T21:12:45Z")

</div>

> [@scanon](#):
>
> For the purposes of Evolution, we care that an API _can be optimized_. We don't want to introduce API that cannot ever be made fast. However, it's perfectly OK to introduce new API and then chip away at optimizing the implementation over following language releases.

Which seems sensible. However, how do you gauge your confidence in that (that a given implementation _can_ be made fast) and what's the required confidence threshold?

All too often we think "oh, this architecture will be fine" and then get a rude awakening when we actually try to optimise it.

---

<div class="post-metadata">

**Author:** ![scanon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scanon/32/2445_2.png) [@scanon](https://forums.swift.org/u/scanon)\
**Post date:** [July 19, 2023, 9:16pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/13 "2023-07-19T21:16:30Z")

</div>

> [@wadetregaskis](#):
>
> Which seems sensible. However, how do you gauge your confidence in that (that a given implementation _can_ be made fast) and what's the required confidence threshold?
> 
> All too often we think "oh, this architecture will be fine" and then get a rude awakening when we actually try to optimise it.

We do have a bunch of people who have quite a lot of experience with these things, so our collective intuition is pretty solid. We're not perfect, but it hasn't led us badly astray yet.

Also, in the case of Regex, we have very deliberately kept the initial ABI surface of the regex engine small by _not_ exposing most of its internal workings. This costs some performance for simple cases in the short term, but also allows us much more freedom in what choices we can make about optimizing long-term, because we're mostly not constrained by ABI compatibility, and is a very deliberate implementation choice.

---

<div class="post-metadata">

**Author:** ![David\_Smith](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/david_smith/32/37508_2.png) [@David\_Smith](https://forums.swift.org/u/David_Smith)\
**Post date:** [July 19, 2023, 9:29pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/14 "2023-07-19T21:29:39Z")

</div>

I'm putting together a project to try this out myself, but one thing that I immediately notice: top level code/global variables can have some overhead in Swift, moving everything into a little struct might actually improve perf.

---

<div class="post-metadata">

**Author:** ![Jumhyn](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jumhyn/32/3891_2.png) [@Jumhyn](https://forums.swift.org/u/Jumhyn)\
**Post date:** [July 19, 2023, 9:29pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/15 "2023-07-19T21:29:54Z")

</div>

I didn’t mean to suggest that any whiff of ‘implementation’ is off-topic in evolution discussions (as Steve notes, understanding whether a proposed API admits a hypothetical efficient implementation is totally in-bounds), I only meant that questions _about_ the current implementation are (likely) off topic for evolution. They’re probably either a PR or a question/suggestion for another category.

---

<div class="post-metadata">

**Author:** ![David\_Smith](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/david_smith/32/37508_2.png) [@David\_Smith](https://forums.swift.org/u/David_Smith)\
**Post date:** [July 19, 2023, 9:50pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/16 "2023-07-19T21:50:39Z")

</div>

Ok, definitely found some things that look suspicious and weren't there last time I wrote a benchmark like this. I'm gonna file a few bug reports based on this, thanks for the test case!

---

<div class="post-metadata">

**Author:** ![Karl](https://avatars.discourse-cdn.com/v4/letter/k/34f0e0/32.png) [@Karl](https://forums.swift.org/u/Karl)\
**Post date:** [July 19, 2023, 9:54pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/17 "2023-07-19T21:54:01Z")

</div>

> [@wadetregaskis](#):
>
> Is this true? That seems counter to the results I'm seeing here, where `String.contains(String)` is much faster than explicitly using `Regex`. In the time profiles I didn't notice any regex stuff under `String.contains(String)`, only Objective-C bridging gunk and CoreFoundation string functions.

When I check in [Godbolt](https://swift.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:swift,selection:(endColumn:24,endLineNumber:2,positionColumn:24,positionLineNumber:2,selectionStartColumn:24,selectionStartLineNumber:2,startColumn:24,startLineNumber:2),source:'func+search(_+str:+String,+for+needle:+String)+-%3E+Bool+%7B%0A++++str.contains(needle)%0A%7D'),l:'5',n:'0',o:'Swift+source+%231',t:'0')),k:50,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:swift58,deviceViewOpen:'1',filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:swift,libs:!(),options:'-O',overrides:!(),selection:(endColumn:57,endLineNumber:20,positionColumn:57,positionLineNumber:20,selectionStartColumn:19,selectionStartLineNumber:20,startColumn:19,startLineNumber:20),source:1),l:'5',n:'0',o:'+x86-64+swiftc+5.8+(Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4), `String.contains(String)` compiles down to a call to `sSy17_StringProcessingE8containsySbSSF`.

Running that through `swift-demangle`:

```swift
$sSy17_StringProcessingE8containsySbSSF ---> (extension in _StringProcessing):Swift.StringProtocol.contains(Swift.String) -> Swift.Bool

```

That matches the signature I find [here](https://github.com/apple/swift-experimental-string-processing/blob/d61ba4cd77fd97954ac05d3619e222eca429181c/Sources/_StringProcessing/Algorithms/Algorithms/Contains.swift#L55). The implementation simply returns weather `firstRange(of: ...) != nil`.

Since `String` conforms to `RegexComponent`, I expect that the most specific overload of `firstRange` will be [this one](https://github.com/apple/swift-experimental-string-processing/blob/d61ba4cd77fd97954ac05d3619e222eca429181c/Sources/_StringProcessing/Algorithms/Algorithms/FirstRange.swift#L68). It constructs a `Regex` using [the `.regex` property](https://github.com/apple/swift-experimental-string-processing/blob/d61ba4cd77fd97954ac05d3619e222eca429181c/Sources/RegexBuilder/DSL.swift#L38), which might be more efficient than whatever Regex initialiser you were comparing with (it uses [`.init(verbatim:)`](https://developer.apple.com/documentation/swift/regex/init(verbatim:)); is that what you were using?), but since it is ultimately a `Regex<Substring>` like any other, it contains a DSL tree that will be executed by the matching engine.

None of these algorithms are inlinable, which might also be responsible for performance differences against in-module code, even if you call the same functions with the same inputs.

I'm not sure why you're seeing any bridging stuff, though. That's certainly strange.

> [@scanon](#):
>
> As a side note: `memmem`, in general, is not actually fast.

Ah, of course - that's what I get for hoping things could be easy for once! 🥲 Oh, well - this seems like a useful operation for the standard library to expose on the `UnsafeBuffer` (and eventually, safe `BufferView`) types.

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [July 19, 2023, 10:04pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/18 "2023-07-19T22:04:09Z")

</div>

> [@David\_Smith](#):
>
> top level code/global variables can have some overhead in Swift, moving everything into a little struct might actually improve perf.

I didn't try a struct specifically, but putting everything inside a function made no difference.

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [July 19, 2023, 10:15pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/19 "2023-07-19T22:15:32Z")

</div>

> [@Karl](#):
>
> When I check in [Godbolt](https://swift.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:swift,selection:(endColumn:24,endLineNumber:2,positionColumn:24,positionLineNumber:2,selectionStartColumn:24,selectionStartLineNumber:2,startColumn:24,startLineNumber:2),source:'func+search(_+str:+String,+for+needle:+String)+-%3E+Bool+%7B%0A++++str.contains(needle)%0A%7D'),l:'5',n:'0',o:'Swift+source+%231',t:'0')),k:50,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:swift58,deviceViewOpen:'1',filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:swift,libs:!(),options:'-O',overrides:!(),selection:(endColumn:57,endLineNumber:20,positionColumn:57,positionLineNumber:20,selectionStartColumn:19,selectionStartLineNumber:20,startColumn:19,startLineNumber:20),source:1),l:'5',n:'0',o:'+x86-64+swiftc+5.8+(Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4), `String.contains(String)` compiles down to a call to `sSy17_StringProcessingE8containsySbSSF`.

That could be misleading. Godbolt compiles on Linux, not macOS, and only for x86-64, not AArch64. I woudn't be at all surprised if the implementations are quite different.

To be clear, I'm only looking at this on macOS 13.4.1, with Swift 5.9 (beta).

Instruments says that `String.contains(String)` actually uses the generic implementation from `StringProtocol` which calls `-[NSString rangeOfString:options:range:locale:]` and that calls `CFStringFindWithOptionsAndLocale`. There's a whole bunch of other gunk in there too, mainly around bridging and ARC, but those are a relatively small amount of the execution time.

I wondered if somehow I was dealing with _NSStrings_ instead of native Swift strings, but I can see from the time profile that `readLine` (my source of these strings) is using `String._fromUTF8Repairing(_:)`, and I can follow that all the way down to `swift_slowAlloc`. So I'm pretty sure these are native Swift strings. Which leads to the question of why they're immediately bridging to `NSString` just to do a 'contains' check…

All this is a bit off topic since the question is why is `Regex` so slow, not `String.contains(String)`. Although both questions are valid.

---

<div class="post-metadata">

**Author:** ![scanon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/scanon/32/2445_2.png) [@scanon](https://forums.swift.org/u/scanon)\
**Post date:** [July 19, 2023, 10:20pm UTC](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247/20 "2023-07-19T22:20:18Z")

</div>

> [@wadetregaskis](#):
>
> Instruments says that `String.contains(String)` actually uses the generic implementation from `StringProtocol` which calls `-[NSString rangeOfString:options:range:locale:]` and that calls `CFStringFindWithOptionsAndLocale`. There's a whole bunch of other gunk in there too, mainly around bridging and ARC, but those are a relatively small amount of the execution time.

Without spending any time on this at all, I believe you're calling `String.contains(_:String)` from Foundation, rather than one of the `_StringProcessing` methods, which would explain all the bridging and NSWhatnot that you're seeing.

[Next page](https://forums.swift.org/t/why-is-regex-slower-than-using-a-bunch-of-string-contains/66247.md?page=2)
