Skip to content

[ABI] [stdlib] Remove magnitude-based overload of abs(_:). #21037

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 2 commits into from
Dec 6, 2018

Conversation

DougGregor
Copy link
Member

The standard library has two versions of the abs(_:) function:

func abs<T : SignedNumeric>(_ x: T) -> T where T.Magnitude == T
func abs<T : SignedNumeric & Comparable>(_ x: T) -> T

The first is more specialized than the second because T.Magnitude is
known to conform to Comparable. Indeed, it’s a more specialized
implementation that returns magnitude.

However, this overload behaves oddly: in the expression abs(-8), the type
checker will pick the first overload because it is more specialized. That’s
a general guiding principle for overloading: pick the most specialized
overload that works.

However, to select that overload, it needs to pick a type for the literal
“8” for which that overload works, and it chooses Double. The “obvious”
answer, Int, doesn’t work because Int.Magnitude == UInt.

There is a conflict between the two rules, here: we prefer more-specialized
overloads (but we’ll fall back to less-specialized if those don’t work) and we prefer to use Int for integer literals (but we’ll fall back to Double if it doesn’t work). We have a few options from a type-checker
perspective:

  1. Consider the more-specialized-function rule to be more important
  2. Consider the integer-literals-prefer-Int rule to be more important
  3. Call the result ambiguous and make the user annotate it

The type checker currently does #1, although at some point in the past it
did #2. Moving forward, #1 is a better choice because it prunes the number
of overloads that need to be considered: if the more-specialized overload
succeeds its type-check, the others need not be considered. It’s also
easier to reason about than the literal-scoring approach, because there can
be a direct definition for “more specialized than” that can be reasoned
about.

I think we should dodge the issue by removing the more-specialized version
of abs(_:). Its use of magnitude seems unlikely to provide a
significant performance benefit, and the presence of overloading either
forces us to consider both overloads always (which is bad for type checker
performance) or accept the regression that abs(-8) is Double. Better
to eliminate the overloading and, if needed in the future, find a better
way to introduce the more-specialized implementation without it being a
separate signature.

Fixes rdar://problem/42345366.

The standard library has two versions of the `abs(_:)` function:

```
func abs<T : SignedNumeric>(_ x: T) -> T where T.Magnitude == T
func abs<T : SignedNumeric & Comparable>(_ x: T) -> T
```

The first is more specialized than the second because `T.Magnitude` is
known to conform to `Comparable`. Indeed, it’s a more specialized
implementation that returns `magnitude`.

However, this overload behaves oddly: in the expression `abs(-8)`, the type
checker will pick the first overload because it is more specialized. That’s
a general guiding principle for overloading: pick the most specialized
overload that works.

However, to select that overload, it needs to pick a type for the literal
“8” for which that overload works, and it chooses `Double`. The “obvious”
answer, `Int`, doesn’t work because `Int.Magnitude == UInt`.

There is a conflict between the two rules, here: we prefer more-specialized
overloads (but we’ll fall back to less-specialized if those don’t work) and we prefer to use `Int` for integer literals (but we’ll fall back to `Double` if it doesn’t work). We have a few options from a type-checker
perspective:

1. Consider the more-specialized-function rule to be more important
2. Consider the integer-literals-prefer-`Int` rule to be more important
3. Call the result ambiguous and make the user annotate it

The type checker currently does #1, although at some point in the past it
did #2. Moving forward, #1 is a better choice because it prunes the number
of overloads that need to be considered: if the more-specialized overload
succeeds its type-check, the others need not be considered. It’s also
easier to reason about than the literal-scoring approach, because there can
be a direct definition for “more specialized than” that can be reasoned
about.

I think we should dodge the issue by removing the more-specialized version
of `abs(_:)`. Its use of `magnitude` seems unlikely to provide a
significant performance benefit, and the presence of overloading either
forces us to consider both overloads always (which is bad for type checker
performance) or accept the regression that `abs(-8)` is `Double`. Better
to eliminate the overloading and, if needed in the future, find a better
way to introduce the more-specialized implementation without it being a
separate signature.

Fixes rdar://problem/42345366.
@DougGregor
Copy link
Member Author

@swift-ci please benchmark

@DougGregor
Copy link
Member Author

@swift-ci please smoke test

@DougGregor
Copy link
Member Author

Potentially controversial standard library change here ;). Let's see if it affects benchmarks at all.

@swift-ci
Copy link
Contributor

swift-ci commented Dec 5, 2018

Build comment file:

Performance: -O

TEST OLD NEW DELTA RATIO
Regression
Breadcrumbs.CopyUTF16CodeUnits.Mixed 57 68 +19.3% 0.84x
IterateData 1399 1563 +11.7% 0.90x
Improvement
NopDeinit 54193 44961 -17.0% 1.21x
SortStringsUnicode 3594 3210 -10.7% 1.12x
CStringLongAscii 470 422 -10.2% 1.11x

