Skip to content

[stdlib] Mark contains methods on Range/ClosedRange transparent #81458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 6, 2025

Conversation

lorentey
Copy link
Member

These are really popular shorthands for implementing bounds checks, but currently they aren’t even marked @inline(__always), so in larger functions they tend to remain outlined, defeating optimizations that would otherwise happen.

The corresponding variants on the partial range types are @_transparent; that helps unoptimized performance, so let’s apply the same attribute here.

rdar://151177326

@lorentey lorentey requested a review from a team as a code owner May 12, 2025 22:15
@lorentey
Copy link
Member Author

@swift-ci test

@lorentey
Copy link
Member Author

I'm not expecting any benchmark impact, but.

@swift-ci benchmark

@benrimmington
Copy link
Contributor

Should you also update:

  • contains(_:) in the PartialRangeFrom extension?

  • ~=(_:_:) in the RangeExpression extension?

@glessard
Copy link
Contributor

@swift-ci please benchmark

@glessard
Copy link
Contributor

Performance (x86_64): -O

Regression OLD NEW DELTA RATIO
ArrayAppendGenericStructs 1390.0 1765.0 +27.0% 0.79x (?)

Code size: -O

Performance (x86_64): -Osize

Regression OLD NEW DELTA RATIO
StringDistance.utf16.ascii 115.15 127.056 +10.3% 0.91x (?)

Code size: -Osize

Performance (x86_64): -Onone

Improvement OLD NEW DELTA RATIO
ClosedRangeContainsRange 15858.0 10739.0 -32.3% 1.48x
RangeContainsClosedRange 12057.0 10318.0 -14.4% 1.17x (?)
ArrayAppendReserved 2786.0 2530.0 -9.2% 1.10x (?)
Data.init.Sequence.809B.Count.RE.I 19137.0 17850.0 -6.7% 1.07x (?)

Code size: -swiftlibs

@lorentey
Copy link
Member Author

Should you also update:

  • contains(_:) in the PartialRangeFrom extension?

This is not actually relevant to bounds checking, but sure, why not have them all transparent!

  • ~=(_:_:) in the RangeExpression extension?

Oh yes indeed!

@lorentey lorentey force-pushed the range-contains-perf branch from fb505b6 to ac58408 Compare May 17, 2025 01:42
@lorentey
Copy link
Member Author

@swift-ci smoke test

@lorentey lorentey force-pushed the range-contains-perf branch from ac58408 to bf01d07 Compare May 19, 2025 18:42
@lorentey
Copy link
Member Author

@swift-ci smoke test

@lorentey
Copy link
Member Author

Touching ~= was a mistake. I will take a look, but this is not worth all this, so I may end up reverting that part.

13:50:27 
 ******************** TEST 'Swift(linux-x86_64) :: Constraints/rdar105781521.swift' FAILED ********************
[...]
13:50:27  /home/build-user/swift/test/Constraints/rdar105781521.swift:13:17: error: unexpected note produced: overloads for '~=' exist with these partially matching parameter lists: (Substring, String)
13:50:27      case .first(true):
13:50:27                  ^
13:50:27  FAIL: Swift(linux-x86_64) :: Constraints/rdar105782480.swift (1298 of 19342)
13:50:27  /home/build-user/swift/test/Constraints/rdar105782480.swift:14:10: error: expected error not produced
13:50:27        // expected-error@-1 {{value of type 'MyEnum' has no member 'invalid'}}
13:50:27  ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13:50:27  /home/build-user/swift/test/Constraints/rdar105782480.swift:15:30: error: incorrect message found
13:50:27        // expected-error@-2 {{'let' binding pattern cannot appear in an expression}}
13:50:27                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13:50:27                               type 'Range<MyEnum>' has no member 'second'
13:50:27  
13:50:27  --
13:50:27  
13:50:27  FAIL: Swift(linux-x86_64) :: Constraints/patterns.swift (1332 of 19342)
13:50:27  ******************** TEST 'Swift(linux-x86_64) :: Constraints/patterns.swift' FAILED ********************
13:50:27  /home/build-user/swift/test/Constraints/patterns.swift:125:7: error: unexpected error produced: reference to member 'HairForceOne' cannot be resolved without a contextual type
13:50:27  case .HairForceOne: // expected-error{{type 'any HairType' has no member 'HairForceOne'}}
13:50:27        ^
13:50:27  /home/build-user/swift/test/Constraints/patterns.swift:125:40: error: incorrect message found
13:50:27  case .HairForceOne: // expected-error{{type 'any HairType' has no member 'HairForceOne'}}
13:50:27                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13:50:27                                         binary operator '~=' cannot be applied to two 'any HairType' operands
13:50:27  FAIL: Swift(linux-x86_64) :: Constraints/result_builder_invalid_stmts.swift (1381 of 19342)
13:50:27  ******************** TEST 'Swift(linux-x86_64) :: Constraints/result_builder_invalid_stmts.swift' FAILED ********************
13:50:27  /home/build-user/swift/test/Constraints/result_builder_invalid_stmts.swift:17:10: error: unexpected note produced: overloads for '~=' exist with these partially matching parameter lists: (Substring, String)
13:50:27      case 1: // expected-error {{expression pattern of type 'Int' cannot match values of type 'String'}}
13:50:27           ^
13:50:27  /home/build-user/swift/test/Constraints/result_builder_invalid_stmts.swift:30:10: error: unexpected note produced: overloads for '~=' exist with these partially matching parameter lists: (Substring, String)
13:50:27      case 1: // expected-error {{expression pattern of type 'Int' cannot match values of type 'String'}}
13:50:27           ^
13:50:27  /home/build-user/swift/test/Constraints/result_builder_invalid_stmts.swift:44:10: error: unexpected note produced: overloads for '~=' exist with these partially matching parameter lists: (Substring, String)
13:50:27      case 1: // expected-error {{expression pattern of type 'Int' cannot match values of type 'String'}}
13:50:27           ^
13:50:27  /home/build-user/swift/test/Constraints/result_builder_invalid_stmts.swift:56:10: error: unexpected note produced: overloads for '~=' exist with these partially matching parameter lists: (Substring, String)
13:50:27      case 1: // expected-error {{expression pattern of type 'Int' cannot match values of type 'String'}}
13:50:27           ^
[etc]

