Skip to content

BinaryInteger distance(to:) fixes #71387

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

Conversation

oscbyspro
Copy link
Contributor

This patch fixes two unnecessary traps in BinaryInteger's distance(to:) method.

  • UInt.max.distance(to: UInt.max / 2), etc.
  • Int64(Int.max).distance(to: Int64(-1 as Int)), etc.
  • Note that Int uses a bespoke implementation

The new signed code path uses bit width comparisons instead of a value comparisons, which are constant-foldable at compile time for fixed-width integers. The unsigned code path now inlines something similar to Int.init(exactly:), but with appropriate comparisons when needed. I rewrote both unsigned code paths for symmetry.

Here's Int.init(exactly:) for reference:

https://github.com/apple/swift/blob/e0853ff796adba7fc05ddfc48933ede619c4f4a0/stdlib/public/core/Integers.swift#L3708-L3723

Before

@inlinable
@inline(__always)
public func distance(to other: Self) -> Int {
  if !Self.isSigned {
    if self > other {
      if let result = Int(exactly: self - other) {
        return -result //.................................................(1) crashes on UInt.max.distance(to: UInt.max / 2)
      }
    } else {
      if let result = Int(exactly: other - self) {
        return result
      }
    }
  } else {
    let isNegative = self < (0 as Self)
    if isNegative == (other < (0 as Self)) {
      if let result = Int(exactly: other - self) {
        return result
      }
    } else {
      if let result = Int(exactly: self.magnitude + other.magnitude) {
        return isNegative ? result : -result //...........................(2) crashes on Int.max.distance(to: -1)
      }
    }
  }
  _preconditionFailure("Distance is not representable in Int")
}

After

@inlinable
@inline(__always)
public func distance(to other: Self) -> Int {
  if Self.isSigned {
    if self.bitWidth <= Int.bitWidth && other.bitWidth <= Int.bitWidth { //...........................(2a) constant-foldable bit width comparison
      return Int(truncatingIfNeeded: other) - Int(truncatingIfNeeded: self) //........................(2b) checked subtraction
    } else {
      return Int(other - self) //.....................................................................(2c) checked subtraction and Int.init(_:)
    }
  } else {
    if self > other {
      let result: Self = self - other //..............................................................(1a) result is in 0...
      if result.bitWidth < Int.bitWidth || result <= Self(truncatingIfNeeded: Int.min.magnitude) { // (1b) smaller unsigned bit widths always fit
        return ~Int(truncatingIfNeeded: result) &+ 1 //...............................................(1c) result is in 0...Int.min.magnitude
      }
    } else {
      let result: Self = other - self //..............................................................(1d) result is in 0...
      if result.bitWidth < Int.bitWidth || result <= Self(truncatingIfNeeded: Int.max.magnitude) { // (1e) smaller unsigned bit widths always fit
        return Int(truncatingIfNeeded: result) //.....................................................(1f) result is in 0...Int.max.magnitude
      }
    }
  }
  _preconditionFailure("Distance is not representable in Int")
}

Addendum

This patch is the least complicated solution I came up with, and it does not preserve the error message for signed integers. I could alter the implementation, if that is important (with some performance penalty when self.bitWidth > Int.bitWidth). I thought I'd leave this part up for review, however, given that Swift.Int only logs overflow:

https://github.com/apple/swift/blob/e0853ff796adba7fc05ddfc48933ede619c4f4a0/stdlib/public/core/IntegerTypes.swift.gyb#L1817-L1820

…:) method.

- `UInt.max.distance(to: UInt.max / 2)`, etc.
- `Int64(Int.max).distance(to: Int64(-1 as Int))`, etc.
@oscbyspro oscbyspro requested a review from a team as a code owner February 5, 2024 13:10
@glessard
Copy link
Contributor

glessard commented Feb 5, 2024

I'm evaluating this by looking at specialized -O compiler output.

There are two cases to consider: (A) the case where Int can represent any distance between two values of some FixedWidthInteger, or (B) otherwise, which I call the narrowing case.

In the case (A), this fix produces identical output as before for an unsigned type, and the output is as good as for Int for the signed types. So case (A) is a win.

In the narrowing case (B):

  • The unsigned case gets longer by 1 instruction somehow. Seems like a small price to pay for the (A) win.
  • The signed case is definitely more straightforward than before, but there is one fewer trap and I believe that's where the "Distance is not representable in Int" message would appear. How much worse does it look if we restore it?

Note that the "Distance is not representable in Int" message only matters in the narrowing case, and (at the moment) only shows up in debug mode. It does seem valuable to have it though.

Godbolt page: https://swift.godbolt.org/z/4jsoYzGTe

@glessard
Copy link
Contributor

glessard commented Feb 5, 2024

@swift-ci please test

@oscbyspro
Copy link
Contributor Author

How much worse does it look if we restore [the error message]?

Well, there are three checked operations: the subtraction in (A), the initializer in (B), and the subtraction in (B).

(A) Subtraction

This trap is easy to defer, albeit doing so is a bit more verbose.

Note

This does not affect the assembly.

let result = Int(truncatingIfNeeded: other).subtractingReportingOverflow(Int(truncatingIfNeeded: self))
if !result.overflow {
  return result.partialValue
}

(B) Initializer

This trap is similarly easy to defer:

Note

This removes a trap from the assembly. The assembly of using Int(exactly: x)! is identical.

if let result = Int(exactly: other - self) {
  return result
}

(B) Subtraction

Deferring this trap devolves into the unfixed version with extra comparisons, absent recoverable arithmetic.

Warning

The assembly would be worse than output.signed1(n: Swift.Int64, a: Swift.Int64) -> Swift.Int32.


Godbolt page: https://swift.godbolt.org/z/q7qdGazc7

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Feb 6, 2024

The unsigned case gets longer by 1 instruction somehow.

This appears to be a result of doing the semantically correct thing: comparing Stride.min.magnitude.

The chef's choice

So, uhm. If you don't care about the error message, I've got some great unsigned assembly cooking:

Godbolt page: https://swift.godbolt.org/z/5YKdjY6x6

Note

I'm not sure where the assembly trap is hiding, but limit - result >= 0 does trap.