Code size: -O

TEST OLD NEW DELTA RATIO
Regression
MonteCarloPi.o 1593 1641 +3.0% 0.97x
MonteCarloE.o 3396 3444 +1.4% 0.99x
Improvement
Walsh.o 8420 7708 -8.5% 1.09x

Performance: -Osize

TEST OLD NEW DELTA RATIO
Regression
IterateData 1355 1532 +13.1% 0.88x
Integrate 334 362 +8.4% 0.92x
Improvement
SortStringsUnicode 3565 3156 -11.5% 1.13x
Breadcrumbs.CopyUTF16CodeUnits.ASCII 20 18 -10.0% 1.11x

Code size: -Osize

TEST OLD NEW DELTA RATIO
Regression
MonteCarloPi.o 1578 1610 +2.0% 0.98x

Performance: -Onone

TEST OLD NEW DELTA RATIO
Regression
Integrate 1517 1926 +27.0% 0.79x
ArrayOfGenericPOD2 1066 1180 +10.7% 0.90x (?)
ArrayOfPOD 777 859 +10.6% 0.90x
Improvement
SortStringsUnicode 5180 4820 -6.9% 1.07x
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 Pro
  Model Identifier: MacPro6,1
  Processor Name: 12-Core Intel Xeon E5
  Processor Speed: 2.7 GHz
  Number of Processors: 1
  Total Number of Cores: 12
  L2 Cache (per Core): 256 KB
  L3 Cache: 30 MB
  Memory: 64 GB
--------------

@stephentyrone
Copy link
Contributor

It's not only there for a performance benefit; the SignedNumeric & Comparable implementation is incorrect for types conforming FloatingPoint (it does the wrong thing by failing to negate -0 and nans with the sign bit set).

We can probably address this by adding a FloatingPoint overload.

@DougGregor
Copy link
Member Author

In a generic context, we won't get the magnitude-based implementation regardless, and overloading on FloatingPoint will introduce the same problems as the existing overload.

@DougGregor
Copy link
Member Author

For example, this does the wrong thing (prints -0.0) in Swift 4.2:

func test<T: SignedNumeric & Comparable>(_ x: T) {
  print(abs(x))
}

test(-0.0)

Within the (single) implementation of abs(_:), dynamically check whether
the numeric type and its `Magnitude` are of the same type and, if so,
return the result of `magnitude`. This ensures that we do the right thing
with respect to (e.g.) floating point values like -0.0, without resorting
to overloading of abs(_:).
@DougGregor
Copy link
Member Author

Hey @stephentyrone , I'd love your thoughts on the evil trick in ab75577

@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor
Copy link
Member Author

@swift-ci please benchmark

1 similar comment
@DougGregor
Copy link
Member Author

@swift-ci please benchmark

@DougGregor
Copy link
Member Author

@swift-ci please test

@swift-ci
Copy link
Contributor

swift-ci commented Dec 6, 2018

Build failed
Swift Test OS X Platform
Git Sha - 85d488d

@swift-ci
Copy link
Contributor

swift-ci commented Dec 6, 2018

Build comment file:

Code size: -O

TEST OLD NEW DELTA RATIO
Improvement
Walsh.o 8420 7628 -9.4% 1.10x
Integrate.o 2520 2456 -2.5% 1.03x

Code size: -Osize

TEST OLD NEW DELTA RATIO
Improvement
Walsh.o 6020 5900 -2.0% 1.02x
Integrate.o 2502 2454 -1.9% 1.02x

Performance: -Onone

TEST OLD NEW DELTA RATIO
Regression
Integrate 1509 2059 +36.4% 0.73x
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 Pro
  Model Identifier: MacPro6,1
  Processor Name: 12-Core Intel Xeon E5
  Processor Speed: 2.7 GHz
  Number of Processors: 1
  Total Number of Cores: 12
  L2 Cache (per Core): 256 KB
  L3 Cache: 30 MB
  Memory: 64 GB
--------------

@stephentyrone
Copy link
Contributor

Hey @stephentyrone , I'd love your thoughts on the evil trick in ab75577

LGTM.

@stephentyrone
Copy link
Contributor

There's a few things about this function that kind of stink, but your changes aren't making it any worse. We should take this as-is, but I may poke at it some more and send you another follow-on PR.

@stephentyrone
Copy link
Contributor

(Also, please clone this for 5.0 when you have a chance.)

@DougGregor
Copy link
Member Author

Okay!

@DougGregor DougGregor merged commit 00d2acd into swiftlang:master Dec 6, 2018
@DougGregor DougGregor deleted the flatten-abs branch December 7, 2018 03:29
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