@lorentey
Copy link
Member Author

@swift-ci smoke test

@lorentey lorentey force-pushed the range-contains-perf branch from 415a190 to e9aaac8 Compare May 21, 2025 05:50
@lorentey
Copy link
Member Author

@swift-ci smoke test

@lorentey
Copy link
Member Author

This looks like a fresh regression on main (unrelated).

23:56:39 
 ******************** TEST 'Swift(macosx-x86_64) :: SILOptimizer/performance-annotations.swift' FAILED ********************
23:56:39  in function specialized _ArrayBuffer._native.getter
23:56:39  /Users/ec2-user/jenkins/workspace/swift-PR-macos-smoke-test/branch-main/swift/test/SILOptimizer/performance-annotations.swift:38:11: error: unexpected error produced: this code performs reference counting operations which can cause locking
23:56:39    return a[i].x
23:56:39            ^
23:56:39  /Users/ec2-user/jenkins/workspace/swift-PR-macos-smoke-test/branch-main/swift/test/SILOptimizer/performance-annotations.swift:43:10: error: unexpected note produced: called from here
23:56:39    return noRTCallsForArrayGet(a, 0)
23:56:39           ^
23:56:39  
23:56:39  --
23:56:39  
23:56:39  ********************

@lorentey
Copy link
Member Author

macOS runs look back in green this morning; Windows runs less so.

@swift-ci smoke test

@lorentey
Copy link
Member Author

lorentey commented May 21, 2025

Now Linux is failing, too 🤡

10:17:20  UNRESOLVED: lldb-api :: lang/swift/completion/TestSwiftREPLCompletion.py (2 of 411)
10:17:20 
 ******************** TEST 'lldb-api :: lang/swift/completion/TestSwiftREPLCompletion.py' FAILED ********************

@lorentey
Copy link
Member Author

@swift-ci smoke test

@lorentey
Copy link
Member Author

lorentey commented May 22, 2025

This again; it looks like it may actually be triggered by this PR:

[2025-05-22T06:04:25.386Z] /Users/ec2-user/jenkins/workspace/swift-PR-macos-smoke-test/branch-main/swift/test/SILOptimizer/performance-annotations.swift:38:11: error: unexpected error produced: this code performs reference counting operations which can cause locking
[2025-05-22T06:04:25.386Z]   return a[i].x
[2025-05-22T06:04:25.386Z]           ^
[2025-05-22T06:04:25.386Z] /Users/ec2-user/jenkins/workspace/swift-PR-macos-smoke-test/branch-main/swift/test/SILOptimizer/performance-annotations.swift:43:10: error: unexpected note produced: called from here
[2025-05-22T06:04:25.386Z]   return noRTCallsForArrayGet(a, 0)
[2025-05-22T06:04:25.386Z]          ^

In my local builds, this keeps passing (on arm64):

PASS: Swift(macosx-arm64) :: SILOptimizer/performance-annotations.swift (4631 of 11741)

Array subscripts do have bounds checks; but it isn't clear to me how force-inlining Range.contains would trigger more retain/release traffic. 🤔 I guess I'll see if this reproduces on x86_64?

@lorentey
Copy link
Member Author

Right, Array's subscript does not rely on Range.contains or ~= to validate the index; as far as I can tell, this PR does not touch that code.

@lorentey
Copy link
Member Author

No, this doesn't locally reproduce on x86_64, either. I'll try rebasing next.

@lorentey
Copy link
Member Author

Meanwhile,

@swift-ci smoke test macOS platform

@lorentey
Copy link
Member Author

