Skip to content

[RFC][DNM] Add isIdentical Methods for Quick Comparisons to String and Substring #82055

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions benchmark/single-source/StringTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ public var benchmarks: [BenchmarkInfo] {
runFunction: run_iterateWords,
tags: [.validation, .String]))
}
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *) {
result.append(
BenchmarkInfo(
name: "StringIdentical",
runFunction: run_StringIdentical,
tags: [.validation, .String]))
}
return result
}

Expand Down Expand Up @@ -1676,3 +1683,14 @@ public func run_iterateWords(_ n: Int) {
blackHole(swiftOrgHTML._words)
}
}

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *)
public func run_StringIdentical(_ n: Int) {
let str1 = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. "
let str2 = str1
for _ in 0 ..< n {
for _ in 0 ..< 100_000 {
check(str1.isIdentical(to: str2))
}
}
}
58 changes: 39 additions & 19 deletions benchmark/single-source/SubstringTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,36 @@

import TestsUtils

public let benchmarks = [
BenchmarkInfo(name: "EqualStringSubstring", runFunction: run_EqualStringSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "EqualSubstringString", runFunction: run_EqualSubstringString, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "EqualSubstringSubstring", runFunction: run_EqualSubstringSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "EqualSubstringSubstringGenericEquatable", runFunction: run_EqualSubstringSubstringGenericEquatable, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringRemoveFirst1", runFunction: run_SubstringRemoveFirst1, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringRemoveLast1", runFunction: run_SubstringRemoveLast1, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "LessSubstringSubstring", runFunction: run_LessSubstringSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "LessSubstringSubstringGenericComparable", runFunction: run_LessSubstringSubstringGenericComparable, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "StringFromLongWholeSubstring", runFunction: run_StringFromLongWholeSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "StringFromLongWholeSubstringGeneric", runFunction: run_StringFromLongWholeSubstringGeneric, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringComparable", runFunction: run_SubstringComparable, tags: [.validation, .api, .String],
setUpFunction: { blackHole(_comparison) }),
BenchmarkInfo(name: "SubstringEqualString", runFunction: run_SubstringEqualString, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringEquatable", runFunction: run_SubstringEquatable, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringFromLongString2", runFunction: run_SubstringFromLongString, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringFromLongStringGeneric2", runFunction: run_SubstringFromLongStringGeneric, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringTrimmingASCIIWhitespace", runFunction: run_SubstringTrimmingASCIIWhitespace, tags: [.validation, .api, .String]),
]
public var benchmarks: [BenchmarkInfo] {
var result = [
BenchmarkInfo(name: "EqualStringSubstring", runFunction: run_EqualStringSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "EqualSubstringString", runFunction: run_EqualSubstringString, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "EqualSubstringSubstring", runFunction: run_EqualSubstringSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "EqualSubstringSubstringGenericEquatable", runFunction: run_EqualSubstringSubstringGenericEquatable, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringRemoveFirst1", runFunction: run_SubstringRemoveFirst1, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringRemoveLast1", runFunction: run_SubstringRemoveLast1, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "LessSubstringSubstring", runFunction: run_LessSubstringSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "LessSubstringSubstringGenericComparable", runFunction: run_LessSubstringSubstringGenericComparable, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "StringFromLongWholeSubstring", runFunction: run_StringFromLongWholeSubstring, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "StringFromLongWholeSubstringGeneric", runFunction: run_StringFromLongWholeSubstringGeneric, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringComparable", runFunction: run_SubstringComparable, tags: [.validation, .api, .String],
setUpFunction: { blackHole(_comparison) }),
BenchmarkInfo(name: "SubstringEqualString", runFunction: run_SubstringEqualString, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringEquatable", runFunction: run_SubstringEquatable, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringFromLongString2", runFunction: run_SubstringFromLongString, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringFromLongStringGeneric2", runFunction: run_SubstringFromLongStringGeneric, tags: [.validation, .api, .String]),
BenchmarkInfo(name: "SubstringTrimmingASCIIWhitespace", runFunction: run_SubstringTrimmingASCIIWhitespace, tags: [.validation, .api, .String]),
]

if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *) {
result.append(
BenchmarkInfo(
name: "SubstringIdentical",
runFunction: run_SubstringIdentical,
tags: [.validation, .String]))
}
return result
}

// A string that doesn't fit in small string storage and doesn't fit in Latin-1
let longWide = "fὢasὢodὢijὢadὢolὢsjὢalὢsdὢjlὢasὢdfὢijὢliὢsdὢjøὢslὢdiὢalὢiὢ"
Expand Down Expand Up @@ -332,3 +343,12 @@ public func run _LessSubstringSubstringGenericStringProtocol(_ n: Int) {
}
}
*/