output.unsigned4(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int32:
        mov     rax, rsi
        sub     eax, edi
        ret
// this is the (B) thing-y, not the proposed generic method...

public func distance4(to other: Self) -> Int32 {
  if Self.isSigned {
    if self.bitWidth <= Int32.bitWidth && other.bitWidth <= Int32.bitWidth {
      return Int32(truncatingIfNeeded: other) - Int32(truncatingIfNeeded: self)
    } else {
      return Int32(exactly: other - self)!
    }
  } else {
    if self > other {
      let result: Self = self - other
      if result.bitWidth < Int32.bitWidth || Self(truncatingIfNeeded: Int32.min.magnitude) - result >= (0 as Self) {
        return ~Int32(truncatingIfNeeded: result) &+ 1
      }
    } else {
      let result: Self = other - self
      if result.bitWidth < Int32.bitWidth || Self(truncatingIfNeeded: Int32.max.magnitude) - result >= (0 as Self) {
        return Int32(truncatingIfNeeded: result)
      }
    }
  }
  preconditionFailure("this is unreachable because the comparison either traps or succeeds")
}

@oscbyspro
Copy link
Contributor Author

I've played with this some more and the subtraction trap trick still yields the best result.

Godbolt: https://swift.godbolt.org/z/r6xKqhz71

Before

Assembly
output.bespokeIntInt(n: Swift.Int, a: Swift.Int) -> Swift.Int:
        sub     rsi, rdi
        jo      .LBB1_2
        mov     rax, rsi
        ret
.LBB1_2:
        ud2

output.oldInt64Int64(n: Swift.Int64, a: Swift.Int64) -> Swift.Int64:
        mov     rax, rsi
        xor     rax, rdi
        js      .LBB2_3
        sub     rsi, rdi
        jo      .LBB2_8
        mov     rax, rsi
        ret
.LBB2_3:
        mov     rcx, rdi
        neg     rcx
        cmovs   rcx, rdi
        mov     rax, rsi
        neg     rax
        cmovs   rax, rsi
        add     rax, rcx
        jb      .LBB2_6
        js      .LBB2_7
        mov     rsi, rax
        neg     rsi
        test    rdi, rdi
        cmovs   rsi, rax
        mov     rax, rsi
        ret
.LBB2_8:
        ud2
.LBB2_6:
        ud2
.LBB2_7:
        ud2

output.oldInt64Int32(n: Swift.Int64, a: Swift.Int64) -> Swift.Int32:
        mov     rax, rsi
        xor     rax, rdi
        js      .LBB4_4
        sub     rsi, rdi
        jo      .LBB4_9
        movsxd  rax, esi
        cmp     rax, rsi
        jne     .LBB4_10
        mov     eax, esi
        ret
.LBB4_4:
        mov     rcx, rdi
        neg     rcx
        cmovs   rcx, rdi
        mov     rax, rsi
        neg     rax
        cmovs   rax, rsi
        add     rax, rcx
        jb      .LBB4_7
        cmp     rax, 2147483647
        ja      .LBB4_8
        mov     esi, eax
        neg     esi
        test    rdi, rdi
        cmovs   esi, eax
        mov     eax, esi
        ret
.LBB4_9:
        ud2
.LBB4_10:
        ud2
.LBB4_7:
        ud2
.LBB4_8:
        ud2

output.oldInt32Int64(n: Swift.Int32, a: Swift.Int32) -> Swift.Int64:
        mov     eax, esi
        xor     eax, edi
        js      .LBB5_4
        sub     esi, edi
        jo      .LBB5_7
        movsxd  rax, esi
        ret
.LBB5_4:
        mov     ecx, edi
        neg     ecx
        cmovs   ecx, edi
        mov     eax, esi
        neg     eax
        cmovs   eax, esi
        add     eax, ecx
        jb      .LBB5_6
        mov     ecx, eax
        mov     rax, rcx
        neg     rax
        test    edi, edi
        cmovs   rax, rcx
        ret
.LBB5_7:
        ud2
.LBB5_6:
        ud2

output.oldInt32Int32(n: Swift.Int32, a: Swift.Int32) -> Swift.Int32:
        mov     eax, esi
        xor     eax, edi
        js      .LBB6_3
        sub     esi, edi
        jo      .LBB6_8
        mov     eax, esi
        ret
.LBB6_3:
        mov     ecx, edi
        neg     ecx
        cmovs   ecx, edi
        mov     eax, esi
        neg     eax
        cmovs   eax, esi
        add     eax, ecx
        jb      .LBB6_6
        js      .LBB6_7
        mov     esi, eax
        neg     esi
        test    edi, edi
        cmovs   esi, eax
        mov     eax, esi
        ret
.LBB6_8:
        ud2
.LBB6_6:
        ud2
.LBB6_7:
        ud2

output.oldUInt64Int64(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int64:
        mov     rax, rsi
        sub     rax, rdi
        jae     .LBB6_4
        sub     rdi, rsi
        js      .LBB6_6
        neg     rdi
        mov     rax, rdi
        ret
.LBB6_4:
        js      .LBB6_5
        ret
.LBB6_6:
        ud2
.LBB6_5:
        ud2

output.oldUInt64Int32(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int32:
        mov     rax, rsi
        sub     rax, rdi
        jae     .LBB7_3
        sub     rdi, rsi
        cmp     rdi, 2147483647
        ja      .LBB7_7
        neg     edi
        mov     eax, edi
        ret
.LBB7_3:
        cmp     rax, 2147483647
        ja      .LBB7_6
        ret
.LBB7_7:
        ud2
.LBB7_6:
        ud2

output.oldUInt32Int64(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int64:
        jmp     (output.newUInt32Int64(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int64)

output.oldUInt32Int32(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int32:
        mov     eax, esi
        sub     eax, edi
        jae     .LBB9_4
        sub     edi, esi
        js      .LBB9_6
        neg     edi
        mov     eax, edi
        ret
.LBB9_4:
        js      .LBB9_5
        ret
.LBB9_6:
        ud2
.LBB9_5:
        ud2
Source
extension BinaryInteger {
  public func distanceV1<T>(to other: Self) -> T where T: SignedInteger & FixedWidthInteger {
    if !Self.isSigned {
      if self > other {
        if let result = T(exactly: self - other) {
          return -result
        }
      } else {
        if let result = T(exactly: other - self) {
          return result
        }
      }
    } else {
      let isNegative = self < (0 as Self)
      if isNegative == (other < (0 as Self)) {
        if let result = T(exactly: other - self) {
          return result
        }
      } else {
        if let result = T(exactly: self.magnitude + other.magnitude) {
          return isNegative ? result : -result
        }
      }
    }
    preconditionFailure("Distance is not representable in Int")
  }
}

After

Assembly
output.bespokeIntInt(n: Swift.Int, a: Swift.Int) -> Swift.Int:
        sub     rsi, rdi
        jo      .LBB1_2
        mov     rax, rsi
        ret
.LBB1_2:
        ud2

output.newInt64Int64(n: Swift.Int64, a: Swift.Int64) -> Swift.Int64:
        sub     rsi, rdi
        jo      .LBB8_2
        mov     rax, rsi
        ret
.LBB8_2:
        ud2

output.newInt64Int32(n: Swift.Int64, a: Swift.Int64) -> Swift.Int32:
        sub     rsi, rdi
        jo      .LBB10_3
        movsxd  rax, esi
        cmp     rax, rsi
        jne     .LBB10_4
        mov     eax, esi
        ret
.LBB10_3:
        ud2
.LBB10_4:
        ud2

output.newInt32Int64(n: Swift.Int32, a: Swift.Int32) -> Swift.Int64:
        movsxd  rax, esi
        movsxd  rcx, edi
        sub     rax, rcx
        ret

output.newInt32Int32(n: Swift.Int32, a: Swift.Int32) -> Swift.Int32:
        sub     esi, edi
        jo      .LBB14_2
        mov     eax, esi
        ret
.LBB14_2:
        ud2

output.newUInt64Int64(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int64:
        mov     rax, rsi
        sub     rax, rdi
        ret

output.newUInt64Int32(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int32:
        mov     rax, rsi
        sub     eax, edi
        ret

output.newUInt32Int64(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int64:
        mov     eax, esi
        sub     eax, edi
        neg     eax
        neg     rax
        sub     esi, edi
        cmovae  rax, rsi
        ret

output.newUInt32Int32(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int32:
        mov     eax, esi
        sub     eax, edi
        ret
Source
extension BinaryInteger {
  public func distanceV2<T>(to other: Self) -> T where T: SignedInteger & FixedWidthInteger {
    if Self.isSigned {
      if self.bitWidth <= T.bitWidth && other.bitWidth <= T.bitWidth {
        return T(truncatingIfNeeded: other) - T(truncatingIfNeeded: self)
      } else {
        return T(exactly: other - self)!
      }
    } else {
      if self > other {
        let result: Self = self - other
        if result.bitWidth >= T.bitWidth {
          // This subtraction traps when the result does not fit.
          _ = Self(truncatingIfNeeded: T.min.magnitude) - result
        }
        return ~T(truncatingIfNeeded: result) &+ 1
      } else {
        let result: Self = other - self
        if result.bitWidth >= T.bitWidth {
          // This subtraction traps when the result does not fit.
          _ = Self(truncatingIfNeeded: T.max.magnitude) - result
        }
        return T(truncatingIfNeeded: result)
      }
    }
  }
}

@glessard
Copy link
Contributor

glessard commented Feb 8, 2024

That's impressive! My preference would be to not lose the message output by _preconditionFailure("Distance is not representable in Int") in debug mode, though.

@oscbyspro
Copy link
Contributor Author

Here's a version where all of the traps have been deferred to the precondition failure at the end. It is the original version with the off-by-one calls to init(exactly:) replaced by inline min/max magnitude comparisons, plus a fast path for smaller or similar signed integers.

"BinaryInteger was not built for this..." the narrator mumbled to himself.


Godbolt: https://swift.godbolt.org/z/7fhv84obP


Assembly
output.heroicInt64Int64(n: Swift.Int64, a: Swift.Int64) -> Swift.Int64:
    sub   rsi, rdi
    jo   .LBB1_2
    mov   rax, rsi
    ret
.LBB1_2:
    ud2

output.heroicInt64Int32(n: Swift.Int64, a: Swift.Int64) -> Swift.Int32:
    mov   rax, rsi
    xor   rax, rdi
    js   .LBB3_4
    sub   rsi, rdi
    jo   .LBB3_13
    movsxd rax, esi
    cmp   rax, rsi
    jne   .LBB3_14
    mov   eax, esi
    ret
.LBB3_4:
    mov   rcx, rdi
    neg   rcx
    cmovs  rcx, rdi
    mov   rax, rsi
    neg   rax
    cmovs  rax, rsi
    add   rax, rcx
    jb   .LBB3_10
    test  rdi, rdi
    js   .LBB3_6
    mov   ecx, 2147483648
    cmp   rax, rcx
    ja   .LBB3_11
    neg   eax
    ret
.LBB3_6:
    cmp   rax, 2147483647
    ja   .LBB3_12
    ret
.LBB3_13:
    ud2
.LBB3_14:
    ud2
.LBB3_10:
    ud2
.LBB3_11:
    ud2
.LBB3_12:
    ud2

output.heroicInt32Int64(n: Swift.Int32, a: Swift.Int32) -> Swift.Int64:
    movsxd rax, esi
    movsxd rcx, edi
    sub   rax, rcx
    ret

output.heroicInt32Int32(n: Swift.Int32, a: Swift.Int32) -> Swift.Int32:
    sub   esi, edi
    jo   .LBB5_2
    mov   eax, esi
    ret
.LBB5_2:
    ud2

output.heroicUInt64Int64(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int64:
    mov   rax, rsi
    sub   rax, rdi
    jae   .LBB6_4
    sub   rdi, rsi
    movabs rax, -9223372036854775808
    cmp   rdi, rax
    ja   .LBB6_6
    neg   rdi
    mov   rax, rdi
    ret
.LBB6_4:
    js   .LBB6_5
    ret
.LBB6_6:
    ud2
.LBB6_5:
    ud2

output.heroicUInt64Int32(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int32:
    mov   rax, rsi
    sub   rax, rdi
    jae   .LBB7_3
    sub   rdi, rsi
    mov   eax, 2147483648
    cmp   rdi, rax
    ja   .LBB7_7
    neg   edi
    mov   eax, edi
    ret
.LBB7_3:
    cmp   rax, 2147483647
    ja   .LBB7_6
    ret
.LBB7_7:
    ud2
.LBB7_6:
    ud2

output.heroicUInt32Int64(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int64:
    mov   eax, esi
    sub   eax, edi
    neg   eax
    neg   rax
    sub   esi, edi
    cmovae rax, rsi
    ret

output.heroicUInt32Int32(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int32:
    mov   eax, esi
    sub   eax, edi
    jae   .LBB9_4
    sub   edi, esi
    cmp   edi, -2147483648
    ja   .LBB9_6
    neg   edi
    mov   eax, edi
    ret
.LBB9_4:
    js   .LBB9_5
    ret
.LBB9_6:
    ud2
.LBB9_5:
    ud2
extension BinaryInteger {
 public func distanceWithHeroicEffortsToPreserveTheErrorMessage<T>(to other: Self) -> T where T: SignedInteger & FixedWidthInteger {
  if Self.isSigned {
   if self.bitWidth <= T.bitWidth && other.bitWidth <= T.bitWidth {
    let result = T(truncatingIfNeeded: other).subtractingReportingOverflow(T(truncatingIfNeeded: self))
    if !result.overflow {
     return result.partialValue
    }
   } else {
    let isNegative = self < (0 as Self)
    if isNegative == (other < (0 as Self)) {
     if let result = T(exactly: other - self) {
      return result
     }
    } else {
     let result = self.magnitude + other.magnitude
     if isNegative {
      if result <= T.max.magnitude {
       return T(truncatingIfNeeded: result)
      }
     } else {
      if result <= T.min.magnitude {
       return ~T(truncatingIfNeeded: result) &+ 1
      }
     }
    }
   }
  } else {
   if self <= other {
    let result: Self = other - self
    if result.bitWidth < T.bitWidth || result <= Self(truncatingIfNeeded: T.max.magnitude) {
     return T(truncatingIfNeeded: result)
    }
   } else {
    let result: Self = self - other
    if result.bitWidth < T.bitWidth || result <= Self(truncatingIfNeeded: T.min.magnitude) {
     return ~T(truncatingIfNeeded: result) &+ 1
    }
   }
  }
  preconditionFailure("Distance is not representable in Int")
 }
}

@glessard
Copy link
Contributor

distanceWithHeroicEffortsToPreserveTheErrorMessage

I truly appreciate the efforts!

swiftlang#71387).

This new version is the original distance(to:) method but with the off-by-one calls to init(exactly:) replaced by inline min/max magnitude comparisons, plus a fast path for small and same-size signed integers. Heroic efforts went into deferring the overflow trap until the precondition failure at the end, per code review.
@oscbyspro
Copy link
Contributor Author

oscbyspro commented Feb 10, 2024

I have updated the pull request to match the Heroic ™️ version.


Note that the signed narrowing case would be trivial with recoverable arithmetic:

let large = other.subtractingReportingOverflow(self)
if !large.overflow, let result = Int(exactly: large.partialValue) {
  return result
}

The method is not easily testable, so here's a testable format (click to show):
import XCTest

final class DistanceToTestsForStdlibIntegers: XCTestCase {
  
  func test() {
    check({ (a:  Int64, b:  Int64) -> Int64? in a.distanceButGenericWithOptionalResult(to: b) })
    check({ (a:  Int64, b:  Int64) -> Int32? in a.distanceButGenericWithOptionalResult(to: b) })
    check({ (a:  Int32, b:  Int32) -> Int64? in a.distanceButGenericWithOptionalResult(to: b) })
    check({ (a:  Int32, b:  Int32) -> Int32? in a.distanceButGenericWithOptionalResult(to: b) })
    check({ (a: UInt64, b: UInt64) -> Int64? in a.distanceButGenericWithOptionalResult(to: b) })
    check({ (a: UInt64, b: UInt64) -> Int32? in a.distanceButGenericWithOptionalResult(to: b) })
    check({ (a: UInt32, b: UInt32) -> Int64? in a.distanceButGenericWithOptionalResult(to: b) })
    check({ (a: UInt32, b: UInt32) -> Int32? in a.distanceButGenericWithOptionalResult(to: b) })
  }
  
  func check<T, U>(_ distance: (T, T) -> U?, file: StaticString = #file, line: UInt = #line) where T: FixedWidthInteger, U: SignedInteger & FixedWidthInteger {
    if T.isSigned, T.bitWidth >= U.bitWidth {
      XCTAssertEqual(distance(T(U.max) - 0, T(-1 as U)), U.min, file: file, line: line)
      XCTAssertEqual(distance(T(U.max) - 0, T(-2 as U)),   nil, file: file, line: line)
      XCTAssertEqual(distance(T(U.max) - 1, T(-2 as U)), U.min, file: file, line: line)
      XCTAssertEqual(distance(T(U.max) - 1, T(-3 as U)),   nil, file: file, line: line)
      
      XCTAssertEqual(distance(T( 0 as U), T(U.max) - 0), U.max, file: file, line: line)
      XCTAssertEqual(distance(T(-2 as U), T(U.max) - 1),   nil, file: file, line: line)
      XCTAssertEqual(distance(T(-1 as U), T(U.max) - 1), U.max, file: file, line: line)
      XCTAssertEqual(distance(T(-1 as U), T(U.max) - 0),   nil, file: file, line: line)
    }
    
    if !T.isSigned, T.bitWidth >= U.bitWidth {
      XCTAssertEqual(distance(T(U.max) + 1, T(0 as U)), U.min, file: file, line: line)
      XCTAssertEqual(distance(T(U.max) + 2, T(0 as U)),   nil, file: file, line: line)
      XCTAssertEqual(distance(T(U.max) + 2, T(1 as U)), U.min, file: file, line: line)
      XCTAssertEqual(distance(T(U.max) + 3, T(1 as U)),   nil, file: file, line: line)
      
      XCTAssertEqual(distance(T(0 as U), T(U.max) + 0), U.max, file: file, line: line)
      XCTAssertEqual(distance(T(0 as U), T(U.max) + 1),   nil, file: file, line: line)
      XCTAssertEqual(distance(T(1 as U), T(U.max) + 1), U.max, file: file, line: line)
      XCTAssertEqual(distance(T(1 as U), T(U.max) + 2),   nil, file: file, line: line)
    }
    
    agnostic: if T.bitWidth < U.bitWidth {
      XCTAssertEqual(distance(T.max, T.min), U(T.min) - U(T.max), file: file, line: line)
      XCTAssertEqual(distance(T.min, T.max), U(T.max) - U(T.min), file: file, line: line)
    }
    
    agnostic: if T.bitWidth > U.bitWidth {
      XCTAssertEqual(distance(T(U.max) + T(U.max) + 1, T(U.max)), U.min, file: file, line: line)
      XCTAssertEqual(distance(T(U.max) + T(U.max) + 2, T(U.max)),   nil, file: file, line: line)
      
      XCTAssertEqual(distance(T(U.max), T(U.max) + T(U.max) + 0), U.max, file: file, line: line)
      XCTAssertEqual(distance(T(U.max), T(U.max) + T(U.max) + 1),   nil, file: file, line: line)
      
      guard T.isSigned else { break agnostic }
      
      XCTAssertEqual(distance(T(U.min), T(U.min) + T(U.min) + 0), U.min, file: file, line: line)
      XCTAssertEqual(distance(T(U.min), T(U.min) + T(U.min) - 1),   nil, file: file, line: line)

      XCTAssertEqual(distance(T(U.min) + T(U.min) + 1, T(U.min)), U.max, file: file, line: line)
      XCTAssertEqual(distance(T(U.min) + T(U.min) + 0, T(U.min)),   nil, file: file, line: line)
    }
  }
}

extension BinaryInteger {
  public func distanceButGenericWithOptionalResult<T>(to other: Self) -> T? where T: SignedInteger & FixedWidthInteger {
    if Self.isSigned {
      if self.bitWidth <= T.bitWidth && other.bitWidth <= T.bitWidth {
        let result = T(truncatingIfNeeded: other).subtractingReportingOverflow(T(truncatingIfNeeded: self))
        if !result.overflow {
          return result.partialValue
        }
      } else {
        let isNegative = self < (0 as Self)
        if isNegative == (other < (0 as Self)) {
          if let result = T(exactly: other - self) {
            return result
          }
        } else {
          let result: Magnitude = self.magnitude + other.magnitude
          if isNegative {
            if result <= T.max.magnitude {
              return T(truncatingIfNeeded: result)
            }
          } else {
            if result <= T.min.magnitude {
              return ~T(truncatingIfNeeded: result) &+ 1
            }
          }
        }
      }
    } else {
      if self <= other {
        let result: Self = other - self
        if result.bitWidth < T.bitWidth || result <= Self(truncatingIfNeeded: T.max.magnitude) {
          return T(truncatingIfNeeded: result)
        }
      } else {
        let result: Self = self - other
        if result.bitWidth < T.bitWidth || result <= Self(truncatingIfNeeded: T.min.magnitude) {
          return ~T(truncatingIfNeeded: result) &+ 1
        }
      }
    }
    return nil // _preconditionFailure("Distance is not representable in \(T.self)")
  }
}

@oscbyspro
Copy link
Contributor Author

@swift-ci please test

1 similar comment
@glessard
Copy link
Contributor

@swift-ci please test

@glessard
Copy link
Contributor

@oscbyspro your PR and the many options it presents are being looked at.

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Feb 29, 2024

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Mar 2, 2024

For what it's worth, I think trapping the big-signed-integer subtraction is a better option because it's so straightforward compared to the heroic sign-magnitude round trip.

@glessard
Copy link
Contributor

After some discussion with @stephentyrone, we've concluded that we should simply have the best possible implementation in the standard library, and that suboptimal error messages are a compiler issue that should be fixed (see #72411).

With that in mind, let's get back to the best possible code-gen option that you found earlier, and we should merge that. Thanks!

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Mar 19, 2024

Cool. I ran some more tests, and it turns out that the _ = a - b trick gets elided in -O mode. I'm unsure why that didn't occur earlier, but the ud2-absent assembly makes more sense now. I'll see if there's some other way to improve the unsigned performance (sigh). In either case, I'll replace the sign-and-magnitude round trip and include the two edge case fixes.

func identity<T>(_ x: T) -> T { x }

// This does not trap in -O mode..?

_ = identity(UInt.min) - identity(UInt.max)

Edit: I found this quite surprising, so I opened (#72455) about it.

…on (swiftlang#71387).

This version:

1. drops the large-signed-integer debug message in favor of a faster approach
2. adds a fast path for smaller unsigned integers because the result always fits
3. uses a more performant comparison for smaller and same-size unsigned integers
@oscbyspro
Copy link
Contributor Author

oscbyspro commented Mar 20, 2024

I have picked the most performant version in the latest commit.

Important

The unsigned part is new, so you might want to take a closer look at it.

Here's the inline version of it...
extension BinaryInteger {
  @inlinable
  @inline(__always)
  public func distance(to other: Self) -> Int {
    if Self.isSigned {
      if self.bitWidth <= Int.bitWidth && other.bitWidth <= Int.bitWidth {
        let lhs = Int(truncatingIfNeeded: self)
        let rhs = Int(truncatingIfNeeded: other)
        let result = rhs.subtractingReportingOverflow(lhs)
        if !result.overflow {
          return result.partialValue
        }
      } else {
        // Use trapping subtraction for performance.
        if let result = Int(exactly: other - self) {
          return result
        }
      }
    } else {
      if self.bitWidth < Int.bitWidth && other.bitWidth < Int.bitWidth {
        // Smaller unsigned integers always fit.
        let lhs = Int(truncatingIfNeeded: self)
        let rhs = Int(truncatingIfNeeded: other)
        return rhs &- lhs
      } else if self <= other {
        let result: Self = other - self
        let distance = Int(truncatingIfNeeded: result)
        // The zero comparison generates better code in Swift 5.10.
        if result.bitWidth <= Int.bitWidth {
          if distance >= Int.zero {
            return distance
          }
        } else {
          if result <= Self(truncatingIfNeeded: Int.max.magnitude) {
            return distance
          }
        }
      } else {
        let result: Self = self - other
        let distance = ~Int(truncatingIfNeeded: result) &+ 1
        // The zero comparison generates better code in Swift 5.10.
        if result.bitWidth <= Int.bitWidth {
          if distance < Int.zero {
            return distance
          }
        } else {
          if result <= Self(truncatingIfNeeded: Int.min.magnitude) {
            return distance
          }
        }
      }
    }
    _preconditionFailure("Distance is not representable in Int")
  }
}

Saves all but one message

None has been lost except the one doomed to destruction [...].

Improved unsigned assembly

I managed to improve the unsigned assembly for sizes x <= Int.bitWidth by:

  1. fast pathing sizes x < Int.bitWidth as the result always fits in Int
  2. comparing against zero rather than min/max magnitude for sizes x <= Int.bitWidth

Tests

I added some expectCrashLater() tests in swift/test/stdlib/Integers.swift.gyb.

Godbolt: https://swift.godbolt.org/z/x3Pqncbx3

Show OLD signed results...
output.oldInt32Int32(n: Swift.Int32, a: Swift.Int32) -> Swift.Int32:
        mov     eax, esi
        xor     eax, edi
        js      .LBB6_3
        sub     esi, edi
        jo      .LBB6_8
        mov     eax, esi
        ret
.LBB6_3:
        mov     ecx, edi
        neg     ecx
        cmovs   ecx, edi
        mov     eax, esi
        neg     eax
        cmovs   eax, esi
        add     eax, ecx
        jb      .LBB6_6
        js      .LBB6_7
        mov     esi, eax
        neg     esi
        test    edi, edi
        cmovs   esi, eax
        mov     eax, esi
        ret
.LBB6_8:
        ud2
.LBB6_6:
        ud2
.LBB6_7:
        ud2
        
output.oldInt32Int64(n: Swift.Int32, a: Swift.Int32) -> Swift.Int64:
        mov     eax, esi
        xor     eax, edi
        js      .LBB5_4
        sub     esi, edi
        jo      .LBB5_7
        movsxd  rax, esi
        ret
.LBB5_4:
        mov     ecx, edi
        neg     ecx
        cmovs   ecx, edi
        mov     eax, esi
        neg     eax
        cmovs   eax, esi
        add     eax, ecx
        jb      .LBB5_6
        mov     ecx, eax
        mov     rax, rcx
        neg     rax
        test    edi, edi
        cmovs   rax, rcx
        ret
.LBB5_7:
        ud2
.LBB5_6:
        ud2
        
output.oldInt64Int32(n: Swift.Int64, a: Swift.Int64) -> Swift.Int32:
        mov     rax, rsi
        xor     rax, rdi
        js      .LBB4_4
        sub     rsi, rdi
        jo      .LBB4_9
        movsxd  rax, esi
        cmp     rax, rsi
        jne     .LBB4_10
        mov     eax, esi
        ret
.LBB4_4:
        mov     rcx, rdi
        neg     rcx
        cmovs   rcx, rdi
        mov     rax, rsi
        neg     rax
        cmovs   rax, rsi
        add     rax, rcx
        jb      .LBB4_7
        cmp     rax, 2147483647
        ja      .LBB4_8
        mov     esi, eax
        neg     esi
        test    rdi, rdi
        cmovs   esi, eax
        mov     eax, esi
        ret
.LBB4_9:
        ud2
.LBB4_10:
        ud2
.LBB4_7:
        ud2
.LBB4_8:
        ud2
        
output.oldInt64Int64(n: Swift.Int64, a: Swift.Int64) -> Swift.Int64:
        mov     rax, rsi
        xor     rax, rdi
        js      .LBB2_3
        sub     rsi, rdi
        jo      .LBB2_8
        mov     rax, rsi
        ret
.LBB2_3:
        mov     rcx, rdi
        neg     rcx
        cmovs   rcx, rdi
        mov     rax, rsi
        neg     rax
        cmovs   rax, rsi
        add     rax, rcx
        jb      .LBB2_6
        js      .LBB2_7
        mov     rsi, rax
        neg     rsi
        test    rdi, rdi
        cmovs   rsi, rax
        mov     rax, rsi
        ret
.LBB2_8:
        ud2
.LBB2_6:
        ud2
.LBB2_7:
        ud2
Show OLD unsigned results...
output.oldUInt32Int32(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int32:
        mov     eax, esi
        sub     eax, edi
        jae     .LBB10_4
        sub     edi, esi
        js      .LBB10_6
        neg     edi
        mov     eax, edi
        ret
.LBB10_4:
        js      .LBB10_5
        ret
.LBB10_6:
        ud2
.LBB10_5:
        ud2

output.oldUInt32Int64(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int64:
        mov     eax, esi
        sub     eax, edi
        neg     eax
        neg     rax
        sub     esi, edi
        cmovae  rax, rsi
        ret

output.oldUInt64Int32(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int32:
        mov     rax, rsi
        sub     rax, rdi
        jae     .LBB8_3
        sub     rdi, rsi
        cmp     rdi, 2147483647
        ja      .LBB8_7
        neg     edi
        mov     eax, edi
        ret
.LBB8_3:
        cmp     rax, 2147483647
        ja      .LBB8_6
        ret
.LBB8_7:
        ud2
.LBB8_6:
        ud2

output.oldUInt64Int64(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int64:
        mov     rax, rsi
        sub     rax, rdi
        jae     .LBB7_4
        sub     rdi, rsi
        js      .LBB7_6
        neg     rdi
        mov     rax, rdi
        ret
.LBB7_4:
        js      .LBB7_5
        ret
.LBB7_6:
        ud2
.LBB7_5:
        ud2
Show NEW signed results...
output.newInt32Int32(n: Swift.Int32, a: Swift.Int32) -> Swift.Int32:
        sub     esi, edi
        jo      .LBB15_2
        mov     eax, esi
        ret
.LBB15_2:
        ud2

output.newInt32Int64(n: Swift.Int32, a: Swift.Int32) -> Swift.Int64:
        movsxd  rax, esi
        movsxd  rcx, edi
        sub     rax, rcx
        ret

output.newInt64Int32(n: Swift.Int64, a: Swift.Int64) -> Swift.Int32:
        sub     rsi, rdi
        jo      .LBB13_3
        movsxd  rax, esi
        cmp     rax, rsi
        jne     .LBB13_4
        mov     eax, esi
        ret
.LBB13_3:
        ud2
.LBB13_4:
        ud2

output.newInt64Int64(n: Swift.Int64, a: Swift.Int64) -> Swift.Int64:
        sub     rsi, rdi
        jo      .LBB11_2
        mov     rax, rsi
        ret
.LBB11_2:
        ud2
Show NEW unsigned results...
output.newUInt32Int32(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int32:
        sub     esi, edi
        jae     .LBB19_3
        js      .LBB19_5
        ud2
.LBB19_3:
        js      .LBB19_4
.LBB19_5:
        mov     eax, esi
        ret
.LBB19_4:
        ud2
        
output.newUInt32Int64(n: Swift.UInt32, a: Swift.UInt32) -> Swift.Int64:
        mov     ecx, edi
        mov     eax, esi
        sub     rax, rcx
        ret

output.newUInt64Int32(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int32:
        mov     rax, rsi
        sub     rax, rdi
        jae     .LBB17_4
        sub     rdi, rsi
        mov     eax, 2147483648
        cmp     rdi, rax
        ja      .LBB17_6
        neg     edi
        mov     eax, edi
        ret
.LBB17_4:
        cmp     rax, 2147483647
        ja      .LBB17_5
        ret
.LBB17_6:
        ud2
.LBB17_5:
        ud2

output.newUInt64Int64(n: Swift.UInt64, a: Swift.UInt64) -> Swift.Int64:
        sub     rsi, rdi
        jae     .LBB16_3
        js      .LBB16_5
        ud2
.LBB16_3:
        js      .LBB16_4
.LBB16_5:
        mov     rax, rsi
        ret
.LBB16_4:
        ud2

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Mar 21, 2024

Here's a much simpler version, with identical specialized -O assembly, written by omitting the same-size-signed-integer debug message. It would require that the expectCrashLater() tests blackHole(_:) the unused expression in -O mode for (#72455) reasons, however. Not sure why, maybe it passes some kind of threshold?

Godbolt: https://swift.godbolt.org/z/4Mqjrex6E

extension BinaryInteger {
  @inlinable
  @inline(__always)
  public func distance(to other: Self) -> Int {
    if self.bitWidth < Int.bitWidth && other.bitWidth < Int.bitWidth {
      // Smaller integers always fit.
      return Int(truncatingIfNeeded: other) &- Int(truncatingIfNeeded: self)
    } else if Self.isSigned || self <= other {
      // Use trapping subtraction for performance.
      if let result = Int(exactly: other - self) {
        return result
      }
    } else {
      // This type is unsigned and the distance is negative here.
      let absolute = self - other
      let distance = ~Int(truncatingIfNeeded: absolute) &+ 1
      // The zero comparison generates better code in Swift 5.10.
      if absolute.bitWidth <= Int.bitWidth {
        if distance < Int.zero {
          return distance
        }
      } else {
        if absolute <= Self(truncatingIfNeeded: Int.min.magnitude) {
          return distance
        }
      }
    }
    _preconditionFailure("Distance is not representable in Int")
  }
}

Edit: I suppose StdlibUnittest calls it noop(_:) instead of blackHole(_:).

https://github.com/apple/swift/blob/8e72a0161665925c57f9b911d3f070ba9ed18051/stdlib/private/StdlibUnittest/StdlibUnittest.swift#L173-L174

@glessard
Copy link
Contributor

glessard commented Apr 1, 2024

@swift-ci please benchmark

@glessard
Copy link
Contributor

glessard commented Apr 1, 2024

@swift-ci please test

@oscbyspro
Copy link
Contributor Author

Performance (x86_64): -O

Regression OLD NEW DELTA RATIO
LessSubstringSubstring 24.275 30.7 +26.5% 0.79x (?)
EqualSubstringString 24.394 30.8 +26.3% 0.79x (?)
EqualSubstringSubstring 24.583 31.034 +26.2% 0.79x (?)
LessSubstringSubstringGenericComparable 24.5 30.92 +26.2% 0.79x (?)
EqualSubstringSubstringGenericEquatable 24.719 31.129 +25.9% 0.79x
UTF8Decode_InitFromData 143.583 178.5 +24.3% 0.80x
EqualStringSubstring 24.808 30.8 +24.2% 0.81x
UTF8Decode_InitDecoding 142.75 175.2 +22.7% 0.81x (?)
UTF8Decode_InitFromCustom_contiguous 143.308 175.364 +22.4% 0.82x (?)
UTF8Decode_InitFromBytes 146.917 179.2 +22.0% 0.82x (?)
UTF8Decode_InitFromCustom_noncontiguous 185.1 219.111 +18.4% 0.84x (?)
StringComparison_longSharedPrefix 211.444 242.7 +14.8% 0.87x (?)
Set.isStrictSubset.Int.Empty 42.818 47.367 +10.6% 0.90x (?)
String.initRepeating.longMixStr.Count100 718.667 791.5 +10.1% 0.91x (?)
StringHashing_nonBMPSlowestPrenormal 305.0 332.917 +9.2% 0.92x (?)
DropWhileAnySequenceLazy 380.4 412.75 +8.5% 0.92x (?)
StringComparison_nonBMPSlowestPrenormal 467.2 505.357 +8.2% 0.92x (?)
StringComparison_emoji 261.677 281.576 +7.6% 0.93x (?)
StringHashing_emoji 243.68 262.095 +7.6% 0.93x (?)
 
Improvement OLD NEW DELTA RATIO
ArrayAppendGenericStructs 1766.667 1400.0 -20.8% 1.26x (?)
NormalizedIterator_ascii 104.25 93.24 -10.6% 1.12x
NormalizedIterator_fastPrenormal 544.545 490.417 -9.9% 1.11x (?)
StringBuilderSmallReservingCapacity 264.667 240.75 -9.0% 1.10x (?)
StringHasPrefixAscii 2423.75 2215.0 -8.6% 1.09x (?)
String.replaceSubrange.String 10.673 9.792 -8.3% 1.09x (?)
StringSwitch 252.286 231.5 -8.2% 1.09x (?)
Dictionary4 217.0 199.857 -7.9% 1.09x (?)
NormalizedIterator_latin1 188.9 174.476 -7.6% 1.08x (?)
StringAdder 309.167 286.286 -7.4% 1.08x (?)

Code size: -O

Improvement OLD NEW DELTA RATIO
BinaryFloatingPointConversionFromBinaryInteger.o 24716 17436 -29.5% 1.42x

Performance (x86_64): -Osize

Regression OLD NEW DELTA RATIO
EqualSubstringSubstring 23.837 30.258 +26.9% 0.79x
EqualStringSubstring 23.839 30.25 +26.9% 0.79x (?)
EqualSubstringSubstringGenericEquatable 23.841 30.25 +26.9% 0.79x (?)
EqualSubstringString 24.813 31.345 +26.3% 0.79x (?)
LessSubstringSubstring 25.029 31.567 +26.1% 0.79x (?)
LessSubstringSubstringGenericComparable 24.938 31.033 +24.4% 0.80x (?)
UTF8Decode_InitFromData 142.875 175.5 +22.8% 0.81x (?)
UTF8Decode_InitDecoding 142.455 174.9 +22.8% 0.81x (?)
UTF8Decode_InitFromCustom_contiguous 143.25 175.875 +22.8% 0.81x (?)
UTF8Decode_InitFromBytes 146.0 178.3 +22.1% 0.82x (?)
Set.isDisjoint.Seq.Int.Empty 49.0 57.714 +17.8% 0.85x (?)
Set.isDisjoint.Int.Empty 49.0 57.708 +17.8% 0.85x (?)
Set.isSubset.Int.Empty 46.81 54.8 +17.1% 0.85x (?)
StringComparison_longSharedPrefix 205.75 238.1 +15.7% 0.86x (?)
String.initRepeating.longMixStr.Count100 717.0 791.0 +10.3% 0.91x (?)
StringWordBuilderReservingCapacity 1199.231 1316.154 +9.7% 0.91x (?)
DropFirstAnySequence 987.0 1080.5 +9.5% 0.91x (?)
PrefixWhileAnySequenceLazy 991.5 1085.0 +9.4% 0.91x (?)
PrefixAnySequence 1005.0 1099.0 +9.4% 0.91x (?)
StringHashing_nonBMPSlowestPrenormal 304.375 332.75 +9.3% 0.91x (?)
StringComparison_emoji 256.25 279.429 +9.0% 0.92x (?)
NormalizedIterator_slowerPrenormal 292.264 318.592 +9.0% 0.92x (?)
UTF8Decode_InitFromCustom_noncontiguous 309.333 336.5 +8.8% 0.92x (?)
String.initRepeating.1AsciiChar.Count100 467.75 505.0 +8.0% 0.93x (?)
StringHashing_emoji 243.478 262.286 +7.7% 0.93x (?)
 
Improvement OLD NEW DELTA RATIO
NibbleSort 1877.778 1546.364 -17.6% 1.21x (?)
FlattenListLoop 1640.0 1388.0 -15.4% 1.18x (?)
StringHasPrefixAscii 2461.25 2152.222 -12.6% 1.14x (?)
StringSwitch 258.833 226.875 -12.3% 1.14x (?)
NormalizedIterator_ascii 103.217 91.8 -11.1% 1.12x (?)
StringBuilder 255.375 233.125 -8.7% 1.10x (?)
StringAdder 306.833 280.714 -8.5% 1.09x (?)
String.replaceSubrange.String 10.667 9.792 -8.2% 1.09x (?)
StringBuilderSmallReservingCapacity 264.0 242.875 -8.0% 1.09x (?)
CharIteration_tweet_unicodeScalars 8576.0 7991.111 -6.8% 1.07x (?)
StringUTF16Builder 272.564 254.242 -6.7% 1.07x (?)
CharIteration_ascii_unicodeScalars 4410.667 4114.286 -6.7% 1.07x (?)

Code size: -Osize

Improvement OLD NEW DELTA RATIO
BinaryFloatingPointConversionFromBinaryInteger.o 23874 16698 -30.1% 1.43x

Performance (x86_64): -Onone

Regression OLD NEW DELTA RATIO
LessSubstringSubstringGenericComparable 27.109 33.417 +23.3% 0.81x (?)
EqualSubstringSubstringGenericEquatable 27.76 34.107 +22.9% 0.81x (?)
UTF8Decode_InitFromData 142.636 175.0 +22.7% 0.82x (?)
LessSubstringSubstring 28.29 34.69 +22.6% 0.82x (?)
EqualSubstringSubstring 29.032 35.214 +21.3% 0.82x (?)
UTF8Decode_InitDecoding 151.0 183.0 +21.2% 0.83x (?)
UTF8Decode_InitFromBytes 149.5 180.9 +21.0% 0.83x (?)
EqualSubstringString 29.289 35.379 +20.8% 0.83x (?)
EqualStringSubstring 29.28 35.261 +20.4% 0.83x (?)
UTF8Decode_InitFromCustom_contiguous 154.0 185.4 +20.4% 0.83x
String.initRepeating.longMixStr.Count100 718.0 790.0 +10.0% 0.91x (?)
StringFromLongWholeSubstring 9.035 9.793 +8.4% 0.92x (?)
 
Improvement OLD NEW DELTA RATIO
Array.removeAll.keepingCapacity.Object 8.931 5.965 -33.2% 1.50x (?)
ArrayOfPOD 737.0 665.0 -9.8% 1.11x (?)
ArrayOfGenericPOD2 1042.0 953.0 -8.5% 1.09x (?)
ArrayAppendLatin1Substring 26694.0 24504.0 -8.2% 1.09x (?)
ArrayAppendAsciiSubstring 26406.0 24264.0 -8.1% 1.09x (?)
ArrayAppendUTF16Substring 26328.0 24384.0 -7.4% 1.08x (?)

Code size: -swiftlibs

How to read the data The tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.

If you see any unexpected regressions, you should consider fixing the
regressions before you merge the PR.

Noise: Sometimes the performance results (not code size!) contain false
alarms. Unexpected regressions which are marked with '(?)' are probably noise.
If you see regressions which you cannot explain you can try to run the
benchmarks again. If regressions still show up, please consult with the
performance team (@eeckstein).

Hardware Overview
  Model Name: Mac mini
  Model Identifier: Macmini8,1
  Processor Name: 6-Core Intel Core i7
  Processor Speed: 3.2 GHz
  Number of Processors: 1
  Total Number of Cores: 6
  L2 Cache (per Core): 256 KB
  L3 Cache: 12 MB
  Memory: 32 GB

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Apr 3, 2024

Do you have any thoughts on the results? I would not have thought there would be specialized fixed-width regressions (stdlib). Although, I'm unfamiliar with the tests that regressed. String stuff, mostly. I could check what they are all about. I suppose I could add some [U]Int128 tests while I'm at it.

@eeckstein
Copy link
Contributor

Most results look like noise (especially those marked with a "?"). Though it would be good to double check the few regressions (not marked with "?"). E.g. by running the benchmarks in instruments and look at the disassembly differences in the hot code area

@oscbyspro oscbyspro closed this Sep 16, 2024
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.

3 participants