Nah, this still passes locally, even on current HEAD, on arm64 and x86_64 both.

@lorentey
Copy link
Member Author

[2025-05-23T02:17:56.403Z] FAIL: Swift(macosx-x86_64) :: SILOptimizer/performance-annotations.swift (8067 of 19086)

¯\(ツ)

lorentey added 7 commits June 4, 2025 17:02
These are really popular shorthands for implementing bounds checks, but currently they aren’t even marked `@inline(__always)`, so in larger functions they tend to remain outlined, defeating optimizations that would otherwise happen.

The corresponding variants on the partial range types are `@_transparent`; that helps unoptimized performance, so let’s apply the same attribute here.

rdar://151177326
Making the generic ~= transparent does not help unoptimized code; adding a specialization does.
…s-inlined instead

The new overload messes up diagnostics too much.
@lorentey lorentey force-pushed the range-contains-perf branch from e9aaac8 to 487baca Compare June 5, 2025 00:03
@lorentey
Copy link
Member Author

lorentey commented Jun 5, 2025

@swift-ci test

@lorentey
Copy link
Member Author

lorentey commented Jun 5, 2025

@swift-ci smoke test

@lorentey
Copy link
Member Author

lorentey commented Jun 5, 2025

I'm resorting to taking things out until the array lookup regression goes away. First up, a1e51a1 replaces @_transparent with @inline(__always), sacrificing debug performance.

@lorentey
Copy link
Member Author

lorentey commented Jun 6, 2025

[2025-06-05T23:52:18.674Z] PASS: Swift(macosx-x86_64) :: SILOptimizer/performance-annotations.swift (8110 of 19207)

Cool; so evidently somehow Array.subscript does invoke one of these APIs. I'm guessing it might be ~=?

Making it transparent evidently induces new retain/release traffic in Array’s subscript, even though I can find no indication that `ClosedRange.contains` is ever called by that code path. Oh well.
@lorentey
Copy link
Member Author

lorentey commented Jun 6, 2025

Right, macOS got a checkmark.

Of course, Linux would like some attention now:

[2025-06-06T00:24:46.474Z] ◇ Test run started.
[2025-06-06T00:24:46.474Z] ↳ Testing Library Version: 6.3-dev (ca81dffea6bd63d)
[2025-06-06T00:24:46.474Z] ↳ Target Platform: x86_64-unknown-linux-gnu
[2025-06-06T00:24:46.474Z] ◇ Suite "URL.Template Template" started.
[2025-06-06T00:24:46.474Z] ◇ Suite "AttributedString Index Tracking" started.
[2025-06-06T00:24:46.474Z] ◇ Suite "URL.Template Value" started.
[2025-06-06T00:24:46.474Z] ◇ Suite "URL.Template PercentEncoding" started.
[2025-06-06T00:24:46.474Z] ◇ Suite "URL.Template Expression" started.
[2025-06-06T00:24:46.474Z] ◇ Test stringRoundTrip(template:) started.
[2025-06-06T00:24:46.474Z] ◇ Test convertToNFCAndPercentEncode() started.
[2025-06-06T00:24:46.474Z] ◇ Test creating() started.
[2025-06-06T00:24:46.474Z] ◇ Test collapseRight() started.
[2025-06-06T00:24:46.474Z] ◇ Test insertionAtEmptyRange() started.
[2025-06-06T00:24:46.474Z] ◇ Test expressibleByLiteral() started.
[2025-06-06T00:24:46.474Z] ◇ Test normalizedAddingPercentEncoding_unreserved() started.
[2025-06-06T00:24:46.474Z] ◇ Test parsingWithMultipleNames() started.
[2025-06-06T02:28:53.851Z] Cancelling nested steps due to timeout
[2025-06-06T02:28:53.854Z] ◇ Test invalidInputRanges() startedSending interrupt signal to process
[2025-06-06T02:29:11.487Z] script returned exit code 143

@lorentey lorentey force-pushed the range-contains-perf branch from a1e51a1 to ff3cf98 Compare June 6, 2025 02:50
@lorentey
Copy link
Member Author

lorentey commented Jun 6, 2025

Further investigation indicates that the performance-annotations issue is caused by the @_transparent attribute on ClosedRange.contains(_: Element), but only if stdlib assertions are enabled, and only when the ObjC runtime is present.

This smells like we have a stray _internalInvariant on Array.subscript's bridging path somewhere that somehow touches ClosedRange, but for the life of me I cannot find it, or figure out how the compiler would think a transparent contains would induce retain/release traffic. Giving up is the right move at this point; this entry point shouldn't be on anyone's bounds checking hot path. I'll still make ClosedRange.contains force-inlined, but I leave marking it @_transparent to someone else.

@lorentey
Copy link
Member Author

lorentey commented Jun 6, 2025

@swift-ci test

@lorentey lorentey enabled auto-merge June 6, 2025 03:00
@lorentey lorentey merged commit e473977 into swiftlang:main Jun 6, 2025
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants