Jens
1
I've never seen a clear answer, despite numerous related blog posts and conversations.
Here's a recent one (that can continue in this thread).
It mentions the following alternatives:
-
CACurrentMediaTime()
-
Foundation.clock()
-
mach_absolute_time() together with mach_timebase_info(…)
-
CFAbsoluteTimeGetCurrent()
-
clock_gettime_nsec_np(CLOCK_UPTIME_RAW)
-
DispatchTime.now().uptimeNanoseconds
-
ProcessInfo.processInfo.systemUptime
Is there a way that is accurate, discoverable, non-verbose and works reliably across most common platforms without specific imports etc?
If not, should/could one be added to the Standard Library? If so, how?
Edit: The accepted answer (as of 2020-02-05) is:
ProcessInfo.processInfo.systemUptime
It is used in XCtest and it …
… ultimately calls down to mach_absolute_time on Darwin, and clock_gettime with CLOCK_MONOTONIC on Linux.
- Imprecision w.r.t. conversion to Double is a non-issue for this level of API.
- Using
mach_absolute_time is the right answer on OS X, via a wrapper in Foundation seems reasonable to me.
(source)
And also:
14 Likes
macOS 10.15 has new XCTest APIs for measuring time and other performance metrics.
It makes the test method from your Improving Float.random(in:using:) post a bit simpler.
import RandomFloat
import XCTest
final class RandomFloatTests: XCTestCase {
override class var defaultMetrics: [XCTMetric] {
[
XCTClockMetric(),
XCTCPUMetric(limitingToCurrentThread: true),
XCTMemoryMetric(),
XCTStorageMetric(),
]
}
func test() {
let floatsPerRun = 10_000_000
var meanSum = 0.0
var numRuns = 0
var prng = WyRand()
measure(metrics: Self.defaultMetrics) {
var sum = 0.0
for _ in 0 ..< floatsPerRun {
let v = pseudorandomFloatInClosedUnitRange(using: &prng)
sum += Double(v)
}
meanSum += sum
numRuns += 1
}
print("Total mean float value:",
meanSum / Double(floatsPerRun * numRuns))
}
}
1 Like
Jens
3
It depends on what you mean by simple. I'd say using XCTest APIs is not as simple as:
- Write/download/copy-paste a single source file (command line) program
- Compile (from command line according to instructions within the file)
- Run
And XCTest is macOS (and Xcode?) only.
Also, according to my experience, testing for performance will almost always mean bumping into various context dependent optimizer glitches etc, and therefore it's important to be able to measure and profile some piece of code in many different contexts. XCTest forces your code into a specific context and is a lot of boilerplate.
Jens
5
XCTest seems to use
Foundation.ProcessInfo.processInfo.systemUptime
to measure time.
The choice of ProcessInfo.processInfo.systemUptime is discussed in apple/swift-corelibs-xctest#109 (search for mach_absolute_time in the conversation).
For a single source benchmark, you could still have a separate measure function, to contain the boilerplate code.
You could also put assert(false, "Compile with optimizations") at the top of the file, if you wanted to stop accidental -Onone results.
3 Likes
Jens
7
I quote the relevant part here:
I am no expert in benchmarking, so I am not especially confident in the optimal mechanism for measuring the passage of time for this purpose, however after some cursory research, I settled on using NSProcessInfo 's systemUptime property, which ultimately calls down to mach_absolute_time on Darwin, and clock_gettime with CLOCK_MONOTONIC on Linux. As far as I can tell, these are the appropriate primitives to use for this purpose in these environments. One potential source of error is introduced in that the time values are being converted to Double , but I'm unsure of the practical impact of that.
And the reply from Daniel Dunbar:
The time measurement parts sound reasonable to me for an initial implementation:
- Imprecision w.r.t. conversion to Double is a non-issue for this level of API.
- Using
mach_absolute_time is the right answer on OS X, via a wrapper in Foundation seems reasonable to me.
1 Like
Jens
9
So it looks like
ProcessInfo.processInfo.systemUptime
is the answer to the question of this thread then, thanks!
1 Like
mattrips
(Matt Rips)
10
I'm assuming that this is the answer, because (1) ProcessInfo is part of the Foundation framework, and thus is the solution closest to being fully available on all platforms, and (2) its use by the XCTest framework seems to validate its reliability for the benchmarking use case.
1 Like
AlexanderM
(Alexander Momchilov)
11
Aren't tests run in debug mode, with minimal optimizations? How would you ensure you're measuring "real" (prod-build) performance using these APIs?
scanon
(Steve Canon)
12
Tests are run in whatever mode you tell the build system to run the tests. It’s certainly possible to write tests that are intended to be run in release mode.
@AlexanderM You can use swift test --configuration release on the command line; or edit the scheme in Xcode, so the Test action uses the Release build configuration.
And as I mentioned earlier, you can add an assert(false, "Compile with optimizations") statement to enforce this.
2 Likes
CFAbsoluteTimeGetCurrent() don't give any guarantee about its resolution (and don't use an high resolution clock under the hood AFAIK).
Jens
16
See edit of OP, I added the (currently) accepted answer, which is not CFAbsoluteTimeGetCurrent().
The documentation doesn't mention resolution. The implementation uses:
-
the GetSystemTime function on Windows, where the SYSTEMTIME structure has a resolution of 1 millisecond.
-
the "obsolescent" gettimeofday function on all other platforms, where the timeval structure has a resolution of 1 microsecond.
On macOS:
import Darwin
// Resolution of `gettimeofday`:
var rt = timespec()
clock_getres(CLOCK_REALTIME, &rt)
print(rt) //> timespec(tv_sec: 0, tv_nsec: 1000)
// Resolution of `mach_absolute_time`:
var ut = timespec()
clock_getres(CLOCK_UPTIME_RAW, &ut)
print(ut) //> timespec(tv_sec: 0, tv_nsec: 1)
I've opened SR-12124 to improve ProcessInfo.processInfo.systemUptime for BSD and Windows.
But I think DispatchTime.now().uptimeNanoseconds is the only cross-platform API (from the original list) which seemingly guarantees "nanosecond precision".
3 Likes