8 ms·
Swift's native Clocks are inefficient
- foolswisdom 3y ago> we’re talking a mere 19 to 30 nanoseconds to get the time elapsed since a reference date and compare it to a threshold. The table shows 19 or 30 milliseconds for Date / NSDate. Or am I misunderstanding something?
- Medea 3y ago"showing the median runtime of the benchmark, which is a million iterations of checking the time"
- foolswisdom 3y agoThanks.
- taspeotis 3y agoDivide by a million
- netruk44 3y agoI've been learning me some Swift and coming from C# I feel somewhat spoiled when it comes to timing things. In C# native Stopwatch class is essentially all you need for simple timers with sub-millisecond precision. Swift has not only the entire table of options from TFA to choose from, but also additional ones like DispatchTime [0]. They might all boil down to the same thing (mach_absolute_time, according to the article), but from the perspective of someone trying to learn the language, it's all a little confusing. Especially since there's also hidden bottlenecks like the one this post is about. [0]: https://developer.apple.com/documentation/dispatch/dispatchtime https://developer.apple.com/documentation/dispatch/dispatcht...
- chubs 3y agoJust use CACurrentMediaTime for that, or Date(), both simple options :)
- fathyb 3y agoI believe `CACurrentMediaTime` depends on `QuartzCore.framework` and `Date` is not monotonic. I would also find it confusing if I found code doing something like network retries using `CACurrentMediaTime`.
- vlovich123 3y agoClock_gettime
- tialaramex 3y agoIn the Swift library documentation itself, hopefully a Swift person can tell me: What is the significance of the list of Apple platforms given? For example the Clock protocol shows iOS 16.0+ among others. I can imagine that e.g. ContinuousClock is platform specific - any particular system may or may not be able to present a clock which exhibits change despite being asleep for a while and so to the extent Apple claim Swift isn't an Apple-only language, ContinuousClock might nevertheless have platform requirements. But the protocol seems free from such a constraint. I can write this protocol for some arbitrary hardware which has no concept of time, I can't implement it, but I can easily write the protocol down, and yet here it is iOS 16.0+ anyway.
- Daedren 3y agoApple just doesn't backport APIs, it's a very very very rare occurence when it happens. It was introduced last year alongside iOS 16 so you require the latest OS, it's the norm really.
- tialaramex 3y agoI guess maybe I didn't explain myself well. Swift is supposedly a cross platform language. This "protocol" unlike the specific clocks certainly seems like something you could equally well provide on say, Linux. But, it's documented as requiring (among others) iOS 16.0+ Maybe there's a separate view onto this documentation if you care about non-Apple platforms? Or maybe there's just an entirely different standard library for everybody else?
- lukeh 3y agoSame standard library (Foundation has some differences, that's another story). But the documentation on Apple's website only covers their own platforms.
- rockbruno 3y agoThe standard library stopped being bundled with Swift apps when ABI stability was achieved. They are now provided as dynamic libraries alongside OS releases, so you can only use Swift features that match the library version for a particular OS version.
- koenneker 3y agoMight this be a hamfisted reaction to timing attacks?
- xyst 3y agoPost mentions this Apple doc, https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time https://developer.apple.com/documentation/kernel/1462446-mac..., which states it can potentially be used to fingerprint a device? How can this API be used to fingerprint devices? It’s just getting the current time. My best guess, you can infer a users time zone? Thus get a very general/broad area of where this user lives (USA vs EU; or US-EST vs US-PST) Maybe I should just set my time to UTC on all devices
- simcop2387 3y agoThe same way that you can do it from javascript I'd imagine. Timezones and such are one data point but the skews and accuracy and such are able to help you differentiate users too. https://forums.whonix.org/t/javascript-time-fingerprinting/7852 https://forums.whonix.org/t/javascript-time-fingerprinting/7...
- lapcat 3y agomach_absolute_time is unrelated to clock time. It's basically the number of CPU cycles since last boot, so it's more of an uptime measure. I suspect the fingerprinting aspect is more indirect: mach_absolute_time is the most accurate way to measure small differences, so if you're trying to measure subtle differences in performance between different devices on some specific task, mach_absolute_time would be the way to go.
- thealistra 3y agoYeah this is correct. Other comments seem misinformed. You can fingerprint a device using this because you know the wall clock difference and you know the previous cpu cycles. So you can assume any device with appropriately more cpu cycles may be the same device. We’re talking measurements taken from different apps using Google or Facebook sdk.
- MBCook 3y ago> It's basically the number of CPU cycles since last boot, so it's more of an uptime measure And there’s the problem. Different devices have different uptimes. If you can get not only the uptime but a very very accurate version, you’ve got a very strong fingerprint.
- user2342 3y agoI'm not fluent in Swift and async, but the line: for try await byte in bytes { ... } for me reads like the time/delta is determined for every single byte received over the network. I.e. millions of times for megabytes sent. Isn't that a point for optimization or do I misunderstand the semantics of the code?
- ajross 3y agoYeah, this is horrifying from a performance design perspective. But in this case you'd still expect that the "current time" retrieval[1] to be small relative to all the other async overhead (context switching for every byte!), and apparently it isn't? [1] On x86 linux, it's just a quick call into the vdso that reads the TSC and some calibration data, dozen cycles or so.
- jerf 3y agoNote the end of the article acknowledges this, so this is clearly a deliberate part of the constructed example to make a particular point and not an oversight by the author. But it is helpful to highlight this point, since it is certainly a live mistake I've seen in real code before. It's an interesting test of how rich one's cost model for running code is.
- marcosdumay 3y agoThe stream reader userspace libraries are very well optimized for handling that kind of "dumb" usage that should obviously create problems. (That's one of the reasons Linux expects you to use glibc instead of making a syscall directly.) But I imagine the time reading ones aren't as much optimized. People normally do not call them all the time.
- saagarjha 3y agoThey look very similar on macOS.
- samatman 3y agoThe code, as the author makes clear, is an MWE. It provides a brief framework for benchmarking the behavior of the clocks. It's not intended to illustrate how to efficiently perform the task it's meant to resemble.
- Shrezzing 3y agoThis is almost certainly intentional, and is very similar to the way web browsers mitigate the Spectre vulnerability[1]. Your processor (almost certainly) does some branch prediction to improve efficiency. If an application developer reliably knows the exact time, they can craft an application which jumps to another application's execution path, granting them complete access to its internal workings. To mitigate this threat, javascript engine developers simply added a random fuzzy delay to all of the precision timing techniques. Swift's large volume of calls to unrequired methods is, almost certainly, Apple's implementation of this mitigation. [1] https://en.wikipedia.org/wiki/Spectre_(security_vulnerability) https://en.wikipedia.org/wiki/Spectre_(security_vulnerabilit...
- beeboobaa3 3y agoHave to protect those pesky application developers from knowing the time so they can write correct software. It makes sense for a web browser. Not for something like Swift.
- vlovich123 3y agoNo, this is pretty clearly just a bug / poor design. Mistakes happen.
- beeboobaa3 3y agoProbably but I'm just responding to GP who implied that Apple, in all its infinite wisdom, did this on purpose.
- fathyb 3y agoIf this was intentional, shouldn't it also affect `mach_absolute_time` which is used by the standard libraries of most languages and accessible to Swift? Also note you can get precise JavaScript measurements (and threading, eg. using pthreads and Emscripten) by adding some headers: https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated https://developer.mozilla.org/en-US/docs/Web/API/Window/cros...
- rfmoz 3y agoOSX clock_gettime() [0] offers CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW, but not CLOCK_UPTIME, only CLOCK_UPTIME_RAW. Maybe someone knows why? On FreeBSD it is available [2]. [0]: https://www.manpagez.com/man/3/clock_gettime_nsec_np/ https://www.manpagez.com/man/3/clock_gettime_nsec_np/ [2]: https://man.freebsd.org/cgi/man.cgi?query=clock_gettime https://man.freebsd.org/cgi/man.cgi?query=clock_gettime
- loeg 3y agoAre you talking about CLOCK_UPTIME_FAST on FreeBSD? It does not have CLOCK_UPTIME_RAW. In FreeBSD, the distinction lives here: http://fxr.watson.org/fxr/source/kern/kern_time.c#L352 http://fxr.watson.org/fxr/source/kern/kern_time.c#L352
- jepler 3y agoI was curious how linux's clock_gettime compared. I wrote a simple program that tried all the clock types documented in my manpage: https://gist.github.com/jepler/e37be8fc27d6fb77eb6e9746014db925 https://gist.github.com/jepler/e37be8fc27d6fb77eb6e9746014db... My two handy systems were an i5-1235U running 6.1.0-20-amd64 and a Ryzen 7 3700X also running 6.1.0-20-amd64. The fastest method was 3.7ns/s call on the i5 and 4ns/call on the Ryzen (REALTIME_COARSE and MONOTONIC_COARSE were about the same). If a "non-coarse" timestamp is required, the time increases to about 20ns/call on ryzen, 12ns on i5. (realtime, tai, monotonic, boottime). On the i5, if I force the benchmark to run on an efficiency core with taskset, times increase to 6.4ns and 19ns.
- jeffbee 3y agoYou can knock almost a third off that fastest time by building with `-static`. In something that is completely trivial like reading the clock via vDSO the indirect call overhead of dynamic libc linking becomes huge. `-static` eliminates one level of indirect calls. The indirect vDSO call remains, though. % ./clocktest | rg MONOTONIC_COARSE MONOTONIC_COARSE : 2.2ns percall
- feverzsj 3y agoI know swift has poor performance, but not expect they did it purposely.
- sholladay 3y agoMaybe unrelated, but I’ve noticed that setting a timer on iOS drains my battery more than I would expect, and the phone gets warm after a while. It’s just bad enough that if the timer is longer than 15 minutes, I often use an alarm instead of a timer. Not something I’ve experienced on Android.
- whywhywhywhy 3y agoLot of glitches in timers and alarms on recent iOS, sometimes they don’t even fire. Extremely poor setting like a timer for cooking, checking your phone and there just isn’t a timer running anymore and your left wondering how far it’s overshot. 15 Pro so definitely not an old phone new software issue.
- saagarjha 3y agoHave you tried profiling the device to see why?
- beeboobaa3 3y agoStill hilarious how apple goes about their "system security". Instead of actually implementing security in the kernel they just kinda prevent you from distributing an app that may call that functionality. Because that way they can still let their buddies abuse it without appearing too biased (by e.g. having a whitelist on the device). This technical failing probably, partially, explains why they are so against allowing sideloading. That, and they're scared of losing their cash cow of course.
- asveikau 3y agoThe hilarious thing is how people justify Apple's bugs with a security concern. Just squinting at the stack trace from the article, my intuition is that someone at Apple added a bunch of nice looking object-oriented stuff without regard for overhead. So a call to get a single integer from the kernel, namely the time, results in lots of objects being created on the heap and tons of "validation" going on. Then somebody on hacker news says this is all for your own good.
- NotPractical 3y ago> This technical failing probably, partially, explains why they are so against allowing sideloading. This occurred to me the other day. I've always laughed at the idea that Apple blocks sideloading for security purposes, but if the first line of defense is and always has been security through obscurity + manual App Store review (>= 2.0) on iOS, it's very possible that sideloading could cause problems. iOS didn't even have an App Store in release 1.0, meanwhile the Android security model has taken into account sideloaded apps since the very beginning [1]: > Android is designed to be open. [...] Securing an open platform requires a strong security architecture and rigorous security programs. Android was designed with multilayered security that's flexible enough to support an open platform while still protecting all users of the platform. [1] https://source.android.com/docs/security/overview https://source.android.com/docs/security/overview Edit: Language revised to clarify that I'm poking fun of the idea and not the one who believes it.
- threatofrain 3y agoIs there a reputation of a security difference between Android and iOS? And in what direction does the badness lean?
- simscitizen 3y agoJust use clock_gettime with whatever clock you want. There’s also a np (non-POSIX) suffixed variant that returns the timestamp in nanoseconds.
- Kallikrates 3y agohttps://github.com/apple/swift/pull/73429 https://github.com/apple/swift/pull/73429
- loeg 3y agoWow, hundreds of milliseconds is a lot worse than I'd expect. I'm not shocked that it's slower than something like plain `rdtsc` (single digit nanoseconds?) but that excuses maybe microseconds of overhead -- not milliseconds and certainly not hundreds of milliseconds.
- gok 3y agoIt's hundreds of milliseconds to do a a million iterations. A single time check is hundreds of nanoseconds.
- loeg 3y agoOh, thanks. The table was unlabeled and I missed that in the text. Hundreds of nanos isn't great but it's certainly better than milliseconds.
- layer8 3y agoThat’s for a million iterations, so really nanoseconds.
- diebeforei485 3y agoIt's required of native apps because native apps are full of API's that collect and sell user data. That's why.
- adsharma 3y agoclock_gettime_nsec_np() seems interesting in that it returns a u64. I proposed something similar for Linux circa 2012. The patch got lost in some other unrelated discussion and I didn't pursue it. struct timeval is a holdover from the 32 bit era. When everyone is using 64 bit machines, we should be able to get this data by reading one u64 from a shared page.
- adsharma 3y agohttps://lkml.org/lkml/2011/12/12/438 https://lkml.org/lkml/2011/12/12/438