@inline(never)
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *)
public func run_SubstringIdentical(_ n: Int) {
let (a, b) = (ss1, ss1)
for _ in 1...n*500 {
blackHole(a.isIdentical(to: b))
}
}
21 changes: 20 additions & 1 deletion stdlib/public/core/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1112,4 +1112,23 @@ extension String {
}
}


@available(SwiftStdlib 6.3, *)
extension String {
/// Returns a boolean value indicating whether this string is identical to
/// `other`.
///
/// Two string values are identical if there is no way to distinguish between
/// them.
///
/// Comparing strings this way includes comparing (normally) hidden
/// implementation details such as the memory location of any underlying
/// string storage object. Therefore, identical strings are guaranteed to
/// compare equal with `==`, but not all equal strings are considered
/// identical.
///
/// - Performance: O(1)
@available(SwiftStdlib 6.3, *)
public func isIdentical(to other: Self) -> Bool {
self._isIdentical(to: other)
}
}
42 changes: 42 additions & 0 deletions stdlib/public/core/Substring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1307,3 +1307,45 @@ extension Substring {
return Substring(_unchecked: Slice(base: base, bounds: r))
}
}

extension Substring {
/// Returns a boolean value indicating whether this substring is identical to
/// `other`.
///
/// Two substring values are identical if there is no way to distinguish
/// between them.
///
/// Comparing substrings this way includes comparing (normally) hidden
/// implementation details such as the memory location of any underlying
/// substring storage object. Therefore, identical substrings are guaranteed
/// to compare equal with `==`, but not all equal substrings are considered
/// identical.
///
/// - Performance: O(1)
@_alwaysEmitIntoClient
public func _isIdentical(to other: Self) -> Bool {
self._wholeGuts.rawBits == other._wholeGuts.rawBits &&
self._offsetRange == other._offsetRange
}
}

@available(SwiftStdlib 6.3, *)
extension Substring {
/// Returns a boolean value indicating whether this substring is identical to
/// `other`.
///
/// Two substring values are identical if there is no way to distinguish
/// between them.
///
/// Comparing substrings this way includes comparing (normally) hidden
/// implementation details such as the memory location of any underlying
/// substring storage object. Therefore, identical substrings are guaranteed
/// to compare equal with `==`, but not all equal substrings are considered
/// identical.
///
/// - Performance: O(1)
@available(SwiftStdlib 6.3, *)
public func isIdentical(to other: Self) -> Bool {
self._isIdentical(to: other)
}
}
58 changes: 58 additions & 0 deletions test/stdlib/StringAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -533,4 +533,62 @@ StringTests.test("hasPrefix/hasSuffix vs Character boundaries") {
expectFalse(s2.hasSuffix("\n"))
}

StringTests.test("isIdentical(to:) small ascii")
.skip(.custom(
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
reason: "Requires Swift 6.3's standard library"
))
.code {
guard #available(SwiftStdlib 6.3, *) else { return }

let a = "Hello"
let b = "Hello"

precondition(a == b)

expectTrue(a.isIdentical(to: a))
expectTrue(b.isIdentical(to: b))
expectTrue(a.isIdentical(to: b)) // Both small ASCII strings
expectTrue(b.isIdentical(to: a))
}

StringTests.test("isIdentical(to:) small unicode")
.skip(.custom(
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
reason: "Requires Swift 6.3's standard library"
))
.code {
guard #available(SwiftStdlib 6.3, *) else { return }

let a = "Cafe\u{301}"
let b = "Cafe\u{301}"
let c = "Café"

precondition(a == b)
precondition(b == c)

expectTrue(a.isIdentical(to: b))
expectTrue(b.isIdentical(to: a))
expectFalse(a.isIdentical(to: c))
expectFalse(b.isIdentical(to: c))
}

StringTests.test("isIdentical(to:) large ascii")
.skip(.custom(
{ if #available(SwiftStdlib 6.3, *) { false } else { true } },
reason: "Requires Swift 6.3's standard library"
))
.code {
guard #available(SwiftStdlib 6.3, *) else { return }

let a = String(repeating: "foo", count: 1000)
let b = String(repeating: "foo", count: 1000)

precondition(a == b)

expectFalse(a.isIdentical(to: b)) // Two large, distinct native strings
expectTrue(a.isIdentical(to: a))
expectTrue(b.isIdentical(to: b))
}

runAllTests()
Loading