Skip to content
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

Timer.measure return generic value #20

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

vanvoorden
Copy link

@vanvoorden vanvoorden commented Jan 16, 2024

Background

The current implementation of Timer.measure takes a body closure that returns no parameter. If body performs some work (or produces some value) that needs to pass a precondition, we can perform that validation in the closure:

timer.measure {
  let result = computeResult()
  precondition(
    performExpensiveValidation(result)
  )
}

If performExpensiveValidation needs to do a lot of work, we are (implicitly) capturing that work along with our measure (which might not be what we want).

An alternative is to capture result and save to a local variable outside of scope:

var result: Result?
timer.measure {
  result = computeResult()
}
precondition(
  performExpensiveValidation(result!)
)

This works… but another possible approach is to just change Timer.measure to return a value (which might be Void). Then, we can do something even simpler:

let result = timer.measure {
  computeResult()
}
precondition(
  performExpensiveValidation(result)
)

Changes

mutating func measure<T>(_ body: () -> T) -> T {
  ...
  let result = body()
  ...
  return result
}

Test Plan

Local tests (from swift-collections-benchmark) pass… but AFAIK none of these unit tests are specifically testing Timer.measure. We could choose to add those unit tests here.

For an "integration" test, we can try pointing a fork of swift-collections to this fork (vanvoorden/swift-collections@ce9db34) and then build to confirm our metrics are still collecting correctly.

swift run -c release benchmark run results.json --cycles 5 --tasks \
"OrderedSet<Int> equality different instance" \
"OrderedSet<Int> equality same instance" \
"OrderedSet<Int>.SubSequence equality different instance" \
"OrderedSet<Int>.SubSequence equality same instance"

swift run -c release benchmark render results.json results.png --amortized false --tasks \
"OrderedSet<Int> equality different instance" \
"OrderedSet<Int> equality same instance" \
"OrderedSet<Int>.SubSequence equality different instance" \
"OrderedSet<Int>.SubSequence equality same instance"
results

Checklist

  • I've read the Contribution Guidelines
  • My contributions are licensed under the Swift license.
  • I've followed the coding style of the rest of the project.
  • I've added tests covering my changes (if appropriate).
  • I've verified that my change compiles and works correctly, and does not break any existing tests.
  • I've updated the documentation (if appropriate).

@vanvoorden vanvoorden changed the title timer measure generic return value Timer.measure return generic value Jan 16, 2024
@vanvoorden vanvoorden marked this pull request as ready for review January 16, 2024 01:36
@vanvoorden vanvoorden requested a review from lorentey as a code owner January 16, 2024 01:36
@vanvoorden
Copy link
Author

@lorentey Any thoughts about this? I've been using this patch locally for a new project. It seems to be working fine for me… is there anything unsafe about this approach?

@lorentey
Copy link
Member

Hm; my reason for not declaring measure this way was that doing that would make it an non-inlinable (i.e., unspecializable) generic function, which has the potential to interfere with measurements in unexpected ways.

To resolve this worry, we could analyze generated code to figure out if that's actually the case (and then watch out for potential future regressions), or we could simply implement this as a second variant, in parallel to the existing measure!

I doubt we could overload the measure name without trouble (although it would be worth a try!), so the second option could require choosing an alternate name for the new variant.

@vanvoorden
Copy link
Author

vanvoorden commented Mar 22, 2024

doing that would make it an non-inlinable

https://github.com/apple/swift-collections-benchmark/blob/main/Sources/CollectionsBenchmark/Basics/Timer.swift#L63

@inline(never)
  public mutating func measure(_ body: () -> Void)

@lorentey Hmm… not sure I understand… it already is not inlined by design?

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.

2 participants